Virtual Thread?
JDK 21(LTS)에 추가된 경량 스레드 OS 스레드를 그대로 사용하지 않고 JVM내부 스케쥴링을 통해서 수십만~수백만개의 스레드를 동시에 사용할 수 있게 한다.
전통적인 Java의 Thread
- Java의 Thread는 OS Thread를 Wrapping한 것 (Platform Thread)
- Java 애플리케이션에서 Thread를 사용하면 실제로는 OS Thread를 사용한 것
- OS Thread는 생성 갯수가 제한적이고 생성, 유지하는 비용이 비싸다
-> 이 때문에 애플리케이션에서는 플랫폼 스레드를 효율적으로 사용하기 위해 Thread Pool을 사용

Throughput
- 전통적인 Thread는 Throughput에 제약이 생김
- 기본적인 Web Reaquest 처리 방식은 Thread Per Request(하나의 요청/하나의 스레드)
- 처리량을 높이려면 스레드 증가 필요, but 스레드를 무한정 늘릴 수 없다.(OS 스레드 제약)
Blocking I/O
- Thread에서 I/O작업을 처리할 때 Blocking이 일어난다
- 작업을 처리하는 시간보다 대기하는 시간이 길다
Reactive Programming
- Blocking I/O를 해결하기 위해 도입
- Webflux 스레드를 대기하지 않고 다른 작업이 처리 가능
- 코드를 작성하고 이해하는 비용이 높다
- Reactive하게 동작하는 라이브러리 지원을 필요로 한다
- JPA를 사용할 수 없고, R2DBC와 같은 레퍼런스가 상대적으로 적은 라이브러리를 사용해야 함
Java Design
- 자바의 디자인은 '스레드 중심'으로 구정됨
- 하지만, Reactive할 때는 작업이 여러 스레드를 거쳐 처리되어, 컨텍스트 확인이 어렵고 디버깅이 어려워 개발 생산성이 낮아짐
Virtual Thread를 통해 해결하고자 하는 문제
1. 애플리케이션의 높은 처리량(throughput) 확보
- Blocking 발생시 내부 스케쥴링을 통해 다른 작업을 처리
2. 자바 플랫폼의 디자인과 조화를 이루는 코드 생성
- 기존 스레드 구조 그대로 사용

- 스레드를 그대로 상속하기에 기존의 스레드에서 사용하던 메소드를 그대로 사용 가능
Reactive Programming과의 비교

Virtual Thread의 구조
Platform Thread

- JVM에 있는 Thread Pool을 가져다 사용함 (실질적으로 OS Thread를 가져다 사용함)
Virtual Thread

- Virtual Thread들이 따로 존재하고, Fork/Join Pool이 존재하고 Carrier Thread가 존재한다. 이는 Virtual Thread가 사용을 결정한다.
- Virtual Thread가 작업을 할당받아 Carrirer Thread 연결 되어있을 때, Blocking이 발생하면 Unmount가 되고, 다른 Virtual Thread와 Mouont가 된다
- 즉, Blocking이 일어났을 때, 계속 기다리고 있는 것이 아니라 Virtual Thread와 Carrirer Thread가 Mount, Unmount를 하며 다른 Task를 처리한다.
- 따라서 Virtual Thread의 갯수가 수십만~수백만개로 늘어날 수 있다
사용하는 자원의 차이

- 하지만 수가 너무 많아지면 안에 있는 데이터 관리를 잘 해야 하기에 자원 사용량을 작게 해야 한다
사용법 - Spring Boot(MVC) 적용법(3.2 이상)

- 내부에서 발생하는 Tomcat, Was에 대한 처리를 Virtual Thread가 감당하게끔 처리함
사용법 - Spring Boot(MVC) 적용법(3.x)

- 직접 @Bean을 등록하여 사용
- 사이즈를 지정하여 사용하는 것이 아니라 Task별로 개별적으로 Virtual Thread를 할당하여 사용함
유의사항
1. Tread Pool관점으로 보면 차이가 있다
- OS의 자원이 아니라 Task별로 할당하는 단위로 생각해야 함
- 기존의 Thread Pool을 Virtual Thread 로 변환하는 것이 아니라 개별 Task를 Virtual Thread에 할당한다고 생각해야 함.
2. Thread Local 사용시 주의
- Platform Thread Pool을 사용할 때 공유를 위해 ThreadLocal을 사용하던 관습대로 사용하게 되면 Virtual Tread는 수십, 수백만개 까지 늘어날 수 있기에 이를 남발하면 메모리 사용이 늘어나게 됨
3. synchronized 사용시 주의
- synchronized 사용시 Virtual Thread 에 연결된 Carrier Thread 가 Blocking 될 수 있으니 주의 해야 한다(이런 경우 pinning이라고 함)
- 이를 회피하기 위해서 ReentrantLock을 통해 우회할 수 있음

성능 테스트
단순 sleep


- Platform Thread는 TPS가 200에 약간 못미치는 데 Tomcat의 기본 쓰레드의 Max값이 200이므로 이를 넘지 못함
- 이는 단순히 Sleep만 했기에 이런 부분도 있지만 I/O에서는 Virtual Thread 의 큰 효과가 있음
쿼리 질의(db 커넥션을 통해 쿼리 응답을 받는데 1초가 걸린다는 가정)


- Platform Thread는 149~150, Mysql에 Max Connection이 151로 지정되어 있음
- Virtual Thread는 에러가 발생
- Platform Thread에서는 Tomcat이 받다가 넘겨 줄때, 충분히 트레픽을 받지 못하면 기다리는 데 반해, Virtual Thread의 경우에는 Tomcat에서 Throughput을 모두 소화하고, JDBC 커넥션으로 로직을 넘겼지만 DB커넥션을 기다리다가 Timeout 이 발생함
성능테스트 해석
1. I/O Blocking이 발생하는 경우 Virtual Thread가 더 좋은 처리량을 보여준다
2. Tomcat servlet이 virtual thread로 (no pool, virtual thread per task) throughput 을 뒤로 넘길때 DB Connection을 가져오려다 Timeout이 발생함 <- 이런 경우를 Overwhelming이라고 부름
기존 Thread pool을 사용하던 이유
1. Platform Thread가 비쌈
2. Throttle 역할도 수행 <- 이를 고려하여 Virtual Thread를 사용해야 함
생각해 볼 점
적합한 사용처
1. I/O Blocking이 발생하는 경우 Virtual Thread가 적합함
2. CPU Intensive작업에는 적합하지 않음(ex. 이미지 프로세싱, 동영상 인코딩)
3. Spring MVC기반 Web API제공시 편리하게 사용할 수 있음
- 높은 throughput을 위해서 Webflux를 고려중이라면 대안이 될 수 있음
Virtual Thread에 대한 오해
1. Virtual Thread 는 기존 Platform Thread를 대체하는 것이 목적이 아님
2. Virtual Thread는 기다림에 대한 개선, 그리고 플랫폼 디자인과의 조화
3. 도입한다고 무조건 처리량이 높아지지 않음
4. Virtual Thread는 그 자체로 Java의 동시성을 완전히 개선했다고 보기는 어려움
Virtual Thread의 제약
1. Thread Pool에 적합하지 않음. Task별로 Virtual Thread를 할당해야 함
2. Thread Local 사용시 메모리 사용이 늘어날 수 있음
3. synchronized 사용시 주의가 필요함(carrier thread가 blocking될 가능성 있음)
- ReentrantLock을 사용
4. 제한된 리소스의 경우 semaphore를 사용
[참고]
Kakao tech
https://www.youtube.com/watch?v=vQP6Rs-ywlQ&ab_channel=kakaotech
'개발 학습 > 컨퍼런스' 카테고리의 다른 글
| 대규모 트래픽을 처리하는 MySQL 샤딩 (0) | 2024.06.13 |
|---|---|
| 신뢰성 있는 카프카 애플리케이션을 만드는 방법 (0) | 2024.06.03 |