100,000개의 HTTP 요청을 동시에 처리해야 한다면 Traditional Thread Pool을 사용한다면 수천 개의 Thread가 필요하고, 메모리 부족으로 애플리케이션이 다운될 수 있습니다. 하지만 Java 21의 Virtual Thread를 사용하면 단 몇 개의 Platform Thread로도 동일한 작업을 처리할 수 있습니다.
과연 이것이 가능할까요? 실제로 얼마나 성능 차이가 날까요?
이 글에서는 직접 실험을 통해 Virtual Thread의 성능을 측정하고, Traditional Thread와 비교 분석한 결과를 공유합니다.
Java의 Traditional Thread는 OS Thread와 1:1 매핑됩니다. 이는 다음과 같은 문제를 야기합니다:
// Traditional Thread Pool 설정
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(50); // 최소 Thread 수
executor.setMaxPoolSize(200); // 최대 Thread 수
executor.setQueueCapacity(500); // 대기 큐 크기
return executor;
}
}
Traditional Thread는 각각 상당한 메모리를 사용하며, 많은 수의 Thread 생성 시 메모리 부족 문제가 발생할 수 있습니다. 또한 Context Switching 비용이 높아 성능 저하를 야기합니다.
Virtual Thread는 이러한 한계를 해결하기 위해 Java 21에서 도입되었습니다:
// Traditional Thread 방식
public class TraditionalThreadExample {
private final ExecutorService executor = Executors.newFixedThreadPool(200);
public void handleRequests() {
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
// I/O 작업 시뮬레이션
try {
Thread.sleep(1000); // 1초 대기
System.out.println("작업 완료: " + Thread.currentThread().getName());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
}
// Virtual Thread 방식
public class VirtualThreadExample {
public void handleRequests() {
for (int i = 0; i < 10000; i++) {
Thread.startVirtualThread(() -> {
// 동일한 I/O 작업
try {
Thread.sleep(1000); // 1초 대기
System.out.println("작업 완료: " + Thread.currentThread().getName());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
}
// Spring Boot에서 Virtual Thread 활성화
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// JVM 옵션: --enable-preview (Java 19-20에서)
// Java 21부터는 정식 기능
System.setProperty("jdk.virtualThreadScheduler.parallelism", "10");
SpringApplication.run(Application.class, args);
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
핵심 차이점:
Virtual Thread는 Platform Thread 위에서 실행되는 경량 Thread로, 수백만 개를 생성해도 메모리 사용량이 크게 증가하지 않습니다.
각 요청은 다음과 같은 I/O 작업을 수행합니다:
예상 응답 시간 범위:
실제 I/O 작업들은 병렬로 처리되므로, 가장 오래 걸리는 작업의 시간에 따라 전체 응답 시간이 결정됩니다.
실제 K6 테스트 출력:
█ THRESHOLDS
errors
✓ 'rate<0.10' rate=0.00%
http_req_duration
✓ 'p(95)<5000' p(95)=549.56ms
{expected_response:true}
✓ 'p(90)<3000' p(90)=503.73ms
response_time
✓ 'p(95)<5000' p(95)=549.59ms
█ TOTAL RESULTS
checks_total.......................: 56252 133.808896/s
checks_succeeded...................: 100.00% 56252 out of 56252
checks_failed......................: 0.00% 0 out of 56252
✓ Virtual Thread - Status is 200
✓ Virtual Thread - Response time < 3s
✓ Virtual Thread - Has product data
✓ Virtual Thread - Content-Type is JSON
CUSTOM
errors..................................................................: 0.00% 0 out of 14063
requests................................................................: 14063 33.452224/s
response_time...........................................................: avg=405.66ms min=204.79ms med=394.2ms max=1.45s p(90)=503.73ms p(95)=549.59ms
HTTP
http_req_duration.......................................................: avg=405.64ms min=118.33ms med=394.19ms max=1.45s p(90)=503.73ms p(95)=549.56ms
{ expected_response:true }............................................: avg=405.64ms min=118.33ms med=394.19ms max=1.45s p(90)=503.73ms p(95)=549.56ms
http_req_failed.........................................................: 0.00% 0 out of 14064
http_reqs...............................................................: 14064 33.454603/s
EXECUTION
iteration_duration......................................................: avg=606.59ms min=321.43ms med=598.56ms max=1.67s p(90)=741.54ms p(95)=787.29ms
iterations..............................................................: 14063 33.452224/s
vus.....................................................................: 1 min=1 max=35
vus_max.................................................................: 35 min=35 max=35
NETWORK
data_received...........................................................: 10 MB 24 kB/s
data_sent...............................................................: 2.0 MB 4.9 kB/s
running (7m00.4s), 00/35 VUs, 14063 complete and 0 interrupted iterations
default ✓ [======================================] 00/35 VUs 7m0s
기본 성능 지표:
체크 결과:
실제 K6 테스트 출력:
█ THRESHOLDS
errors
✓ 'rate<0.10' rate=0.00%
http_req_duration
✓ 'p(95)<5000' p(95)=1.53s
{expected_response:true}
✓ 'p(90)<3000' p(90)=1.45s
response_time
✓ 'p(95)<5000' p(95)=1.53s
█ TOTAL RESULTS
checks_total.......................: 29248 69.533637/s
checks_succeeded...................: 100.00% 29248 out of 29248
checks_failed......................: 0.00% 0 out of 29248
✓ Traditional Thread - Status is 200
✓ Traditional Thread - Response time < 3s
✓ Traditional Thread - Has product data
✓ Traditional Thread - Content-Type is JSON
CUSTOM
errors..................................................................: 0.00% 0 out of 7312
requests................................................................: 7312 17.383409/s
response_time...........................................................: avg=966.61ms min=207.29ms med=990.32ms max=2.27s p(90)=1.45s p(95)=1.53s
HTTP
http_req_duration.......................................................: avg=966.5ms min=155.13ms med=990.3ms max=2.27s p(90)=1.45s p(95)=1.53s
{ expected_response:true }............................................: avg=966.5ms min=155.13ms med=990.3ms max=2.27s p(90)=1.45s p(95)=1.53s
http_req_failed.........................................................: 0.00% 0 out of 7313
http_reqs...............................................................: 7313 17.385787/s
EXECUTION
iteration_duration......................................................: avg=1.16s min=309.47ms med=1.18s max=2.47s p(90)=1.65s p(95)=1.74s
iterations..............................................................: 7312 17.383409/s
vus.....................................................................: 1 min=1 max=35
vus_max.................................................................: 35 min=35 max=35
NETWORK
data_received...........................................................: 5.2 MB 12 kB/s
data_sent...............................................................: 1.1 MB 2.7 kB/s
running (7m00.6s), 00/35 VUs, 7312 complete and 0 interrupted iterations
default ✓ [======================================] 00/35 VUs 7m0s
기본 성능 지표:
체크 결과:
위 K6 테스트 결과를 분석하면 몇 가지 중요한 사실을 확인할 수 있습니다:
Virtual Thread: 14,063 iterations (33.45 req/s)
Traditional Thread: 7,312 iterations (17.38 req/s)
비율: 1.92배 (약 92% 증가)
이는 동일한 하드웨어 환경에서 Virtual Thread가 거의 2배에 가까운 처리 성능을 보여준다는 의미입니다.
Virtual Thread 응답 시간 분포:
Traditional Thread 응답 시간 분포:
Traditional Thread의 결과에서 주목할 점:
이는 Thread Pool이 포화되어 요청 대기 시간이 발생했음을 의미합니다.
Context Switching이란?
현재 테스트 결과를 바탕으로 한 추정:
서버 비용 절감 계산:
필요 서버 수 = 목표 처리량 / 서버당 처리량
Traditional Thread: 100,000 req/s ÷ 17.38 req/s = 5,756대
Virtual Thread: 100,000 req/s ÷ 33.45 req/s = 2,989대
절감율: (5,756 - 2,989) ÷ 5,756 = 48.1%
주의사항: 이는 선형적 확장을 가정한 것으로, 실제 환경에서는 다른 병목 요소들이 영향을 줄 수 있습니다.
응답 시간 개선 효과:
사용자 만족도 예상 효과:
즉시 적용 가능한 영역:
기술적 제약사항:
이러한 객관적 분석을 통해 Virtual Thread의 장점과 한계를 명확히 이해하고, 실제 도입 시 합리적인 의사결정을 내릴 수 있습니다.
이번 실험을 통해 Virtual Thread가 I/O 집약적인 애플리케이션에서 Traditional Thread 대비 현저한 성능 향상을 제공함을 확인했습니다.
핵심 성능 지표
실제 측정된 장점
하지만 Virtual Thread가 모든 상황에서 Traditional Thread를 대체해야 하는 것은 아닙니다. 이번 실험 결과가 보여주는 것은 특정 조건에서의 성능 우위이며, 실제 도입 시에는 다음과 같은 균형잡힌 접근이 필요합니다.
Virtual Thread가 효과적인 영역
기존 Traditional Thread가 여전히 적합한 영역
Virtual Thread는 기존 시스템을 전면 교체하라는 명령이 아니라, 점진적 개선을 위한 선택지입니다.
1. 기존 시스템과의 공존
2. 비즈니스 가치 중심의 도입
3. 장기적 관점의 기술 전략
Virtual Thread는 혁신적인 기술이지만 신중한 도입이 필요한 도구입니다.
결국 Virtual Thread의 진정한 가치는 더 나은 사용자 경험과 효율적인 리소스 활용을 통해 지속가능한 서비스를 만드는 데 있습니다. 기술 자체의 우수성보다는 우리가 해결하고자 하는 문제에 얼마나 적합한지가 더 중요한 판단 기준이 되어야 합니다.
Java 21을 사용하고 있다면, Virtual Thread를 하나의 강력한 옵션으로 고려해보시기 바랍니다. 특히 I/O 집약적인 웹 애플리케이션에서는 기존 시스템의 성능을 한 단계 끌어올릴 수 있는 현실적인 솔루션이 될 것입니다.