Spring Boot(Tomcat)에서 동시 요청 처리를 위해 쓰레드와 쓰레드풀이 어떻게 동작하는지 실무 관점에서 분석한다.
쓰레드 = 프로세스 내에서 실제 코드를 실행하는 주체 (자원)
프로세스 (JVM)
↓
쓰레드 1: 사용자 A의 /author/1 요청 처리
쓰레드 2: 사용자 B의 /author/2 요청 처리
쓰레드 3: 사용자 C의 /login 요청 처리
핵심: 사용자 요청 1개 = 쓰레드 1개 할당
WAS(Tomcat)가 멀티쓰레드를 생성/관리한다.
사용자 A → GET /author/1
사용자 B → POST /login
사용자 C → GET /posts
↓ 동시 요청
Tomcat
├── 쓰레드1 → /author/1 처리
├── 쓰레드2 → /login 처리
└── 쓰레드3 → /posts 처리
✅ 동시 요청 처리 가능 (CPU/메모리 허용 범위 내)
✅ 한 쓰레드 지연 → 다른 쓰레드 정상 동작
쓰레드 생성 = 1MB 메모리 소모
200 쓰레드 = 200MB 메모리
10만 쓰레드 = 100GB 메모리 → 서버 사망!
쓰레드1 실행 → CPU 코어 점유
↓ (시간 슬라이스 종료)
쓰레드2 실행 → CPU 상태 저장/복원 (수십 μs 지연)
↓
쓰레드1 재실행 → 반복
고객 10만명 동시 요청 → 쓰레드 10만개 생성 시도
→ 메모리 고갈 → OutOfMemoryError → 서버 다운
문제 해결: 쓰레드를 생성/소멸 반복 → 재사용
Tomcat 기본 설정
- 최대 쓰레드: 200개
- 최소 쓰레드: 10개
- 요청 초과 시: 거절 or 대기
1. Tomcat 시작 → 쓰레드 10개 미리 생성 (대기)
2. 사용자 200명 요청 → 쓰레드풀 200개까지 확장
3. 쓰레드 사용 완료 → 풀에 반납 (소멸 X)
4. 새 요청 → 반납된 쓰레드 재사용

블랙프라이데이: 10만명 동시 로그인
Tomcat: "쓰레드 10만개 생성?" → OOM → 서버 다운
10만명 동시 요청
Tomcat 쓰레드풀 (200개)
├── 200개 요청: 즉시 처리
└── 나머지 99,800개: 큐 대기 → 순차 처리
결과: 서버 안정성 보장
1. 요청 거절 (HTTP 503 Service Unavailable)
2. 외부 큐 대기 (RabbitMQ, Kafka)
3. 비동기 처리 (@Async)
# application-prod.yml
server:
tomcat:
max-threads: 200 # 최대 쓰레드
min-spare-threads: 10 # 최소 대기 쓰레드
max-connections: 10000 # 최대 연결
accept-count: 1000 # 대기 큐 크기
쓰레드풀 200개로 한계 → 추가 개선 필요
1. 로드밸런서 + WAS 클러스터
├── WAS1 (200 쓰레드)
├── WAS2 (200 쓰레드)
└── WAS3 (200 쓰레드)
→ 총 600 쓰레드
2. 큐잉 서비스
Redis → 요청 줄세우기 → 순차 처리
3. 비동기 처리
@Async + CompletableFuture
실무 확인 사항
- Active Threads: 현재 사용 쓰레드 수
- Queue Size: 대기 큐 크기
- Response Time: 응답 지연 여부
JMX/Actuator로 실시간 모니터링
쓰레드 기본
- 요청 1개 = 쓰레드 1개
- 멀티쓰레드 = 동시 처리
문제점
- 생성 비용 큼 (1MB/쓰레드)
- 컨텍스트 스위칭 오버헤드
- 폭발 위험
해결책
- 쓰레드풀 (Tomcat 기본 200개)
- 초과 요청 → 큐 대기/거절
- 로드밸런서 + 클러스터링
Tomcat 쓰레드풀 덕분에 200명 이상도 안전하게 처리! 10만명은 클러스터링으로 해결.