Virtual Thread 적용후 비교

결론
- 아래 두가지 조건을 충족한다면 Platform Thread 를 사용하는것을 추천합니다.
- Tomcat 10.1.15 버전 이하
- 적절한 스레드 및 메모리 튜닝 적용
개요
- JDK 21 에서 Virtual Thread 기능이 추가되면서 Spring Boot 3.1 에 각각의 스레드 타입을 적용후 비교해보았습니다~
- JDK 19 에서 Preview Features 로 Virtual Thread 가 추가 되었습니다.
- JDK 21 에서 Feature 로 적용되었습니다.
- 이전 Spring Boot 2.6.15 버전에서 비교
- JVM 내부 스케줄링을 통해 제공되는 경량 스레드입니다.
- 동작하는 동안, OS 에서 제공하는 스레드 (Platform Thread) 와 1대1로 매핑됩니다.
- 블로킹 상황 발생시, 해당 Virtual Thread 는 블로킹 되고 다른 Virtual Thread 가 Platform Thread 에 연결되어 실행됩니다.
- 이때, Platform Thread 가 변경되는게 아니므로 Context Switching 비용이 비교적 저렴합니다.
- Platform Thread 대비 메모리를 적게 사용합니다.
- Platfrom Thread: 약 2KB
- Virtual Thrad: 200~300B
- 따라서 Spring Web MVC 에서 Virtual Thread 적용시, 속도와 처리량이 올라갈것으로 예상됩니다.
- 관련 자료
테스트에 사용한 버전
- JDK: Termurin JDK 21.0.1+12
- Spring Boot: 3.1.5
- Spring Web MVC: 6.0.13
- Spring Web Flux: 6.0.13
테스트
준비물
- CentOS 7.9 환경 (NHN Cloud Instance)
- 슈터
- 타겟
- Spring WebMVC with Platform Thread (128MB Heap Memory)
- Spring WebMVC with Virtual Thread (128MB Heap Memory)
- Spring WebFlux (128MB Heap Memory)
인스턴스 세팅
1. 네트워크 관련 세팅
- 요청량 만큼 포트(소켓=파일) 를 사용하기에 포트와 파일 용량을 늘려줍니다.
# /etc/sysctl.conf
# 포트 범위 증가
net.ipv4.ip_local_port_range = 1024 65535
# TIME_WAIT 인 포트를 사용할수 있도록 수정
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
- Too Many Open Files 관련 아래 내용 추가 (한 프로세스가 동시에 열수 있는 최대 파일 갯수 증가)
# /etc/security/limits.conf
# 마지막 라인에 추가
* hard nofile 65535
* soft nofile 65535
2. 스레드 관련 세팅
- Unable to create new native thread 관련 아래 내용 추가 (한 프로세스가 만들수 있는 최대 스레드 갯수 증가)
# /etc/security/limits.d/20-nproc.conf
# 마지막 라인에 추가
* hard nproc 65535
* soft nproc 65535
테스트 시나리오
- 요청 처리 시나리오
- DB, API 호출 등으로 인해 총 3초의 대기가 생겼다고 가정합니다.
- 3초 동안
Thread.sleep 이후 응답
- WebFlux 의 경우,
Mono.delay 로 3초 대기후 응답
- 부하 시나리오
- 처음 30초: 0 RPS 에서 Max RPS 까지 요청량을 높입니다.
- 후반 30초: Max RPS 에서 유지 합니다.
예상 결과
아래의 순서로 처리량이 높을것으로 예상했습니다..
- Spring WebFlux (128MB)
- Spring WebMVC with Virtual Thread (128MB)
- Spring WebMVC with System Thread (128MB)
테스트 결과
| Max RPS | Platform Thread | Virtual Thread | WebFlux |
|---|
| 100 RPS | 3002 ms | 3003 ms | - |
| 200 RPS | 3003 ms | 3004 ms | - |
| 300 RPS | 3006 ms | 3017.833 ms (6회 실행 평균) | - |
| 310 RPS | 3009 ms | java.lang.OutOfMemoryError 발생 | - |
| 320 RPS | 3014 ms | java.lang.OutOfMemoryError 발생 | - |
| 330 RPS | 3016.167 ms (6회 실행 평균) | java.lang.OutOfMemoryError 발생 | - |
| 340 RPS | java.lang.OutOfMemoryError 발생 | java.lang.OutOfMemoryError 발생 | - |
| 350 RPS | java.lang.OutOfMemoryError 발생 | java.lang.OutOfMemoryError 발생 | - |
| ... | | | |
| 400 RPS | java.lang.OutOfMemoryError 발생 | java.lang.OutOfMemoryError 발생 | - |
| ... | | | |
| 1000 RPS | - | - | 3014 ms |
| 2000 RPS | - | - | 3183 ms |
| 3000 RPS | - | - | 3216 ms |
- 예상과 다르게, Platform Thread 가 더 좋은 성능을 보여주었습니다 (약 10%)
- Web Flux 는 약 10배 더 나은 성능을 보여주었습니다.
- 아래 사항을 변경해서 테스트 해보았으나, 결과는 항상 Platform Thread 가 더 좋았습니다..
- CPU 갯수 (1개, 2개)
- 할당 힙메모리 (160MB)
- Sleep 타임 변경 (0ms, 100ms, 1s)
- 테스트툴 변경 (Apache Benchmark)
- 또한 Virtual Thread 풀의 경우, 스트레스에 약한 모습을 보였습니다.
- 허용치를 넘겨버리는 경우, 너무 쉽게 OOME 가 발생했습니다.
Why?
- 해당 원인은 메모리 덤프를 통해 확인할수 있었습니다.
- Platform Thread 사용시, 스레드풀은 전체 용량의 약 5% 만 차지하고 있었습니다.
- 나머지 커넥션 관련 클래스 가 전체 용량의 50% 이상을 차지하고 있었습니다.


메모리 덤프 - Virtual Thread


- 따라서 어떻게 테스트 환경을 바꾸든지간에 최대 5~10% 의 성능 향상만 가능할것으로 보입니다.
- 스레드를 늘리더라도 요청량을 그만큼 늘리는 경우, 커넥션 관련 클래스의 용량도 같이 늘어납니다.
- 즉, 스레드 용량과 커넥션 관련 클래스의 용량 비율에 큰차이를 발생 시키기 어렵고
이로인해 Virtual Thread 적용에 따른 메모리 절약 효과로 인한 큰 처리량을 기대하기 어려울것 같습니다.
결론
- 대부분의 블로그에서 Virtual Thread 의 성능이 더 낫다고 기술하고 있어 아래의 결론을 내리기가 쉽지 않았는데요..
하지만 많은 테스트 결과 아래처럼 결론 내릴수 있을것으로 보입니다.
- Tomcat 10.1.15 이하 버전을 사용한다면 Virtual Thread 적용에 따른 처리량이 크게 증가하지 않을것으로 보입니다.
(동시성 향상에 따른 처리속도는 증가할수 있습니다.)
- 단, Platform Thread 환경에서 적절한 스레드풀 용량을 적용해주지 않는다면 Virtual Thread 가 유리합니다.
(200개로 설정후 앞선 테스트 진행시, 평균 응답시간이 15초 였습니다.)
- 결론적으로 높은 동시성이 필요한 환경에서는 당분간 Spring Web Flux 를 사용하는것이 나을것 같습니다.
Reference
Spring Boot Virtual Thread
Linux Tuninig