kakao tech meet 영상을 보고 요약했다.
https://www.youtube.com/watch?v=vQP6Rs-ywlQ
실무에서 써야하는데 단순히 설정값만 넣어주면 되긴하는데 어떤 배경이 있는지 알고는 써야할 것 같아서 정리해본다.
Virtual Thread
JDK21에 추가된 경량 스레드
OS 스레드를 그대로 사용하지 않고 JVM 내부 스케줄링을 통해서 수 십 ~ 수 백만개의 스레드를 동시에 사용할 수 있게 해줌
기존 Thread 문제
Thread per request
기본 Web Request 처리 방식은 Thread per request인데, OS 스레드 개수 제한으로 인해 개수를 늘릴 수가 없었다.
Blocking I/O
Thread에서 I/O 작업 시 Blocking이 일어나면서 처리시간보다 대기 시간이 긴 문제가 있었음
Reactive Programming(Webflux)를 사용하면 비동기식으로 처리하여 스레드 제약을 극복할 수 있었으나, DB 커넥션, JPA를 사용할 수 없으며 R2DBC라는 레퍼런스가 적은 기술을 사용해야했다. 또한 프로그래밍의 난이도가 매우 높아지는 문제가 있었다. 또한 자바는 디버깅 자체가 스레드 기반이기 때문에 비동기식 프로그래밍을 하면 디버깅이 어려워지는 문제도 있었다.
Virtual Thread 작동 방식
기존 OS 스레드와 1대 1로 매핑되던 것(Platform thread)을, Carrier Thread로 매핑하고 virtual Thread들을 스케줄링 하여 사용하는 방식이다.
Blocking 발생 시, Virtual Thread는 Carrier Thread에서 unmount 된다.
그리고 다른 virtual Thread와 연결하여 작업을 계속 처리하게 된다.
사용 방법
자바의 경우 startVirtualThread(), ofVirtual(), newVirtualThreadPerTaskExecutor() 등의 메서드를 사용하면 된다.
스프링의 경우 간단하게 설정만 추가해주면 알아서 웹 요청을 처리할 때 버추얼 스레드 기반의 동작 방식으로 작동하게 된다.
3.2 이하의 3.x 버전에서는 Bean을 따로 등록 처리해줘야한다.
유의 사항
할당 단위
리소스 할당이 아니라 그냥 Task 마다 할당되는 것으로 이해해야한다. 기존에는 Platform Thread를 OS Thread로 일대일 매핑한다고 생각하고 그 스레드에 자원이 할당된다고 생각했겠지만, Virtual Thread는 Task별로 할당되며 리소스를 사용하는 단위라고 생각하면 안된다. 개별 task를 Virtual Thread에 할당해야 성능상의 이점을 누릴 수 있다.
Thread Local 사용
기존에는 ThreadPool을 사용할 때 스레드간 공유를 위해서 ThreadLocal을 사용했다. Virtual Thread는 Heap을 사용하기 때문에 남발하면 메모리 사용이 기하급수적으로 늘어날 수 있으므로 유의해야한다.
synchronized 사용
synchronized 사용 시 Virtual Thread에 연결된 Carrier Thread가 Blocking 될 수 있다(pinning). 또는 JNI와 같은 네이티브 코드 사용 시 의해 pinning이 발생하 수도 있다. ReentrantLock으로 pinning을 우회할 수도 있다.
DB Connection 성능 테스트 시, Virtual Thread의 과도한 요청으로 DB에서 timeout execption이 나는 경우가 발생했다. 이런 경우를 Overwhelming이라고 부른다고 한다. 이것을 통해 기존 Thread pool을 사용하던 것이 Throttle 역할도 수행하던 것이라는 점을 역으로 확인할 수 있었다고 한다. 따라서 Overwhelming이 발생할 것으로 예상된다면 Semaphore 등의 lock을 통해 Throttle을 따로 걸어주어야할 수도 있겠다.
I/O Intensive 작업은 디스크 읽기/쓰기, 네트워크 통신 등 외부 자원에 의존하는 작업을 의미한다. 대량의 파일 읽기/쓰기 작업, 데이터베이스 쿼리 처리, HTTP API 호출 등의 네트워크 요청/응답, 로그 저장 및 스트리밍 데이터 처리 등의 작업이 해당한다.
I/O Intensive 작업의 waiting time을 줄이는 정도의 목적이라고 생각해야한다. 만능이 아니다.
추가
DB Connection에 overwhelming이 발생하는 것이 예상되는 경우, 연결 풀 크기 설정, Connection Timeout 설정 등을 하여 Virtual Thread에 의한 사이드 이펙을 방지할 수 있다고 한다.
- spring.datasource.hikari.maximum-pool-size=50(Throttle 역할)
- spring.datasource.hikari.connection-timeout=30000(Virtual Thread의 무한 대기 방지)
스레드 풀 크기 제한을 할 수도 있다고 한다.
Virtual Thread Executor 위에 제어 레이어를 추가하여 동시에 실행되는 작업 수를 제한
- ExecutorService limitedExecutor = Executors.newFixedThreadPool(100);
'Programming-[Backend] > Java' 카테고리의 다른 글
[TIL] JVM HeapSize, HeapDumpPath 설정 (0) | 2025.01.06 |
---|---|
비동기 작업: @Async 어노테이션, 스레드 관리 (0) | 2024.11.14 |
[TIL] Docker image JVM Heap 크기 및 옵션 설정, buildpack-gradle bootBuildImage, Packeto buildpack (0) | 2024.07.26 |
자바 기초 강의 정리 - 8. 클래스 패스, JAR (0) | 2024.07.15 |
자바 기초 강의 정리 - 7. 리플렉션, 어노테이션, 클래스 로더 (0) | 2024.07.14 |