Page History
비동기적으로 작동하는 이벤트(Actor메시지)의 성능을 유닛테스트기를 활용하여 측정할수 있는 방법을 닷넷 환경에서 비동기적으로 작동하는 이벤트의 Message Delivery Once 를 검증하는방법과
Nbench를 이용하여 유닛테스트환경에서 성능측정을 할수 있는방법을 소개합니다.
이 샘플에서는 액터성능 테스트가 포함되어 있지만 액터모델과 상관없이 유닛테스트내에서 성능테스트툴을 이용할수 있습니다.액터메시지 모델이 이용되었지만 액터모델과 상관없이~
유닛테스트내에서 성능측정 기법을 이용할수 있습니다.
측정할 수 없는 것은 관리할수 없다 - 피터 드러크
| Table of Contents |
|---|
테스트 탐색기
...
테스트 탐색기를 통해 성능유닛 테스트를 수행하고 측정할수 있습니다.
...
- 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 -자바버전으로도 병렬 작성중입니다.
기본 유닛테스트
...
| Code Block | ||
|---|---|---|
| ||
[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이 제공하는 큐검사 방식으로
...
| Code Block |
|---|
testProbe.ExpectMsg("world"); |
Nbench에서 지원하는 함수로 수신검사가 되면 완료되면 성능카운터를 1 증가하는 심플한 측정방법입니다. 기본유닛테스트기에서는 로직검사만 수행할수 증가합니다.
성능 테스트모드일때만 수행할수 있으며 기본 유닛테스트에서는 성능테스트 제외하여 도메인검증에 집중할수 있습니다.
| Code Block |
|---|
_addCounter.Increment(); |
...
유닛테스트가 완료된 로직의 성능테스트가 필요시 Nbench를 활용하여 성능 유닛테스트 검사기를 추가할수 있습니다.
| Code Block | ||
|---|---|---|
| ||
[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가 제공하는 테스트 탐색기를 통해서도 수행할수 있습니다.
성능측정 리포트
...
| Code Block | ||
|---|---|---|
|
...
[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개 동시 발생시킬수 있지만, TPS제약을 할수있는 ThrottleLimitActor 가 준비되었으며
| Code Block | ||
|---|---|---|
| ||
[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을 유지하였는가? 는 다음과 같이 분리하여 측정될수 있습니다.
| Code Block | ||
|---|---|---|
| ||
[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)]
성능제약 통과 로그
| Code Block | ||
|---|---|---|
| ||
[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 으로 설정한 케이스)
| Code Block |
|---|
[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
여기서 확장된 개념이 소비자의 소비능력을 측정하여 생산량을 동적 TPS조절할수 있는 조절기를 설계하는것이 BackPressure 입니다.
GC 성능 테스트기
...
| Code Block |
|---|
---------- Measurements ----------
Metric : TotalCollections [Gen0]
Per Second ( collections )
Average : 251.73166139361632
Max : 305.97882626522244
Min : 178.82369771642138
Std. Deviation : 47.515272144026895
Std. Error : 15.025648361787713
Per Test ( collections )
Average : 1
Max : 1
Min : 1
Std. Deviation : 0
Std. Error : 0
----------
Metric : TotalCollections [Gen1]
Per Second ( collections )
Average : 251.73166139361632
Max : 305.97882626522244
Min : 178.82369771642138
Std. Deviation : 47.515272144026895
Std. Error : 15.025648361787713
Per Test ( collections )
Average : 1
Max : 1
Min : 1
Std. Deviation : 0
Std. Error : 0
|
GC 성능테스트 측정도 이용할수 있는것은 보너스입니다.
이상 유닛테스트와 함께 심플한 성능테스트를 함께 할수 있는 방법을 살펴보았으며
측정할 수 없으면 성능 개선을 할수 없는것과 마찬가지로 여기서 이용된 기술과 관련기술 링크도 첨부합니다.
JUnit with Bench
자바에서는 JHM를 이용하여 마이크로 벤치마크할수 있으며 대응하는 버전도 동일하게 준비되어있습니다.
참고링크 :
- https://getakka.net/articles/actors/testing-actor-systems.html
- https://nbench.io/
- https://github.com/Pro-Coded-External/Pro.NBench.xUnit
- Introducing NBench - Automated Performance Testing and Benchmarking for .NET
- https://blog.knoldus.com/backpressure-in-akka-stream/
- https://www.baeldung.com/java-microbenchmark-harness


