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

  • CPU 사용률 급등 대응에서 붙잡아야 할 핵심 판단 기준은 무엇인가?
  • 높은 CPU를 stack trace로 연결한다라는 기준은 어떤 JVM/OS 신호로 확인되는가?
  • Java/Spring 코드에서 Actuator process.cpu.usage, HTTP request count, endpoint latency를 같은 시각에 본다 흐름은 어떤 장애 신호로 드러나는가?

개요

이 문서의 주제는 CPU 사용률 급등 대응이다. CPU 급등 시 Java thread, GC, JIT, busy loop, traffic spike를 분리하는 대응 절차를 정리한다.

먼저 붙잡을 기준은 다음 문장이다. 높은 CPU를 stack trace로 연결한다. CPU 급등 대응은 평균 CPU 숫자를 보는 일이 아니라 어떤 OS thread가 CPU를 쓰고, 그 thread가 어떤 Java stack에 있는지 연결하는 일이다.

핵심 모델

  • 판단 기준: 높은 CPU를 stack trace로 연결한다.
  • Host CPU 평균보다 JVM process CPU, thread별 CPU, container CPU quota를 먼저 나눈다.
  • top -H의 LWP id를 16진수로 바꿔 thread dump의 nid와 연결해야 Java 코드 위치가 나온다.
  • CPU 급등은 application hot path, GC, JIT compilation, retry storm, traffic spike로 나누어 본다.
  • CPU 포화 중 thread pool 증설은 처리량보다 context switch와 tail latency를 키울 수 있다.
  • 즉시 완화는 유입 제한, 비싼 endpoint 차단, batch 중지, retry 제한처럼 CPU 사용량을 줄이는 조치부터 검토한다.

문서별 핵심 구분

  • CPU 급등은 hot code, GC, JIT, retry, traffic spike 중 하나일 수 있다.
  • top -H와 thread dump를 맞춰 Java stack을 찾는다.
  • JFR은 CPU 원인을 짧게 수집하는 데 유용하다.

원리

CPU 급등의 첫 분기는 “누가 CPU를 쓰는가”다. 전체 host가 바쁜지, 특정 JVM process가 바쁜지, JVM 안의 특정 LWP가 바쁜지 순서대로 좁힌다.

Java thread dump는 stack을 보여 주지만 CPU 사용량은 보여 주지 않는다. 반대로 top -H는 CPU를 쓰는 LWP를 보여 주지만 Java method 이름은 보여 주지 않는다. 두 자료를 nid로 연결해야 실제 hot code를 찾을 수 있다.

CPU 원인이 항상 application code는 아니다. GC thread가 CPU를 쓰거나 JIT compilation이 배포 직후 CPU를 올리거나, retry storm이 같은 코드를 반복 실행해 CPU를 태울 수 있다. 그래서 CPU 대응은 thread stack, GC log/JFR, traffic/retry 지표를 같은 시간축에 놓고 판단한다.

Java/Spring 연결

  • Actuator process.cpu.usage, HTTP request count, endpoint latency를 같은 시각에 본다.
  • JSON 직렬화, encryption, compression, report generation, large sorting/grouping은 CPU hot path 후보가 된다.
  • GC CPU인지 application CPU인지 구분하려면 GC log/JFR event와 hot thread stack을 같이 본다.
  • Retry, scheduler, batch job이 같은 CPU를 반복 사용하면 사용자 요청도 함께 느려진다.
  • Container 배포에서는 CPU quota, throttling, JVM ActiveProcessorCount 인식 차이를 확인한다.

코드와 설정 예시

date -Is
top -H -p <pid>
ps -L -p <pid> -o tid,pcpu,stat,comm
jcmd <pid> Thread.print > thread-$(date +%s).txt
jcmd <pid> JFR.start name=cpu settings=profile duration=120s filename=cpu.jfr

예시는 CPU를 쓰는 LWP와 Java stack을 연결하기 위한 최소 묶음이다. top -H에서 CPU가 높은 thread id를 찾고, 16진수 nid로 변환해 thread dump에서 같은 thread를 확인한다.

관찰 명령

top -H -p <pid>
ps -L -p <pid> -o tid,pcpu,stat,comm
jcmd <pid> Thread.print
jcmd <pid> JFR.start name=cpu duration=120s filename=cpu.jfr

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

지표 해석

신호먼저 의심할 것다음 확인
Process CPU 높음JVM 또는 sidecar CPU 사용process별 CPU, container quota
특정 LWP CPU 높음hot Java threadLWP id thread dump nid
여러 GC thread CPU 높음GC work, allocation pressureGC log, JFR allocation
CPU 높고 retry 증가retry storm, downstream 장애 전파retry count, dependency latency

CPU 지표는 hot thread, GC event, traffic/retry 증가, latency spike의 순서를 맞춰 해석한다. CPU가 먼저 오른 것인지, 하위 장애 재시도가 CPU를 뒤따라 올린 것인지가 대응을 가른다.

장애 상황에서 보는 순서

  1. 사용자 영향, 시작 시각, CPU가 높은 host/container/PID를 기록한다.
  2. top -Hps -L로 CPU를 쓰는 LWP를 찾는다.
  3. 같은 시각의 thread dump에서 LWP nid와 Java stack을 맞춘다.
  4. GC log/JFR로 GC/JIT/application hot path를 분리한다.
  5. traffic spike, retry 증가, scheduler/batch 실행 시각을 확인한다.
  6. 유입 제한, 비싼 endpoint 차단, batch 중지, retry 제한으로 CPU 사용량을 낮춘다.

CPU가 내려가도 원인이 사라졌다고 보지는 않는다. 어떤 stack이 CPU를 썼고 어떤 완화가 CPU를 낮췄는지 함께 남겨야 근본 수정으로 이어진다.

실전 팁

  • CPU가 높을 때 thread dump만 떠서는 부족하다. CPU 높은 LWP id를 먼저 확보해야 stack과 연결된다.
  • JFR은 duration을 짧게 제한한다. CPU 장애 중 장시간 profile은 파일과 CPU 비용을 다시 만든다.
  • CPU hot path가 특정 endpoint라면 임시 완화는 endpoint rate limit이나 feature flag 차단이 더 직접적이다.
  • CPU가 GC 때문이면 allocation 원인을 찾아야지 collector부터 바꾸지 않는다.
  • CPU 증설은 완화일 수 있지만 retry storm이나 busy loop를 고치지는 못한다.

주니어 팁

  • top -H의 thread id와 thread dump의 nid를 연결하는 절차를 손에 익힌다.
  • CPU가 높다고 thread pool을 늘리면 안 된다. CPU core가 이미 바쁘면 더 많은 thread는 더 많은 전환 비용을 만든다.
  • CPU 급등과 load average 급등을 같은 뜻으로 보지 않는다. %wa가 높으면 disk 대기도 같이 본다.

시니어 팁

  • CPU heavy endpoint는 별도 executor, rate limit, cache, batch offload 기준을 설계에 포함한다.
  • Container CPU quota와 JVM processor 인식 차이를 표준 점검 항목으로 둔다.
  • CPU 대응 runbook에는 hot thread 매핑 절차와 JFR 수집 제한 시간을 같이 적는다.

Guru급 팁

  • CPU flame graph는 결론이 아니라 비용 지도다. 가장 뜨거운 method가 사용자 영향과 연결되는지 확인해야 한다.
  • Hot path 최적화 뒤에는 GC, lock, DB 같은 다음 병목이 드러날 수 있다. 같은 부하로 재측정한다.
  • 장애 후에는 “CPU를 누가 썼나”뿐 아니라 “왜 그 코드 경로가 그 시각에 많이 실행됐나”까지 남긴다.

위험 신호!

  • 평균 CPU만 보고 hot thread를 찾지 않는다.
  • LWP id와 nid를 연결하지 않고 Java 코드 원인을 단정한다.
  • GC CPU와 application CPU를 구분하지 않는다.
  • CPU 포화 중 thread pool size부터 늘린다.
  • Retry storm으로 CPU가 오른 상황에서 timeout/retry 정책을 보지 않는다.

확인 질문

확인 질문

  • CPU를 많이 쓰는 Java 코드를 찾으려면 어떤 연결이 필요한가?
    • top -Hps -L의 LWP id를 16진수로 변환해 thread dump의 nid와 연결해야 한다.
  • CPU 급등 중 thread pool을 늘리기 전에 확인해야 할 것은 무엇인가?
    • CPU core와 quota가 이미 포화됐는지, hot path가 계산 작업인지, retry나 GC가 CPU를 쓰는지 확인해야 한다.
  • CPU 장애 보고서에 반드시 남겨야 할 증거는 무엇인가?
    • 높은 CPU를 보인 PID/LWP, 연결된 Java stack, GC/JFR 증거, traffic/retry 변화, 적용한 완화와 효과를 남겨야 한다.

참고 문서