Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Archives
Today
Total
관리 메뉴

장미정원

Spring Webflux, MVC + 성능 테스트 본문

Back-end

Spring Webflux, MVC + 성능 테스트

신희성 2025. 1. 31. 17:46

 

 

Spring WebFlux, MVC Stack에 대해

 

WebFlux

Spring WebFlux는 reactive web stack 프래임워크이며, non-blocking I/O를 지원하고 event-loop 방식으로 적은 양의 스레드로 대용량 요청을 처리할 수 있습니다.

WebFlux 스택을 기존에 사용하는 MVC 모델과 비교하며 설명하도록 하겠습니다.

 

MVC Thread Per Request 모델

Spring MVC는 요청을 처리할 때 blocking 방식으로 처리합니다. (DB I/O, HTTP req ex. RestTemplate)

MVC 서버는 요청이 들어올 때 ThreadPool에서 Thread를 꺼내서 요청 하나에 할당합니다. (thread per request)

 

일반적인 경우에는 이러한 방식이 문제되지 않습니다. 하지만 blocking 처리를 진행 할 때는 해당 thread가 대기 상태가 됩니다. 이러한 thread는 해당 blocking 처리가 끝날 때 까지 대기하다 모든 요청 처리가 끝나고 나서 ThreadPool에 반환됩니다.

 

spring boot에서 기본적으로 사용하는 tomcat was의 default tread pool의 thread 갯수는 200개 입니다.

DB I/O에 5초 이상이 걸리는 서버가 있다고 가정을 하고, 만약 300개의 요청이 한번에 서버에 들어왔다면, 먼저온 200개의 요청은 하나씩 thread가 할당되어 요청을 처리합니다. 하지만 db blocking I/O로 5초동안 200개의 thread는 대기상태가 됩니다.

이렇게 되면 남은 300개의 요청은 thread를 할당받지 못해 대기하거나 요청을 처리하지 못해 예외가 발생하게 됩니다.

 

tomcat thread pool의 설정은 application.yml 파일에서 설정할 수 있습니다. 이러한 문제를 해결하기 위해 thread pool의 thread 수를 200개 이상으로 설정하거나 thread가 부족할 때 더 많은 thread를 만드는 등의 설정을 할 수 있지만,

thread를 너무 많이 구성하게 된다면, 서비스의 유후 기간에는 자원 낭비가 될 수 있고 cpu core 수의 대비해서 너무 많은 양의 thread를 할당받게 된다면 context switching 비용이 많이 들 수 있습니다.

 

이러한 문제를 근본적으로 해결하려면 thread를 할당받은 요청을 처리할 때 대기상태를 최대한 줄이고 thread의 사용률을 늘려야합니다.

 

WebFlux Reactive Application

reactive applicationnon-blocking의 처리와 같이 요청한 처리 결과를 기다리지 않고 변화에 반응하는 것을 중점으로 만들어진 application 입니다.

WebFlux reactive application은 기존의 thread per request 모델이 아닌 event loop 모델로 동작하여, I/O와 같은 작업에서 thread를 대기시키지 않고 해당 I/O 작업이 끝낼 때 까지 다른 요청을 처리합니다. 그 후 I/O 작업이 종료되면 Event Loop는 콜백 형태로 작업의 결과값을 전달하게 됩니다.

 

 

Event Loop

 


WebFlux는 reactive application을 구현할 때 Event loop 모델을 사용합니다. event loop에서 사용하는 thread의 갯수는 일반적으로 cpu core 갯수만큼 구성되어 있습니다.

 

DB I/O, Network I/O 등의 처리를 이벤트 큐에 쌓아 두고 비동기적으로 실행 후 콜백을 통해 응답 값을 반환합니다.

이렇게 적은 양의 thread로 수 많은 요청을 처리할 수 있으며 thread 갯수가 core 갯수 만큼 적게 구성되어 있기 때문에 context switching 비용이 MVC stack에 비해 덜 든다는 장점이 있습니다.

 

이렇게 non-blocking 방식으로 I/O 작업에 대한 thread 사용률을 효율적으로 가져갈 수 있습니다. 특히 MSA 구조에서 MS 끼리 발생하는 수 많은 Netwok I/O를 non-blocking 하게 처리하한다면 성능 향상을 기대해볼 수 있습니다.

 

하지만 non-blocking 방식을 지원하지 않는 I/O를 처리(ex. JDBC)한다면.. thread는 대기 상태에 빠집니다. 적은 양의 thread로 동작하는 event loop에서 blocking I/O를 처리한다면 심각한 성능 저하가 발생하기 때문에 WebFlux를 사용할 때는 모든 I/O처리가 non-blocking I/O인지 확인하여야 합니다.

 

 

참고 자료: https://dev-jj.tistory.com/entry/Spring-WebFlux-EventLoop-Non-Blocking

 

 

WebFlux, MVC 성능 테스트

동일한 스펙의 API를 가진 WebFlux와 MVC Stack의 Spring Boot Server에 부하를 주어 성능 테스트를 진행해보도록 하겠습니다.

성능 테스트는 jmeter를 사용하였습니다.

 

먼저 성능을 테스트할 API에 대해 소개하겠습니다.

 

1. NetWork I/O API

  • 2초의 delay가 있는 HTTP 요청을 보내 응답 값을 반환하는 API

2. DB I/O API

  • 2건의 select query, 1건의 update query, 1건의 insert query를 처리하는 API

실제 프로젝트와 같이 예제 환경을 구성하였습니다.

 

NetWork I/O API

https://httpbin.org/delay/2 해당 url에 HTTP 요청을 하여 response를 반환합니다.

 

jmeter 설정

  • Number of Threads: 2000
  • Ramp-up period: 50s
  • Loop Count: 5

MVC

RestTemaplate을 사용하여 요청하였습니다.

 

결과

  • 처리량: 75.8/s
  • 응답 평균: 14,377ms (약 14초)

요약보고서

 

TPS 그래프

 

응답 시간 그래프

 

WebFlux

WebClient를 사용하여 요청하였습니다.

 

 

결과

  • 처리량: 153/s
  • 응답 평균: 2,751ms (약 3초)

요약 보고서

 

TPS 그래프

 

응답 시간 그래프

 

 

 

DB I/O API

2건의 select query, 1건의 update query, 1건의 insert query를 처리합니다.

모두 RDBMS인 Mysql 데이터베이스를 사용합니다.

 

jmeter 설정

  • Number of Threads: 2000
  • Ramp-up period: 50s
  • Loop Count: 5

 

MVC

Spring Data JPA를 사용하여 처리하였습니다.

 

아래와 같은 Thread Pool 설정을 적용하였습니다.

server:
    tomcat:
    max-connections: 10000
    accept-count: 1000
    threads:
    max: 3000
    min-spare: 1000

 

 

결과

  • 처리량: 519.4/s
  • 응답 평균: 5,620ms (약 6초)

요약 보고서
TPS 그래프

 

응답 시간 그래프

 

Thread Status TimeLine

 

WebFlux

R2DBC를 사용하여 처리하였습니다.

 

결과

  • 처리량: 2,483.2/s
  • 응답 평균: 1,138ms (약 1초)

 

TPS 그래프

 

응답 시간 그래프

 

Thread Status TimeLine

 

 

성능 테스트 결과

Network I/O API의 결과 MVC 서버는 약 80TPS, 평균 레이턴시 약 14초이며 WenFlux 서버는 약 150TPS, 평균 레이턴시 약 3초로 WebFlux가 MVC보다 약 2배 많은 처리량과 5배 빠른 응답 레이턴시를 보여주었습니다.

 

DB I/O API의 결과는 MVC 서버는 약 520TPS, 평균 레이턴시 약 6초이며 WenFlux 서버는 약 2500TPS, 평균 레이턴시 약 1초로 WebFlux가 MVC보다 약 5배 많은 처리량과 5배 빠른 응답 레이턴시를 보여주었습니다.

 

추가로 Thread Status Timeline을 보면 WebFlux는 적은 양의 스레드를 긴 대기 시간을 가지지 않고 아주 좋은 사용 효율을 내고 있는 것을 시각적으로 확인이 가능합니다. 하지만 MVC는 300개의 스레드를 생성하여 blocking DB I/O에 대해 대기 시간를 가지고 컨텍스트 스위칭 비용도 클 것으로 예상됩니다.

 

아래는 테스트를 진행한 application github repository 입니다.

https://github.com/tlsgmltjd/spring-mvc-webflux

 

총총

 

테스트 결과로 I/O 작업이 빈번하게 발생하는 애플리케이션에서는 WebFlux의 효율이 좋다는 것이 확인되었습니다. 하지만 WebFlux가 꼭 정답은 아닙니다.

 

함수형 프로그래밍으로 인해 개발 패러다임이 크게 변경되어 큰 러닝커브가 있습니다. 또한 WenFlux 생태계는 MVC 만큼 성숙하지 않아 여러 제약이 존재합니다. (RDBMS에 대한 non-blocking I/O의 지원도 최근에 진행되었습니다.) 하지만 분명한 이점이 존재하며 계속 발전해 나가고 있기 때문에 신중하게 고려하여 도입해 보는 것도 나쁘지 않다고 생각합니다.