이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Thread Pool Starvation에서 붙잡아야 할 핵심 판단 기준은 무엇인가?
  • pool 내부 대기는 전체 처리 중단으로 번질 수 있다라는 기준은 어떤 JVM/OS 신호로 확인되는가?
  • Java/Spring 코드에서 Thread Pool Starvation 판단 기준은 어떤 장애 신호로 드러나는가?

개요

이 문서의 주제는 Thread Pool Starvation이다. Thread pool이 모두 대기 작업으로 묶여 새 작업을 처리하지 못하는 장애 패턴을 다룬다.

먼저 붙잡을 기준은 다음 문장이다. pool 내부 대기는 전체 처리 중단으로 번질 수 있다. 이 문서는 이 기준을 Java/Spring 실행 흐름, JVM/OS 관찰 지표, 장애 판단 순서로 연결한다.

핵심 모델

  • 판단 기준: starvation은 일이 없어서가 아니라 실행할 worker가 없어서 생긴다.
  • Worker가 모두 blocking 대기 중이면 queue에 작업이 있어도 처리되지 않는다.
  • Nested async 작업이 같은 pool에 다시 작업을 제출하고 기다리면 self-starvation이 생길 수 있다.
  • Unbounded queue는 starvation을 실패 대신 지연과 memory 증가로 숨긴다.
  • Starvation은 CPU 사용률이 낮아도 latency가 커지는 모양으로 나타날 수 있다.

문서별 핵심 구분

  • pool의 모든 worker가 대기 작업에 묶이면 새 작업이 실행되지 못한다.
  • 같은 pool에 작업을 제출하고 결과를 기다리는 구조가 위험하다.
  • starvation은 CPU가 낮은데 latency가 커지는 형태로 자주 보인다.

원리

Thread pool starvation은 worker가 모두 바쁜 상태를 넘어, worker들이 새 작업이나 완료 작업이 실행되기를 기다리면서 pool 내부가 스스로 막히는 패턴이다.

대표적인 구조는 같은 pool 안에서 부모 작업이 자식 작업을 제출하고 get()으로 기다리는 경우다. 자식 작업을 실행할 빈 worker가 없으면 부모 worker들은 모두 기다리고, queue에는 실행되지 못한 자식 작업이 쌓인다.

이 장애는 CPU가 낮은데 latency가 커지는 형태로 자주 나타난다. thread들은 계산을 하는 것이 아니라 Future.get, CountDownLatch.await, queue take, connection acquire 같은 위치에서 기다린다.

Thread Pool Starvation 관점에서 중요한 점은 “worker 수 부족”과 “pool 안 의존성”을 구분하는 것이다. 단순히 pool size를 키우면 잠시 완화될 수 있지만, 같은 구조가 남아 있으면 더 큰 규모에서 다시 막힌다.

Java/Spring 연결

  • Tomcat worker가 DB connection acquire에서 몰리면 HTTP 요청은 들어와도 handler 실행이 늦어진다.
  • @Async 작업이 같은 executor에 하위 작업을 제출하고 get()으로 기다리면 pool이 막힐 수 있다.
  • Hikari pending, executor queue, Tomcat busy thread를 같은 시간축에서 본다.
  • Retry가 같은 pool에서 실행되면 원래 작업보다 retry 작업이 worker를 더 빨리 고갈시킬 수 있다.
  • Rejection과 timeout 정책이 없으면 사용자는 실패 대신 긴 대기를 경험한다.

코드와 설정 예시

Future<Order> child = executor.submit(() -> orderClient.fetch(orderId));
 
// 같은 executor의 worker 안에서 다시 같은 executor 작업을 기다리면 starvation 후보가 된다.
return child.get();

예시는 같은 pool 안에서 작업을 제출하고 다시 기다리는 구조를 보여 준다. 모든 worker가 이런 대기 상태에 빠지면 queue에 있는 완료 작업이나 자식 작업이 실행될 기회를 잃는다.

관찰 명령

jcmd <pid> Thread.print
curl -s localhost:8080/actuator/metrics/executor.queued
curl -s localhost:8080/actuator/metrics/executor.active

Thread Pool Starvation 관찰 명령은 결론이 아니라 가설 확인 도구다. 운영 중에는 반복 간격, 대상 PID, 수집 시각을 함께 남기고 출력이 큰 명령은 범위를 제한한다.

지표 해석

신호먼저 의심할 것다음 확인
Tomcat busy thread가 max 근처blocking I/O 또는 긴 요청 처리thread dump와 endpoint별 latency
executor queue가 계속 증가worker 부족 또는 downstream 지연active/queued/rejected 지표
event loop thread CPU가 높음loop 안 CPU 작업 또는 blocking 호출JFR, thread name, stack trace
timeout 후 retry 급증backpressure 실패retry 로그와 downstream error rate

Thread Pool Starvation에서는 핵심 신호와 보조 지표를 같은 시간축에서 맞춘다. 먼저 변한 신호와 뒤따라 포화된 신호를 분리해야 원인과 결과를 구분할 수 있다.

장애 상황에서 보는 순서

  1. executor active, queue, rejected, completed 지표를 확인한다.
  2. thread dump에서 worker가 어떤 대기 지점에 몰렸는지 본다.
  3. DB pool pending, HTTP client pool pending, lock wait를 같이 확인한다.
  4. nested async, synchronous wait, retry 폭주를 코드 경로에서 찾는다.
  5. pool 크기보다 queue bound, timeout, rejection, bulkhead를 먼저 정한다.

이 순서는 thread pool starvation을 단순 worker 부족이 아니라 대기 구조 문제로 읽기 위한 루틴이다.

실전 팁

  • dump에서 같은 executor worker가 Future.get, join, await에 몰려 있는지 본다.
  • parent task와 child task가 같은 pool을 쓰는지 코드 경로를 확인한다.
  • 사용자 요청, batch, callback, scheduler는 가능한 한 pool을 분리한다.
  • queue depth와 active worker가 동시에 높은데 completed가 늘지 않으면 starvation을 의심한다.
  • pool size 증가는 임시 완화로만 보고 의존 구조를 제거한다.

주니어 팁

  • Thread Pool Starvation 주제는 용어 정의보다 장애 때 어떤 증거로 확인할지까지 연결해 익힌다.
  • 정상 상태에서 이 문서의 관찰 명령을 실행해 Thread Pool Starvation 기준선을 남긴다.
  • 장애 시에는 Thread Pool Starvation 신호가 평소 기준선에서 벗어난 시각과 방향을 먼저 본다.

시니어 팁

  • Thread Pool Starvation 주제의 영향이 pool, queue, retry, timeout으로 번지는 경로를 설계 리뷰에 포함한다.
  • Thread Pool Starvation runbook에는 실행 명령, 정상 범위, 해석 기준, 금지할 대응을 같이 둔다.
  • Thread Pool Starvation 판단이 container, LB, 다중 인스턴스 환경에서도 같은 의미인지 확인한다.

Guru급 팁

  • Thread Pool Starvation 주제는 단일 snapshot보다 변화 방향이 중요하다. 같은 명령을 간격을 두고 실행해 상태가 증가, 유지, 회복 중 어디인지 본다.
  • 고급 도구는 Thread Pool Starvation 가설을 좁힐 때만 사용하고 기본 지표와 모순되는지 먼저 확인한다.
  • Thread Pool Starvation 조치 후에는 CPU, memory, socket, disk, pool 중 다음 포화 지점이 어디로 이동했는지 확인한다.

위험 신호!

  • 모든 worker가 CompletableFuture.join()이나 Future.get()에서 멈춰 있다.
  • executor queue는 쌓이는데 처리 완료 수가 늘지 않는다.
  • CPU 사용률은 낮은데 요청 timeout이 증가한다.
  • 공용 executor를 여러 기능이 공유해 어느 작업이 굶기는지 모른다.
  • rejection이 없어서 장애가 빠르게 실패하지 않고 오래 대기한다.

확인 질문

확인 질문

  • Thread Pool Starvation 주제를 장애 중 확인할 때 Application 로그만으로 부족한 이유는 무엇인가?
    • 로그는 코드 경로를 보여 주지만 Thread Pool Starvation의 JVM/OS 자원 상태, 대기 위치, 포화 방향은 별도 지표로 확인해야 하기 때문이다.
  • Thread Pool Starvation 관련 설정이나 pool 크기를 바꾸기 전에 확인할 것은 무엇인가?
    • 먼저 변한 신호, 하위 자원 capacity, 변경이 완화인지 증폭인지 판단할 기준을 Thread Pool Starvation 지표로 확인해야 한다.
  • Thread Pool Starvation 재발을 막기 위해 장애 기록에 무엇을 남겨야 하는가?
    • 발생 시간, 영향 범위, 사용한 명령, Thread Pool Starvation 핵심 지표, 원인 판단, 완화 조치, 근본 수정, 남은 리스크를 남긴다.

참고 문서