|
| 1 | +# Virtual Threads Performance Test 🚀 |
| 2 | + |
| 3 | +Java 21의 가상 스레드(Virtual Threads)와 전통적인 플랫폼 스레드(Platform Threads)의 성능을 실제로 비교하는 프로젝트입니다. |
| 4 | + |
| 5 | +## 핵심 포인트 |
| 6 | + |
| 7 | +**코드는 완전히 동일하고, 설정 하나만 바꿔서 테스트합니다!** |
| 8 | + |
| 9 | +```yaml |
| 10 | +# application-virtual.yml |
| 11 | +spring: |
| 12 | + threads: |
| 13 | + virtual: |
| 14 | + enabled: true # 이것만으로 끝! |
| 15 | +``` |
| 16 | +
|
| 17 | +## 빠른 시작 |
| 18 | +
|
| 19 | +### 1. 일반 플랫폼 스레드 테스트 |
| 20 | +
|
| 21 | +```bash |
| 22 | +# 애플리케이션 실행 |
| 23 | +./gradlew bootRun |
| 24 | + |
| 25 | +# 엔드포인트 동작 확인 (다른 터미널) |
| 26 | +curl http://localhost:8080/api/sleep-test |
| 27 | +# 출력: OK (100ms 소요) |
| 28 | + |
| 29 | +# JMeter로 성능 테스트 |
| 30 | +jmeter |
| 31 | +# File → Open → jmeter/Spring-MVC-Performance-Test.jmx |
| 32 | +# Run → Start |
| 33 | + |
| 34 | +# 결과 확인 후 종료 |
| 35 | +Ctrl+C |
| 36 | +``` |
| 37 | + |
| 38 | +### 2. 가상 스레드 테스트 ⚡ |
| 39 | + |
| 40 | +```bash |
| 41 | +# 가상 스레드로 실행 |
| 42 | +./gradlew bootRun --args='--spring.profiles.active=virtual' |
| 43 | + |
| 44 | +# 동일한 JMeter 테스트 실행 |
| 45 | + |
| 46 | +# 결과 비교! |
| 47 | +``` |
| 48 | + |
| 49 | +### 3. 스레드 타입 확인 |
| 50 | + |
| 51 | +```bash |
| 52 | +# 플랫폼 스레드 |
| 53 | +curl http://localhost:8080/api/thread-info |
| 54 | +# 출력: Thread Type: 🐌 Platform Thread |
| 55 | + |
| 56 | +# 가상 스레드 |
| 57 | +curl http://localhost:8080/api/thread-info |
| 58 | +# 출력: Thread Type: 🚀 Virtual Thread |
| 59 | +``` |
| 60 | + |
| 61 | +## 주요 기능 |
| 62 | + |
| 63 | +### 순수 I/O 지연 시뮬레이션 ⏱️ |
| 64 | + |
| 65 | +`Thread.sleep(100)`으로 실제 운영 환경의 I/O 대기 시간을 시뮬레이션합니다: |
| 66 | + |
| 67 | +- **시뮬레이션 시간**: 고정 100ms |
| 68 | +- **테스트 대상**: `/api/sleep-test` 엔드포인트 |
| 69 | +- **DB 의존성**: 없음 (순수 스레드 성능 테스트) |
| 70 | + |
| 71 | +이 지연 시간 동안: |
| 72 | +- **플랫폼 스레드**: 100ms 동안 OS 스레드가 블로킹되어 낭비됨 😴 |
| 73 | +- **가상 스레드**: carrier thread에서 unmount되어 다른 작업 처리 🚀 |
| 74 | + |
| 75 | +## 예상 결과 |
| 76 | + |
| 77 | +| 지표 | 플랫폼 스레드 | 가상 스레드 | 차이 | |
| 78 | +|------|-------------|------------|------| |
| 79 | +| **Throughput** | ~500 req/sec | ~5,000 req/sec | **10배 향상** 🚀 | |
| 80 | +| **Response Time (Avg)** | ~500ms | ~100ms | **5배 빠름** ⚡ | |
| 81 | +| **P95 Response Time** | ~1,000ms | ~110ms | **9배 빠름** | |
| 82 | +| **Error Rate** | 0~5% | 0% | **안정성 향상** ✅ | |
| 83 | +| **Platform Thread Count** | 200 (고갈) | 8~16 (CPU 코어 수) | **리소스 절약** 💾 | |
| 84 | +| **Virtual Thread Count** | - | 5,000+ | **무제한 동시성** ♾️ | |
| 85 | + |
| 86 | +## 기술 스택 |
| 87 | + |
| 88 | +- Java 21 |
| 89 | +- Spring Boot 3.4.4 |
| 90 | +- Spring MVC (동일한 코드) |
| 91 | +- MySQL 8.0 (선택 사항, 테스트에 미사용) |
| 92 | +- Apache JMeter |
| 93 | +- Lombok |
| 94 | + |
| 95 | +## 프로젝트 구조 |
| 96 | + |
| 97 | +``` |
| 98 | +├── src/main/java/ex/demo/ |
| 99 | +│ ├── SleepTestController.java # Thread.sleep(100) 테스트 API ⏱️ |
| 100 | +│ ├── ThreadInfoController.java # 스레드 타입 확인 |
| 101 | +│ ├── Comment*.java # 댓글 기능 (참고용) |
| 102 | +│ └── SimulatedDelayService.java # 지연 서비스 (참고용) |
| 103 | +├── src/main/resources/ |
| 104 | +│ ├── application.yml # 플랫폼 스레드 (기본) |
| 105 | +│ └── application-virtual.yml # 가상 스레드 |
| 106 | +├── jmeter/ |
| 107 | +│ └── Spring-MVC-Performance-Test.jmx # JMeter 테스트 플랜 |
| 108 | +└── docs/ |
| 109 | + ├── performance-test.md # 상세 가이드 |
| 110 | + └── test-results-template.md # 결과 기록 템플릿 |
| 111 | +``` |
| 112 | + |
| 113 | +## 테스트 시나리오 |
| 114 | + |
| 115 | +- **동시 사용자**: 500명 |
| 116 | +- **램프업 시간**: 30초 |
| 117 | +- **반복 횟수**: 10회 |
| 118 | +- **Think Time**: 1초 |
| 119 | +- **총 요청 수**: 5,000회 |
| 120 | +- **각 요청 처리 시간**: 100ms (Thread.sleep) |
| 121 | + |
| 122 | +## 가상 스레드란? |
| 123 | + |
| 124 | +**Java 21의 혁신적인 기능**으로, 수백만 개의 경량 스레드를 만들 수 있습니다. |
| 125 | + |
| 126 | +### 전통적인 플랫폼 스레드 |
| 127 | + |
| 128 | +``` |
| 129 | +요청 1 → OS 스레드 1 (1MB 메모리) → DB 대기 중 😴 |
| 130 | +요청 2 → OS 스레드 2 (1MB 메모리) → DB 대기 중 😴 |
| 131 | +... |
| 132 | +요청 200 → OS 스레드 200 (1MB 메모리) → DB 대기 중 😴 |
| 133 | +요청 201 → ❌ 스레드 풀 고갈! 대기... |
| 134 | +``` |
| 135 | + |
| 136 | +### 가상 스레드 |
| 137 | + |
| 138 | +``` |
| 139 | +요청 1 → 가상 스레드 1 → DB 대기 시 OS 스레드 반납 |
| 140 | + → OS 스레드는 다른 가상 스레드 처리 🚀 |
| 141 | +요청 2 → 가상 스레드 2 → DB 대기 시 OS 스레드 반납 |
| 142 | +... |
| 143 | +요청 100만 → 가상 스레드 100만 → 모두 처리 가능! ✅ |
| 144 | +``` |
| 145 | + |
| 146 | +## 가상 스레드의 장점 |
| 147 | + |
| 148 | +1. **높은 처리량**: I/O 대기 시간을 낭비하지 않음 |
| 149 | +2. **낮은 리소스 사용**: OS 스레드 10~20개로 수만 개의 요청 처리 |
| 150 | +3. **코드 변경 불필요**: 기존 블로킹 코드 그대로 사용 |
| 151 | +4. **학습 비용 낮음**: Reactive 프로그래밍 불필요 |
| 152 | +5. **안정성 향상**: 스레드 풀 고갈 없음 |
| 153 | + |
| 154 | +## 언제 사용해야 하는가? |
| 155 | + |
| 156 | +### ✅ 가상 스레드 사용 추천 |
| 157 | + |
| 158 | +- **I/O Bound 작업**: DB 쿼리, 외부 API 호출, 파일 I/O |
| 159 | +- **높은 동시성**: 마이크로서비스, 실시간 채팅, 알림 시스템 |
| 160 | +- **블로킹 코드 유지**: 기존 코드 마이그레이션 부담 최소화 |
| 161 | + |
| 162 | +### ❌ 가상 스레드 효과 없음 |
| 163 | + |
| 164 | +- **CPU Bound 작업**: 복잡한 계산, 암호화, 이미지 처리 |
| 165 | +- **낮은 동시성**: 동시 요청이 적을 때 (< 100) |
| 166 | +- **메모리 Bound**: 대용량 데이터 처리 |
| 167 | + |
| 168 | +## 주의사항 |
| 169 | + |
| 170 | +### Synchronized 블록 피하기 |
| 171 | + |
| 172 | +```java |
| 173 | +// ❌ 가상 스레드에서 피해야 할 코드 |
| 174 | +synchronized (lock) { |
| 175 | + // I/O 작업 |
| 176 | + db.query(); |
| 177 | +} |
| 178 | + |
| 179 | +// ✅ 대신 이렇게 |
| 180 | +Lock lock = new ReentrantLock(); |
| 181 | +lock.lock(); |
| 182 | +try { |
| 183 | + db.query(); |
| 184 | +} finally { |
| 185 | + lock.unlock(); |
| 186 | +} |
| 187 | +``` |
| 188 | + |
| 189 | +## 참고 자료 |
| 190 | + |
| 191 | +- [JEP 444: Virtual Threads](https://openjdk.org/jeps/444) |
| 192 | +- [Spring Boot 3.2+ Virtual Threads](https://spring.io/blog/2023/09/09/all-together-now-spring-boot-3-2-graalvm-native-images-java-21-and-virtual) |
| 193 | +- [상세 테스트 가이드](docs/performance-test.md) |
| 194 | + |
| 195 | +## 라이선스 |
| 196 | + |
| 197 | +MIT License |
0 commit comments