# WebFlux vs. 가상 스레드: 초당 2000 요청에서의 비교
높은 부하 환경에서 Spring WebFlux의 리액티브 모델과 Project Loom의 새로운 가상 스레드 패러다임 중 선택이 Java 애플리케이션의 성능과 확장성을 좌우합니다. 이 연구는 샘플 마이크로서비스 프로젝트에서 초당 2000 요청으로 양 접근 방식의 로드 테스트 결과를 제시합니다.
Spring WebFlux 아키텍처: 이벤트 루프와 논블로킹 I/O
Spring WebFlux는 이벤트 루프를 활용한 논블로킹 I/O 모델을 사용합니다. 전통적인 "하나의 요청—하나의 스레드" 모델과 달리 WebFlux는 소수의 스레드로 수천 개의 연결을 처리합니다. 핵심 원리는 스레드가 I/O 대기를 할 때 블록되지 않고, 이벤트(예: "소켓에 데이터 도착")에 관심을 등록한 후 다른 작업으로 전환하는 것입니다.
중심 구성 요소는 기본적으로 사용되는 Netty입니다. Netty는 EventLoopGroup을 통해 이벤트 루프를 관리합니다. 이벤트 루프 스레드 수는 다음 공식으로 결정됩니다:
int DEFAULT_IO_WORKER_COUNT = Integer.parseInt(System.getProperty(
ReactorNetty.IO_WORKER_COUNT,
"" + Math.max(Runtime.getRuntime().availableProcessors(), 4)));
즉, 사용 가능한 프로세서 수와 같지만 최소 4개입니다. 컨테이너화된 환경에서는 할당된 리소스에 맞게 이 값을 조정할 수 있습니다.
각 네트워크 연결(Channel)은 특정 이벤트 루프에 바인딩됩니다(channel affinity). 이를 통해 동기화 오버헤드를 없애고 CPU 캐시 효율성을 높입니다. 그러나 이벤트 루프 내에서 블로킹 작업이 발생하면 해당 루프에 고정된 모든 연결의 처리가 중단됩니다.
가상 스레드: Project Loom 작동 원리
가상 스레드는 "하나의 요청—하나의 스레드" 모델을 유지하면서 오버헤드를 대폭 줄입니다. 각 요청에 독립적인 가상 스레드가 할당되며, 이는 플랫폼 스레드(carrier threads)에 마운트됩니다. Carrier 스레드는 ForkJoinPool 기반 스케줄러에 의해 관리됩니다.
기본 carrier 스레드 수는 사용 가능한 프로세서 수와 같습니다. 예를 들어 2 CPU 컨테이너에서는 수천 개의 가상 스레드를 실행할 수 있는 2개의 carrier 스레드가 생성됩니다.
기본 스케줄러 생성 메서드:
private static ForkJoinPool createDefaultScheduler() {
ForkJoinWorkerThreadFactory factory = pool -> new CarrierThread(pool);
int parallelism, maxPoolSize, minRunnable;
String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism");
String maxPoolSiz
가상 스레드의 장점:
- 명령형 프로그래밍 스타일 유지
- 읽기 쉬운 스택 트레이스
- ThreadLocal 지원
- 기존 블로킹 라이브러리와의 통합 간소화
초당 2000 요청 로드 테스트 결과
샘플 마이크로서비스 프로젝트에서의 테스트 결과는 다음과 같습니다:
- WebFlux는 이벤트 루프에 블로킹 작업이 없을 때 안정적인 성능을 보였습니다. 그러나 블로킹 라이브러리(JDBC 등) 통합 시 별도의 스레드 풀(Schedulers.boundedElastic())로 작업을 오프로드해야 했으며, 이는 복잡성을 더했습니다.
- 가상 스레드는 개발과 디버깅을 간소화하면서 높은 성능을 제공했습니다. 초당 2000 요청에서 Spring MVC와 Tomcat上的 가상 스레드는 WebFlux 처리량과 맞먹었으며, 대부분의 Java 개발자에게 익숙한 코드로 작성되었습니다.
WebFlux의 핵심은 리액티브 체인을 제대로 사용하고 이벤트 루프에서 블로킹 호출을 피하는 것이었습니다. 가상 스레드의 경우 CPU 중심 작업은 해결하지 않으므로 높은 CPU 부하는 carrier 스레드 경합을 초래할 수 있습니다.
핵심 차이점과 권장 사항
WebFlux와 가상 스레드 선택 시 다음 측면을 고려하세요:
- 워크로드 유형:
- I/O 중심 앱(많은 I/O 대기)에서 둘 다 우수
- CPU 중심 작업에서는 carrier 스레드 경합으로 가상 스레드가 뒤처질 수 있음
- 라이브러리 생태계:
- 블로킹 라이브러리(JDBC)에 의존하는 프로젝트라면 가상 스레드가 마이그레이션을 용이하게 함
- 리액티브 드라이버(R2DBC)를 지원하는 신규 프로젝트라면 WebFlux가 여전히 적합
- 팀 전문성:
- 리액티브 프로그래밍은 깊은 비동기 패턴 지식이 필요
- 가상 스레드는 익숙한 명령형 스타일을 가능하게 함
주요 요약
- 초당 2000 요청 성능: 둘 다 부하를 처리하지만 가상 스레드는 명령형 스타일을 유지해 개발을 간소화
- 주요 제한: WebFlux 이벤트 루프의 블로킹 작업은 해당 루프의 모든 요청을 마비시킴; 가상 스레드는 CPU 중심 문제를 해결하지 않음
- 생태계: WebFlux 도입은 블로킹 라이브러리를 리액티브 것으로 교체해야 하며 비용이 큼
- 디버깅: 리액티브 앱은 복잡한 스택 트레이스로 진단이 어렵지만 가상 스레드는 익숙한 프로파일링 도구 제공
— Editorial Team
아직 댓글이 없습니다.