반응형

스프링부트(SpringBoot) 순차적 방식과 비동기처리(asyn애노테이션), Completablefuture와 성능 비교

Spring Boot 기반의 웹 애플리케이션은 HTTP 요청이 들어왔을 때 내장된 서블릿 컨테이너(Tomcat)가 관리하는 독립적인 1개의 Worker 쓰레드에 의해 동기 방식으로 실행된다. 하지만 요청 처리 중 @Async가 명시된 메써드를 호출하면 앞서 등록한 ThreadPoolTaskExecutor 빈에 의해 관리되는 또 다른 독립적인 Worker 쓰레드로 실행된다. 
별도의 쓰레드로 동작하기에 원래의 요청 쓰레드는 그대로 다음 문장을 실행하여 HTTP 응답을 마칠 수 있다.


배경

리얼환경에서의 서비스를 위해 실제 환경(l4 - API서버 3)에서 어느정도의 트래픽을 안전하게 처리할 수 있는지에 대한 산정 필요(서버 도입 시점 등)'

해당 테스트는 단순 select로직을 통한 테스트로 참고 부탁드립니다.

메서드 내에서 여러 곳으로부터의 데이터를 받아와 처리하는 작업이 있는 경우 async애노테이션, CompletableFuture를 사용한 방식이 훨씬이

성능상 훨씬 많은 이점을 가져다 줄 수 있습니다. 

테스트 로직

기존로직

기존로직.png

변경로직 (@Asyn 애노테이션과 CompletableFuture를 통한 비동기 처리)

변경로직.png

쿼리 (위 : 변경쿼리, 아래 : 기존쿼리)

쿼리.png

성능측정

L4장비에 트래픽 부하(gatling) - 부하 툴로는 gatling을 사용하였습니다. 무료로 사용할 수 있는 툴로 쉽게 사용하실 수 있습니다.

1. Request : 9000, Duration 10초  - TPS 300

기존 로직
cm_9000_1.pngcm_9000_2.png

변경로직

asyn_9000_1.pngasyn_9000_2.png

2. Request : 24000, Duration 10초  - TPS 800

기존로직                                                                 변경로직(ThreadPool - 200)                                변경로직(ThreadPool - 400)


ThreadPool설정값 조정

스프링부트를 사용할 경우 별도 application.properties에 server.tomcat.max-threads 다음설정을 해주지 않는 경우

기본 max-thread의 개수는 200개가 된다. (The default value for max-threads is 200)

따라서 400으로 올려서 테스트 진행


스레드의 개수를 600으로 두고 테스트를 했을 떄는 다음과 같은 에러가 발생했다.

[java.util.concurrent.ThreadPoolExecutor@18da28a1[Running, pool size = 741, active threads = 741, queued tasks = 200, completed tasks = 207]] did not accept task: java.util.concurrent.CompletableFuture$AsyncSupply@47ff8bc8] with root cause

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$AsyncSupply@47ff8bc8 rejected from java.util.concurrent.ThreadPoolExecutor@18da28a1[Running, pool size = 741, active threads = 741, queued tasks = 200, completed tasks = 207]

        at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)

        at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)


3. 결론

1번 테스트(TPS 300) 결과를 보면 Asyn annotation과 Completablefuture를 사용한 쪽이 응답 속도가 훨씬 안정적으로 처리되는 것을 볼 수 있다.(99th percentile 기준)
2번 테스트 결과(TPS 800)를 봤을 때 1/3 정도의 요청이 fail나는 것을 확인 할 수 있었고 TPS 800 은 요청에 대한 fail비율과 응답속도(100ms 이내를 기준으로 한다)로 보았을 때 서버 3대로 처리할 수 있는 요청량이 아니라는 판단을 할 수 있다.
그 이후 TPS 400, 500, 600 700으로도 테스트 한 결과 안정적
(최대 50ms - 광고 비지니스 특성상)이내로 ADX, DSP에 값을 전달하기 위해서는 
최대 TPS 300을 기준으로 운영방침을 가져갈 수 있을 것 같다.

TPS 300 -> 한 서버당 하루요청량 2500만을 기준으로 둘 수 있겠다. 

(물론 카산드라 3.0에 데이터가 유입량과 로직 추가에 따른 부분을 항상 염두해 두어야 할 것이다.)



추가적으로, 비동기방식으로 처리를 했을 때 메모리, cpu사용률이 더 높은 것을 확인 할 수 있었다.

무작정 비동기방식이 좋다고 말할 수도 동기 방식이 좋다고도 말하기 힘들 것 같다.

단순한 작업은 동기 방식 한 메서드 내에서 순차적으로 이루어지지 않아도 되는 작업이 여러 개 있는 경우(DB호출, 외부 API호출 등)이 있는 경우에는

비동기 방식이 성능상 이점을 가져다 줄 수 있을 것이다.

반응형

+ Recent posts