이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
Thread Pool Connection Pool 병목 연결에서 붙잡아야 할 핵심 판단 기준은 무엇인가?pool은 하위 병목을 증폭할 수 있다라는 기준은 어떤 JVM/OS 신호로 확인되는가?- Java/Spring 코드에서
TomcatmaxThreads가 가득 차면 새 요청은 accept 이후에도 애플리케이션 실행까지 밀릴 수 있다흐름은 어떤 장애 신호로 드러나는가?
개요
이 문서의 주제는 Thread Pool Connection Pool 병목 연결이다. Thread pool과 connection pool의 대기, 고갈, 장애 전파를 OS 자원과 연결한다.
먼저 붙잡을 기준은 다음 문장이다. pool은 처리량을 마법처럼 늘리는 장치가 아니라 대기열과 제한값을 명시하는 장치다. Tomcat worker, executor, HikariCP, HTTP client pool 중 하나가 막히면 나머지 pool은 원인일 수도 있고 하위 지연의 결과일 수도 있다.
핵심 모델
- 판단 기준: pool은 하위 병목을 증폭할 수 있다.
- Tomcat thread pool은 요청을 실행하는 worker의 수를 제한하고, connection pool은 DB나 외부 시스템 동시 사용량을 제한한다.
- connection을 기다리는 thread도 worker를 점유하므로 DB 대기가 HTTP 요청 대기로 전파될 수 있다.
- pool size 증가는 downstream capacity가 남아 있을 때만 완화책이다.
- pending, active, idle, timeout, queue length는 같은 시각에 봐야 원인 pool과 결과 pool을 구분할 수 있다.
- 장애 대응은 pool 값을 키우기 전에 대기 위치와 하위 latency를 확인하는 순서로 진행한다.
문서별 핵심 구분
- thread pool과 connection pool은 서로 다른 대기열이지만 요청 안에서 이어진다.
- DB connection 대기는 worker thread를 점유해 정상 API까지 지연시킬 수 있다.
- pool size 증가는 하위 자원이 여유 있을 때만 완화책이 된다.
원리
Thread pool과 connection pool은 모두 “동시에 몇 개까지 처리할 것인가”를 정하지만, 막히는 위치가 다르다. worker thread가 부족하면 요청 실행 자체가 대기하고, DB connection이 부족하면 이미 worker를 잡은 요청이 connection 획득에서 멈춘다.
pool pending이 증가했다고 해서 항상 pool size가 작다는 뜻은 아니다. DB query가 느려져 active connection이 오래 반환되지 않거나, 외부 HTTP 호출 timeout이 길어져 client pool이 비워지지 않거나, executor 작업이 blocking I/O를 잡고 있으면 pool 대기는 결과로 나타난다.
따라서 pool 장애는 busy worker, active connection, idle connection, pending acquire, downstream latency, timeout 로그, thread dump를 같은 시간축에 놓고 읽어야 한다. 값을 먼저 키우면 잠깐 완화될 수 있지만 DB, Redis, 외부 API에 더 큰 동시 부하를 밀어 넣어 장애를 넓힐 수 있다.
Java/Spring 연결
- Tomcat
maxThreads가 가득 차면 새 요청은 accept 이후에도 애플리케이션 실행까지 밀릴 수 있다. - HikariCP
active=max,idle=0,pending>0조합은 connection 획득 대기를 강하게 의심하게 한다. - thread dump에서
HikariPool.getConnection, HTTP client acquire, Redis connection acquire 같은 frame이 반복되면 pool 대기 위치가 드러난다. connectionTimeout, HTTP client timeout, transaction timeout이 서로 다르면 짧은 장애가 긴 worker 점유로 번질 수 있다.- pool metric은 평균 latency보다 pending, active, idle, timeout count를 우선 대시보드에 둔다.
코드와 설정 예시
date -Is
curl -s localhost:8080/actuator/metrics/tomcat.threads.busy
curl -s localhost:8080/actuator/metrics/hikaricp.connections.active
curl -s localhost:8080/actuator/metrics/hikaricp.connections.idle
curl -s localhost:8080/actuator/metrics/hikaricp.connections.pending
jcmd <pid> Thread.print예시는 “몇 개가 실행 중인가”와 “어디서 기다리는가”를 동시에 보기 위한 것이다. metric에서 pending이 늘어난 시각과 thread dump의 대기 frame, DB 또는 외부 API latency 로그가 같은 시각인지 맞춘다.
관찰 명령
curl -s localhost:8080/actuator/metrics/tomcat.threads.busy
curl -s localhost:8080/actuator/metrics/hikaricp.connections.active
curl -s localhost:8080/actuator/metrics/hikaricp.connections.idle
curl -s localhost:8080/actuator/metrics/hikaricp.connections.pending
jcmd <pid> Thread.printThread Pool Connection Pool 병목 연결 관찰 명령은 결론이 아니라 가설 확인 도구다. 운영 중에는 반복 간격, 대상 PID, 수집 시각을 함께 남기고 출력이 큰 명령은 범위를 제한한다.
지표 해석
| 신호 | 먼저 의심할 것 | 다음 확인 |
|---|---|---|
| Tomcat busy가 max 근처 | worker 포화 | request latency, executor queue, thread dump |
| Hikari active=max, idle=0 | DB connection 반환 지연 | query latency, transaction, leak detection |
| Hikari pending 증가 | connection 획득 대기 | getConnection stack, DB capacity, timeout |
| pool pending이 downstream latency 뒤에 증가 | pool은 결과일 가능성 | DB/Redis/API p95, timeout 로그 |
Pool 분석에서는 DB latency, Hikari pending, Tomcat busy, timeout 로그의 순서가 중요하다. 하위 지연이 먼저였는지 worker 고갈이 먼저였는지에 따라 대응이 달라진다.
장애 상황에서 보는 순서
- 지연된 API와 정상 API가 같은 pool을 공유하는지 확인한다.
- Tomcat busy/current, executor active/queue, Hikari active/idle/pending을 같은 시각에 수집한다.
- thread dump에서 worker가 CPU 계산, DB connection acquire, query 실행, 외부 HTTP 호출 중 어디에 머무는지 본다.
- DB, Redis, 외부 API의 latency와 timeout 로그가 pool pending보다 먼저 변했는지 확인한다.
- 임시 완화는 유입 제한, 느린 endpoint 차단, timeout 축소, retry 제한, pool 값 조정 순서로 검토한다.
- pool size 증가는 downstream이 받아낼 수 있고 FD, memory, DB connection limit이 남아 있을 때만 적용한다.
이 순서는 pool을 원인으로 단정하지 않기 위한 절차다. pending이 사라졌더라도 downstream latency가 그대로면 pool 조정은 완화일 뿐 근본 수정이 아니다.
실전 팁
- pool metric은 active만 보지 말고 idle과 pending을 같이 본다. active가 높아도 idle이 남아 있으면 병목 위치가 다를 수 있다.
- connection 획득 timeout과 query timeout을 분리해 기록한다. 둘은 원인과 대응이 다르다.
- pool size를 늘리기 전에 DB
max_connections, 외부 API rate limit, JVM thread stack memory, FD limit을 확인한다. - retry가 있는 서비스는 pool 고갈이 더 빨리 온다. 재시도가 같은 pool을 다시 점유하기 때문이다.
- endpoint별 pool 사용량을 알 수 없으면 느린 endpoint 하나가 전체 worker를 점유하는 상황을 놓치기 쉽다.
주니어 팁
- thread pool은 요청 실행 자리이고 connection pool은 하위 시스템 사용권이다. 둘을 같은 “스레드 개수”로 이해하면 안 된다.
- pending이 높을 때 바로 pool size를 키우지 말고 active가 왜 반환되지 않는지 먼저 본다.
- thread dump에서 같은 대기 frame이 여러 worker에 반복되면 metric보다 더 직접적인 단서가 될 수 있다.
시니어 팁
- pool 설계는 각 하위 시스템의 실제 동시 처리량과 timeout 정책을 기준으로 맞춘다.
- bulkhead를 두면 느린 외부 시스템이 전체 Tomcat worker를 점유하는 상황을 줄일 수 있다.
- pool 대시보드는 saturation뿐 아니라 acquire timeout, usage time, pending time을 endpoint나 dependency 기준으로 쪼갤수록 장애 격리가 빨라진다.
Guru급 팁
- Pool 크기는 평균 처리시간이 아니라 p95/p99 사용시간, dependency capacity, timeout budget을 기준으로 잡는다.
- Bulkhead는 pool을 여러 개 만드는 일이 아니라 장애 전파 범위를 설계하는 일이다. 느린 dependency가 전체 worker를 잡지 못하게 해야 한다.
- Pool 조정 후에는 pending만 볼 것이 아니라 downstream error, DB saturation, FD 사용량, thread stack memory까지 다시 본다.
위험 신호!
- Hikari pending만 보고 DB pool size부터 늘린다.
- Tomcat busy가 높은데 thread dump 없이 CPU 문제라고 단정한다.
- DB가 느린 상황에서 애플리케이션 pool만 키워 DB 동시 부하를 더 늘린다.
- timeout이 너무 길어 worker가 하위 장애를 오래 붙잡고 있다.
- retry와 pool 고갈이 동시에 있는데 재시도 정책을 확인하지 않는다.
- pool metric, downstream latency, thread dump의 시각이 서로 맞지 않는다.
확인 질문
확인 질문
- connection pool pending이 증가했을 때 바로 pool size를 늘리면 왜 위험한가?
- DB나 외부 시스템이 이미 느린 상태라면 더 많은 동시 요청을 밀어 넣어 downstream 장애와 전체 지연을 키울 수 있기 때문이다.
- thread pool 병목과 connection pool 병목을 구분하는 가장 직접적인 자료는 무엇인가?
- Actuator pool metric과 같은 시각의 thread dump다. worker가
getConnection에서 기다리는지, CPU 계산 중인지, 외부 호출 중인지 확인해야 한다.- pool 장애 회고에 반드시 남겨야 할 값은 무엇인가?
- active, idle, pending, timeout count, downstream latency, pool 설정값, thread dump 시각, 적용한 완화 조치와 그 효과를 남겨야 한다.