이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
Thread 고갈과 응답 지연 대응에서 붙잡아야 할 핵심 판단 기준은 무엇인가?느린 요청은 thread 위치로 좁힌다라는 기준은 어떤 JVM/OS 신호로 확인되는가?- Java/Spring 코드에서
Tomcatbusy가 높고 Hikaripending도 높으면 DB connection 대기가 worker를 점유하는지 본다흐름은 어떤 장애 신호로 드러나는가?
개요
이 문서의 주제는 Thread 고갈과 응답 지연 대응이다. Thread 고갈, pool starvation, blocking I/O로 인한 응답 지연 대응 절차를 정리한다.
먼저 붙잡을 기준은 다음 문장이다. 느린 요청은 thread 위치로 좁힌다. CPU가 낮아도 모든 worker가 DB connection, 외부 HTTP, lock, file I/O에서 기다리면 사용자는 장애를 겪는다.
핵심 모델
- 판단 기준: 느린 요청은 thread 위치로 좁힌다.
- Thread 고갈은 worker 수 부족이 아니라 worker가 반환되지 않는 상태로 보는 것이 먼저다.
- Tomcat busy, executor queue, Hikari pending, downstream latency를 같은 시각에 맞춘다.
- Thread dump는 한 번보다 2~3회 반복 샘플이 강하다. 같은 stack이 계속 보이면 대기 위치가 고정된다.
- Thread pool 증설은 하위 자원이 받아낼 수 있을 때만 완화책이다.
- 즉시 완화는 유입 제한, 느린 endpoint 차단, timeout 축소, retry 제한, downstream 보호 순서로 검토한다.
문서별 핵심 구분
- thread 고갈은 busy thread, executor queue, downstream 대기를 같이 봐야 한다.
- CPU가 낮아도 모든 worker가 blocking 중이면 응답은 멈춘다.
- thread dump 반복 샘플로 대기 위치를 고정한다.
원리
Thread 고갈은 “thread가 많다”보다 “thread가 어디에 머무는가”가 중요하다. CPU를 쓰며 계산 중인지, DB connection을 기다리는지, query 실행 중인지, 외부 HTTP 응답을 기다리는지, lock에 막혔는지에 따라 조치가 다르다.
CPU가 낮고 latency가 높다면 worker가 blocking 상태일 수 있다. 이때 CPU 증설이나 thread pool 증설은 근본 조치가 아니며, 대기 위치와 timeout 정책을 먼저 확인해야 한다.
Thread dump는 순간 사진이다. 같은 간격으로 여러 번 떠서 같은 stack이 반복되는지, worker가 계속 같은 dependency에 묶이는지, 대기 상태가 이동하는지 확인해야 한다.
Java/Spring 연결
- Tomcat
busy가 높고 Hikaripending도 높으면 DB connection 대기가 worker를 점유하는지 본다. @Async, scheduler, custom executor는 Tomcat worker와 다른 pool이므로 metric과 queue를 분리한다.- WebClient/HTTP client, Redis, DB timeout이 길면 하위 장애가 worker 고갈로 전파된다.
- ThreadLocal 누수나 request scope 오해는 thread 재사용 환경에서 증상을 숨길 수 있다.
- Actuator metric은 thread 수를 보여 주지만, 실제 대기 위치는 thread dump stack이 말해 준다.
코드와 설정 예시
date -Is
jcmd <pid> Thread.print > thread-1.txt
sleep 10
jcmd <pid> Thread.print > thread-2.txt
curl -s localhost:8080/actuator/metrics/tomcat.threads.busy
curl -s localhost:8080/actuator/metrics/hikaricp.connections.pending예시는 반복 thread dump와 pool metric을 같은 시각에 묶는 흐름이다. 두 dump에서 같은 stack이 반복되면 일시적 spike보다 지속 대기를 의심할 수 있다.
관찰 명령
jcmd <pid> Thread.print > thread.txt
curl -s localhost:8080/actuator/metrics/tomcat.threads.busy
curl -s localhost:8080/actuator/metrics/executor.queuedThread 고갈과 응답 지연 대응 관찰 명령은 결론이 아니라 가설 확인 도구다. 운영 중에는 반복 간격, 대상 PID, 수집 시각을 함께 남기고 출력이 큰 명령은 범위를 제한한다.
지표 해석
| 신호 | 먼저 의심할 것 | 다음 확인 |
|---|---|---|
| Tomcat busy=max | worker 포화 | thread dump, request latency |
| executor queue 증가 | async 작업 적체 | executor active/queued/completed |
| Hikari pending 증가 | DB connection 대기 | active/idle/pending, query latency |
| CPU 낮고 latency 높음 | blocking I/O, lock, downstream 대기 | repeated thread dump |
Thread 대응은 busy worker, queue, pending, downstream latency의 순서를 맞춘다. downstream 지연이 먼저면 pool 고갈은 원인이 아니라 결과일 수 있다.
장애 상황에서 보는 순서
- 영향 endpoint와 시작 시각, timeout 로그를 고정한다.
- Tomcat busy/current, executor queue, Hikari active/idle/pending을 수집한다.
- Thread dump를 2~3회 반복 수집해 대기 stack이 고정되는지 본다.
- DB, Redis, 외부 HTTP, file I/O, lock 중 어디에서 worker가 머무는지 분류한다.
- 유입 제한, 느린 endpoint 차단, timeout 축소, retry 제한으로 worker 점유 시간을 줄인다.
- Pool size 변경은 하위 capacity와 FD/memory 여유를 확인한 뒤 적용한다.
Thread 고갈이 풀려도 downstream latency가 그대로면 근본 원인은 남아 있다. 완화와 원인 수정을 보고서에서 분리한다.
실전 팁
- Thread dump 파일에는 수집 시각과 PID를 이름에 넣는다.
WAITING,TIMED_WAITING,BLOCKED,RUNNABLE은 stack과 함께 해석해야 한다. 상태명만으로 원인을 확정하지 않는다.- DB connection acquire 대기와 query 실행 대기는 다르다. 전자는 pool/반환 문제, 후자는 DB latency 문제일 수 있다.
- Timeout을 길게 늘리면 오류는 줄어도 worker 점유 시간이 늘어 장애가 커질 수 있다.
- Thread pool 증설 후에는 context switch, memory stack, downstream 부하를 다시 확인한다.
주니어 팁
- Thread 수가 많다는 사실보다 어떤 stack에 몰렸는지가 더 중요하다.
- CPU가 낮아도 장애가 아닐 수 있다는 뜻은 아니다. 모든 worker가 기다리면 응답은 멈춘다.
- Pool metric을 볼 때 active만 보지 말고 idle, pending, queue를 같이 본다.
시니어 팁
- Dependency별 bulkhead와 timeout budget을 설계해 느린 하위 시스템이 전체 worker를 잡지 못하게 한다.
- Endpoint별 worker 점유 시간과 pool 사용량을 관측하면 장애 격리가 빨라진다.
- Thread pool 값은 DB connection 수, HTTP client pool, FD limit, memory stack 예산과 함께 결정한다.
Guru급 팁
- Repeated dump에서 같은 stack이 사라지지 않으면 순간 지연이 아니라 구조적 대기일 가능성이 크다.
- Thread 고갈 회고에는 “어떤 pool이 찼는가”보다 “왜 반환되지 않았는가”가 들어가야 한다.
- Worker 점유 시간을 줄이는 설계는 timeout, cancellation, backpressure, bulkhead가 함께 움직여야 한다.
위험 신호!
- Thread dump 없이 pool size부터 늘린다.
- Hikari pending이 높은데 DB latency와 query 상태를 보지 않는다.
- Timeout을 늘려 worker 점유 시간을 더 길게 만든다.
- Retry 정책을 확인하지 않고 downstream 장애를 반복 호출한다.
- CPU가 낮다는 이유로 thread 고갈 가능성을 배제한다.
확인 질문
확인 질문
- Thread 고갈에서 반복 thread dump가 중요한 이유는 무엇인가?
- 한 번의 dump는 순간 상태만 보여 주지만, 반복 dump에서 같은 stack이 유지되면 지속 대기 위치를 확정하기 쉽기 때문이다.
- Pool size를 늘리기 전에 확인해야 할 것은 무엇인가?
- Worker가 어디서 반환되지 않는지, downstream capacity와 timeout/retry 정책이 어떤지, FD와 memory 여유가 있는지 확인해야 한다.
- Thread 고갈 보고서에 반드시 남겨야 할 값은 무엇인가?
- Tomcat busy, executor queue, Hikari active/idle/pending, 반복 thread dump, downstream latency, 완화 조치와 효과를 남겨야 한다.