대규모 이벤트나 티켓팅 서비스에서 가장 먼저 부딪히는 문제는 트래픽 폭주(traffic spike)이다. 수만 명의 사용자가 동시에 접속하는 상황에서 서비스 전체가 무너지지 않도록 하기 위해 대부분의 시스템은 접속 대기열(VWR; Virtual Waiting Room)을 도입한다.
초기 설계 단계에서 가장 먼저 떠오르는 유혹은 "현재 운영 중인 Spring Boot(WAS) 서버 내부에 대기열 로직을 녹여내는 것"이다. 인프라 구조를 바꿀 필요 없이 익숙한 애플리케이션 레벨에서 Filter나 Interceptor를 이용해 트래픽을 통제하면 될 것처럼 보이기 때문이다.
하지만 이 단순한 접근은 실전에서 쉽게 서비스 전체 장애로 이어질 수 있다.
이 글에서는 단일 WAS 내부에서 대기열을 처리하려고 했을 때 마주하게 되는 물리적, 구조적 한계와 이를 극복하기 위해 대기열 시스템을 별도의 아키텍처로 분리해야 했던 이유를 정리해 본다.
1. Spring MVC의 Thread-per-request와 스레드 고갈
필자가 주로 사용하는 환경은 Spring MVC + Tomcat 기반의 WAS이다.
Spring MVC는 기본적으로 Thread-per-request 모델로 동작한다.
즉
1 HTTP 요청 → 1 Worker Thread
Tomcat의 기본 설정은 보통 다음과 같다.

만약 대기열을 HTTP 연결을 유지한 채 대기시키는 방식(예: Long-polling, SSE 등)으로 구현한다면 어떻게 될까.
수만 명의 사용자가 동시에 접속하는 순간 다음과 같은 상황이 발생할 수 있다.
10,000 users
→ 10,000 concurrent HTTP connections
→ Worker Thread 고갈
결국 대기 중인 사용자들이 서버의 스레드를 모두 점유하게 되고, 정작 대기열을 통과한 실제 서비스 요청이 처리되지 못하는 Thread Pool Exhaustion 상황이 발생한다. 이 문제는 대기열 시스템이 오히려 실제 서비스 요청을 가로막는 병목이 될 수 있기 때문에 트래픽 스파이크 상황에서 치명적이다.
2. WebFlux 도입 고려
이 문제를 경험하면 자연스럽게 Spring WebFlux를 떠올리게 된다. WebFlux는 Netty 기반의 이벤트 루프(Event Loop)와 Non-Blocking I/O를 사용한다.
소수의 Event Loop Thread
→ 수만 개의 connection 처리
따라서 다음과 같은 기대를 하게 된다.
WebFlux로 전환하면 1만 명이 대기해도 스레드가 묶이지 않으니 문제가 해결되는 것 아닐까?
하지만 여기서 중요한 착각이 발생한다. WebFlux는 Connection Scalability를 해결해 줄 뿐 트래픽 제어(Traffic Control) 문제를 해결해 주지는 않는다.
3. 동일 JVM 자원 경합과 DB 커넥션 풀
WebFlux 기반 애플리케이션에서도 대기열 로직과 실제 비즈니스 로직을 동일한 WAS에서 실행한다면 또 다른 문제가 발생한다.
바로 JVM 자원 경합이다.

트래픽 스파이크 상황에서는 대기열 상태 조회 요청과 실제 비즈니스 요청이 동시에 몰린다. 이 두 종류의 요청이 동일한 JVM 환경에서 CPU, 메모리, 스레드 자원을 공유하게 되면 대기열 트래픽이 증가할수록 비즈니스 로직의 처리 지연 역시 함께 증가할 수 있다.
특히 많은 서비스가 WebFlux 환경에서도 여전히 JDBC와 JPA/Hibernate 기반의 데이터 접근 계층을 사용하고 있기 때문에 애플리케이션 전체가 완전한 Non-blocking 스택으로 동작하지 않는 경우가 많다. JDBC는 Blocking I/O 기반 드라이버이므로 WebFlux에서는 이러한 작업을 별도의 Worker Thread Pool(boundedElastic)로 위임하게 되며 트래픽이 급증하면 이 스레드 풀 역시 빠르게 포화 상태에 도달할 수 있다.
여기에 DB Connection Pool이라는 또 다른 물리적 제약이 존재한다. 일반적으로 서비스의 DB 커넥션 풀은 수십 개에서 많아야 수백 개 수준으로 제한된다. 따라서 WebFlux가 수만 개의 요청을 동시에 수용하더라도 실제 데이터베이스 접근 단계에서는 훨씬 작은 처리량으로 제한되는 구조적 병목이 발생한다. 결국 대기열 트래픽이 증가할수록 핵심 서비스가 사용할 수 있는 자원이 잠식되고 그 결과 대기열 시스템이 서비스를 보호하는 것이 아니라 서비스 지연을 가속시키는 상황이 발생할 수 있다.
이러한 이유로 대기열 시스템의 핵심 목적은 단순히 많은 연결을 유지하는 것이 아니라 서비스가 감당할 수 있는 처리량에 맞게 유입 트래픽을 제어하는 것에 있다. 예를 들어 서비스가 안정적으로 처리할 수 있는 요청이 초당 200개라면, 10,000명의 사용자가 동시에 접속하더라도 모든 요청을 한 번에 서비스로 전달하는 것이 아니라 대기열에 저장한 뒤 초당 200명씩 순차적으로 진입시키는 방식으로 트래픽을 제어해야 한다.
4. 아키텍처의 물리적 분리
일련의 실패 시나리오를 통해 우리는 하나의 명확한 결론에 도달한다.
WebFlux는 연결 확장성(Connection Scalability)을 해결할 뿐 트래픽 제어(Traffic Control)를 위한 대기열 시스템의 근본적인 설계 문제를 해결해주진 않는다.
트래픽 제어와 핵심 비즈니스 로직은 동일한 JVM 환경에서 공존해서는 안 된다.

API Gateway : 앞단에서 진입 토큰 검증, Rate Limiting, Routing을 담당
VWR 서버 : WebFlux와 Redis 기반으로 구축되어 수만 명의 대기 커넥션과 큐 상태만 전담하여 처리한다.
본 서비스 WAS : VWR에서 정당한 토큰을 발급받은 소수의 유저만 진입하여, 제한된 스레드와 DB 커넥션 풀 안에서 평화롭게 비즈니스 로직에만 집중한다.
다음에는 실제로 이 구조를 어떻게 구현할 수 있는지, API Gateway와 VWR(Virtual Waiting Room) 서버를 이용한 대기열 시스템 설계를 구체적으로 살펴보겠다.
'Programming Note' 카테고리의 다른 글
| 유클리드 호제법(Euclidean algorithm) (0) | 2026.05.04 |
|---|---|
| WSL에 USB 장치 연결하기 (0) | 2026.03.10 |
| [Git] Local 변경 사항 Discard 이후에도 Modified 상태가 남아있는 경우 (0) | 2026.01.28 |
| JsonPath 사용해보기 (0) | 2025.11.04 |
| XML과 HTML의 차이 (0) | 2025.11.03 |