비동기적으로 작동하는 이벤트(Actor메시지)의 성능을 유닛테스트기를 활용하여 측정할수 있는 방법을 소개합니다.
이 샘플에서는 액터성능 테스트가 예시되어있지만 액터모델과 상관없이 유닛테스트내에서 심플한 측정모드를 이용할수 있습니다.
테스트 탐색기
테스트 탐색기를 통해 성능유닛 테스트를 수행하고 측정할수 있습니다.
여기서 설명하는 작동코드는 깃헙을 통해서도 확인할수 있으며
추가로 다양한 액터모델을 테스트하고 학습할수 있는 코드를 지속 업데이트 예정입니다.
git :
- https://github.com/psmon/NetCoreLabs/blob/main/ActorLibTest/Intro/RoutersTest.cs - 닷넷버전
- https://github.com/psmon/java-labs/blob/master/springweb/src/test/java/com/webnori/springweb/akka/README.md -자바버전으로도 병렬 작성중입니다.
기본 유닛테스트
[Theory(DisplayName = "RoundRobinPoolTest")]
[InlineData(3,1000)]
public void RoundRobinPoolTest(int nodeCount, int testCount, bool isPerformTest = false)
{
var actorSystem = akkaService.GetActorSystem();
TestProbe testProbe = this.CreateTestProbe(actorSystem);
var props = new RoundRobinPool(nodeCount)
.Props(Props.Create(() => new BasicActor()));
var actor = actorSystem.ActorOf(props);
for (int i = 0; i < nodeCount; i++)
{
actor.Tell(testProbe.Ref);
}
int cutOff = 3000;
Within(TimeSpan.FromMilliseconds(cutOff), () =>
{
for (int i = 0; i < testCount; i++)
{
actor.Tell("hello" + i);
}
for (int i = 0; i < testCount; i++)
{
testProbe.ExpectMsg("world");
if (isPerformTest)
{
_dictionary.Add(_key++, _key);
_addCounter.Increment();
}
}
});
}
측정을 위해서는 비동기적으로 발생하는 이벤트가 완료되었다란 정의를 어떻게 내리고 측정을 해야할까요?
위코드는 단순하게 이벤트 생성에서만 측정하는것이아닌 메시지함에서 확인함까지 완료하는 검증로직이며
Message delivery once 수준을 검증 합니다.
Akka테스트 툴킷
메시지 수신함에서 메시지가 있는지를 하나씩 꺼내 체크하는 Akka Testkit이 제공하는 큐검사 방식으로
핵심로직을 블락을 시키지 않고 검사할수 있는 방법입니다.
testProbe.ExpectMsg("world");
Nbench에서 지원하는 함수로 수신검사가 완료되면 성능카운터를 1 증가합니다.
성능 테스트모드일때만 수행할수 있으며 기본 유닛테스트에서는 성능테스트 제외하여 도메인검증에 집중할수 있습니다.
_addCounter.Increment();
유닛테스트가 완료된 로직의 성능테스트가 필요시 Nbench를 활용하여 성능 유닛테스트 검사기를 추가할수 있습니다.
[NBenchFact]
[PerfBenchmark(NumberOfIterations = 3, RunMode = RunMode.Throughput,
RunTimeMilliseconds = 1000, TestMode = TestMode.Test)]
[CounterThroughputAssertion("TestCounter", MustBe.GreaterThan, 1000.0d)]
[CounterTotalAssertion("TestCounter", MustBe.GreaterThan, 1500.0d)]
[CounterMeasurement("TestCounter")]
public void RoundRobinPoolTestPerformanceTest()
{
RoundRobinPoolTest(5, 3000, true);
}
주요 설정 옵션은 다음과 같습니다.
- NumberOfIterations : 테스트 횟수
- RunTimeMilliseconds : 측정 기준초 , 1000ms 여야지 TPS(Test Per Sec) 측정입니다.
- CounterThroughputAssertion : 최소 완료 회수로 이 값을 준수를 못하면 성능 테스트 실패합니다.
- CounterTotalAssertion : 평균 완료회수로 이 값을 준수못하면 성능 테스트 실패합니다.
성능 유닛트세트가 작성되면, VS-IDE가 제공하는 테스트 탐색기를 통해서도 수행할수 있습니다.
성능측정 리포트
[PASS] Expected [Counter] TestCounter to must be greater than 1,000.00 operations; actual value was 94,160.06 operations.
[PASS] Expected [Counter] TestCounter to must be greater than 1,500.00 operations; actual value was 93,000.00 operations.
---------- Measurements ----------
Metric : [Counter] TestCounter
Per Second ( operations )
Average : 94160.06329051661
Max : 94937.13528317196
Min : 93266.78965179897
Std. Deviation : 841.2138544819605
Std. Error : 485.6750453312026
Per Test ( operations )
Average : 93000
Max : 93000
Min : 93000
Std. Deviation : 0
Std. Error : 0
----------
성능측정이 통과가 되면 간단한 리포팅이 표시가 되며 성능측정이 통과유무의 정보가 표현됩니다.
- [PASS] Expected [Counter] TestCounter to must be greater than 1,000.00 operations; actual value was 94,160.06 operations.
- CounterThroughputAssertion 에서 설정된 값 이상인 최소 TPS 통과됨을 의미하며 추가로 94160 수행되었음을 표현합니다.
- [PASS] Expected [Counter] TestCounter to must be greater than 1,500.00 operations; actual value was 93,000.00 operations.
- CounterTotalAssertion 에서 설정된 값 이상인 평균 TPS가 통과됨을 의미하며 평균 수행횟수가 93000임을 표현합니다.
성능제약 테스트
다양한 이유로 호출량을 통제해야할 필요도 있습니다. 최종 소비자가 충분한 소비를 하지못하는경우 생산을 제약하는 경우이며
성능제약이 잘 작동하는지도 검증할수 있습니다.
이벤트가를 N개 동시 발생하며, 소비가 되는구간에서 성능카운트를 1올려줍니다.
[Theory(DisplayName = "테스트 n초당 1회 호출제약")]
[InlineData(5, 1, false)]
public void ThrottleLimitTest(int givenTestCount, int givenLimitSeconds, bool isPerformTest)
{
.......
// Create ThrottleLimit Actor
throttleLimitActor = actorSystem.ActorOf(Props.Create(() => new ThrottleLimitActor(1, givenLimitSeconds, 1000)));
throttleLimitActor.Tell(new SetTarget(probe))
for (int i = 0; i < givenTestCount; i++)
{
throttleLimitActor.Tell(new EventCmd()
{
Message = "test",
});
}
//Then : Safe processing within N seconds limit
for (int i = 0; i < givenTestCount; i++)
{
probe.ExpectMsg<EventCmd>(message =>
{
Assert.Equal("test", message.Message);
});
output.WriteLine($"[{DateTime.Now}] - GTPRequestCmd");
if (isPerformTest)
{
_dictionary.Add(_key++, _key);
_addCounter.Increment();
}
}
...........
}
도메인 검증은 발생메시지가 모두 소비되었나를 검증하며 , TPS 1을 유지하였는가 성능검증은 다음과같이 작성합니다.
[NBenchFact]
[PerfBenchmark(NumberOfIterations = 3, RunMode = RunMode.Throughput,
RunTimeMilliseconds = 1000, TestMode = TestMode.Test)]
[CounterThroughputAssertion("TestCounter", MustBe.LessThanOrEqualTo, 1.0d)]
[CounterTotalAssertion("TestCounter", MustBe.LessThanOrEqualTo, 1)]
[CounterMeasurement("TestCounter")]
public void ThrottleLimitPerformanceTest()
{
ThrottleLimitTest(1, 1, true);
}
통과옵션을을 1보다 작다로 설정을 하여 TPS1이하인지 검증을 할수가 있습니다.
- [CounterThroughputAssertion("TestCounter", MustBe.LessThanOrEqualTo, 1.0d)]
- [CounterTotalAssertion("TestCounter", MustBe.LessThanOrEqualTo, 1)]
성능제약 통과 로그
[PASS] Expected [Counter] TestCounter to must be less than or equal to 1.00 operations; actual value was 0.98 operations.
[PASS] Expected [Counter] TestCounter to must be less than or equal to 1.00 operations; actual value was 1.00 operations.
---------- Measurements ----------
Metric : [Counter] TestCounter
Per Second ( operations )
Average : 0.9802234345891607
Max : 0.9823810933292493
Min : 0.9787775577268321
Std. Deviation : 0.0019042957466220024
Std. Error : 0.0010994456619288725
Per Test ( operations )
Average : 1
Max : 1
Min : 1
Std. Deviation : 0
Std. Error : 0
검증 옵션을 GreaterThanOrEqualTo 반대로 설정을 하면 실패가 발생하며 아래와같이 검증 실패가 발생하게됩니다.
[FAIL] Expected [Counter] TestCounter to must be greater than or equal to 1.00 operations; actual value was 0.98 operations.
Expected: True
Actual: False
샘플 코드 : https://github.com/psmon/NetCoreLabs/blob/main/ActorLibTest/tools/ThrottleLimitActorTest.cs
이상 유닛테스트와 함께 심플한 성능테스트를 함께 할수 있는 방법을 살펴보았으며
측정할수 없으면 개선할수 없으며~ 활용된 기술의 링크도 추가하였습니다.
참고링크 :
- https://getakka.net/articles/actors/testing-actor-systems.html
- https://nbench.io/
- https://github.com/Pro-Coded-External/Pro.NBench.xUnit


