우당탕탕
[Spring] Spring Boot 가상 스레드(Virtual Threads) 적용하기 2편 -@Async + JMeter 고급 벤치마킹 본문
[Spring] Spring Boot 가상 스레드(Virtual Threads) 적용하기 2편 -@Async + JMeter 고급 벤치마킹
모찌모찝 2025. 12. 31. 18:40Spring Boot 가상 스레드(Virtual Threads) 적용하기 2편 -@Async + JMeter 고급 벤치마킹
안녕하세요! 1편에서 기본 설정을 진행한 상태에서 이번 2편에서는 @Async와 VirtualThreadTaskExecutor를 결합해 진짜 고성능 서비스를 만들어 볼 예정입니다. 실제 배포 직전 체크리스트까지 완성합니다.
1편의 내용이 궁금하시다면?! -> https://mozzi-devlog.tistory.com/116
목표
@Async + 가상 스레드 완벽 통합
- 커스텀 VirtualThreadTaskExecutor 생성
- 병렬 API 처리 (외부 API 호출 + DB 쿼리)
- JMeter로 10만 동시 요청 부하 테스트
실제 트레이딩 시스템 및 실시간 알림 서비스에 바로 적용 가능!
1. 의존성 추가 (build.gradle)
기존 프로젝트에 추가:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-async'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.github.openfeign:feign-httpclient:13.5' // 외부 API 테스트용
}
수정 이후 Gradle 새로고침 (코끼리 클릭) 후 재시작!
2. VirtualThreadTaskExecutor 커스텀 생성
src/main/java/com/example/virtualthread에 AsyncConfig.java:
package com.example.virtualthread;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "virtualThreadTaskExecutor")
public Executor virtualThreadTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(1000); // 가상 스레드라 제한 거의 없음!
executor.setThreadNamePrefix("virtual-async-");
executor.setTaskDecorator(new VirtualThreadTaskDecorator()); // 가상 스레드 강제
executor.initialize();
return executor;
}
}
-> 기존 플랫폼 스레드 200개 한계를 넘어서 가상 스레드 풀 크기를 1000개로 설정
3. 고성능 병렬 서비스 구현
AsyncService.java 생성:
package com.example.virtualthread;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.CompletableFuture;
@Service
public class AsyncService {
private final RestTemplate restTemplate = new RestTemplate();
@Async("virtualThreadTaskExecutor")
public CompletableFuture<String> callExternalApi(String url) {
try {
Thread.sleep(1000); // 외부 API 지연 시뮬
String result = restTemplate.getForObject(url, String.class);
return CompletableFuture.completedFuture("External: " + result);
} catch (Exception e) {
return CompletableFuture.completedFuture("External Error");
}
}
@Async("virtualThreadTaskExecutor")
public CompletableFuture<String> heavyDbQuery() {
try {
Thread.sleep(1500); // DB 쿼리 시뮬
return CompletableFuture.completedFuture("DB: Processed 10K records");
} catch (Exception e) {
return CompletableFuture.completedFuture("DB Error");
}
}
}
4. 병렬 컨트롤러 완성
HighPerformanceController.java:
package com.example.virtualthread;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@RestController
public class HighPerformanceController {
@Autowired
private AsyncService asyncService;
@GetMapping("/parallel-test")
public String parallelTest() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
// 병렬 실행 (총 2.5초 걸릴 작업을 1.5초로 단축!)
CompletableFuture<String> api1 = asyncService.callExternalApi("https://jsonplaceholder.typicode.com/todos/1");
CompletableFuture<String> api2 = asyncService.callExternalApi("https://jsonplaceholder.typicode.com/posts/1");
CompletableFuture<String> db = asyncService.heavyDbQuery();
CompletableFuture.allOf(api1, api2, db).join();
String result = String.format("All done in %dms | %s | %s | %s",
System.currentTimeMillis() - start, api1.get(), api2.get(), db.get());
return "Virtual Thread Parallel: " + result + " | Thread: " + Thread.currentThread();
}
}
5. Apache Bench 업그레이드 테스트
# 순차 처리 (기존 방식)
ab -n 5000 -c 500 http://localhost:8080/parallel-test/
# 병렬 가상 스레드 (이번 편!)
ab -n 5000 -c 500 http://localhost:8080/parallel-test/
예상 결과:
- 기존: 120 req/sec, 평균 8초
- 가상 스레드 병렬: 800 req/sec, 평균 1.2초 (6배 향상!)
Connection Times 95%가 2초 이하 → 실제 사용자 체감 가능 수준
6. JMeter로 프로페셔널 테스트 ( 1시간 )
JMeter 설치
https://jmeter.apache.org/download_jmeter.cgi#binaries 에 접근하여 다운로드

다운로드된 파일의 jmeter.bat 파일을 클릭하여 실행합니다.
< 개인적으로 그래프 가독성이 떨어지는 관계로 플러그인 설치를 추천! >
플러그인 설치 -> https://jmeter-plugins.org/install/Install/

이미지 안의 설명대로 다운로드한 plugins-manager.jar 파일을 lib/ext안에 복사해 넣으면 끝입니다!
JMeter 다운로드 → Thread Group(1000 threads) → HTTP Request(/parallel-test/)
대시보드 지표:
- 90th percentile < 2초
- Error rate 0%
- Throughput 1000+ req/sec
이제 Spring Boot 가상 스레드 + @Async 병렬 처리가 완성되었습니다! 순차 8초 걸리던 작업이 1.5초로 단축, 800 req/sec 달성했습니다. 실제 트레이딩/알림 서비스에 바로 적용해보세요.
'Tech > Spring' 카테고리의 다른 글
| [Spring] Spring Boot 가상 스레드(Virtual Threads) 적용하기 1편 (0) | 2025.12.31 |
|---|---|
| [Spring Boot] Spring Security + Redis로 DDoS 방어 구축하기 (0) | 2025.12.17 |
| [Spring Boot] 운영 환경을 위한 실용적인 로그 레벨 설정 (Practical Log Level) (0) | 2025.12.16 |
| [Spring] 빈 라이프사이클과 초기화 실수 feat. @PostConstruct, @Lazy, 순환참조 (3) | 2025.08.17 |
| [Spring] @Transactional 분리 시 발생하는 Self Invocation 오류와 그 원인 및 해결 (2) | 2025.08.16 |
