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

  • CPU Bound와 Load Average에서 붙잡아야 할 핵심 판단 기준은 무엇인가?
  • CPU 포화와 대기열을 같이 본다라는 기준은 어떤 JVM/OS 신호로 확인되는가?
  • Java/Spring 코드에서 CPU를 많이 쓰는 LWP id는 16진수로 바꿔 thread dump의 nid와 연결한다 흐름은 어떤 장애 신호로 드러나는가?

개요

이 문서의 주제는 CPU Bound와 Load Average이다. CPU bound, load average, runnable thread를 구분해 계산 병목을 판단하는 방법을 정리한다.

먼저 붙잡을 기준은 다음 문장이다. CPU 포화와 대기열을 같이 본다. CPU bound는 runnable 작업이 core를 계속 요구하는 상태이고, load average는 runnable task뿐 아니라 uninterruptible I/O 대기까지 섞일 수 있으므로 CPU 사용률과 함께 해석해야 한다.

핵심 모델

  • 판단 기준: CPU 포화와 대기열을 같이 본다.
  • CPU bound는 runnable 작업이 core를 계속 요구해 계산 대기열이 길어지는 상태다.
  • Load average는 CPU 사용률과 같지 않고, runnable task와 uninterruptible I/O 대기를 함께 반영할 수 있다.
  • CPU가 높을 때는 host 평균보다 process CPU, thread별 CPU, Java stack을 연결해야 한다.
  • CPU bound 작업에 thread pool을 더하면 처리량보다 context switch와 tail latency가 먼저 늘 수 있다.
  • Container에서는 host core 수가 아니라 CPU quota와 JVM이 인식한 processor 수를 함께 본다.

문서별 핵심 구분

  • CPU bound는 runnable 작업이 core를 계속 요구하는 상태다.
  • load average는 CPU 대기뿐 아니라 일부 I/O 대기도 포함한다.
  • hot thread와 전체 CPU 평균을 함께 봐야 한다.

원리

CPU bound는 계산할 일이 많아 CPU core가 계속 바쁜 상태다. 이때 runnable thread가 core 수보다 많고 CPU 사용률이 높으며, 특정 Java thread가 CPU를 많이 쓰는 경우가 많다.

Load average는 CPU 대기열만 뜻하지 않는다. Linux에서는 uninterruptible I/O 대기 상태도 load에 반영될 수 있으므로, load가 높을 때는 %us/%sy/%wa, runnable thread, disk 지표를 같이 봐야 한다.

Java 서버에서는 JSON 직렬화, 암호화, 압축, 정렬/집계, busy loop, GC work, JIT compilation이 CPU를 올릴 수 있다. Thread dump만 보면 CPU 사용량은 보이지 않고, top -H만 보면 Java stack은 보이지 않으므로 둘을 연결해야 한다.

따라서 CPU 장애는 host 평균 CPU와 process/thread CPU, JVM thread stack, GC/JFR event를 같은 시각에 맞춰야 원인을 좁힐 수 있다.

Java/Spring 연결

  • CPU를 많이 쓰는 LWP id는 16진수로 바꿔 thread dump의 nid와 연결한다.
  • Actuator process.cpu.usage와 host CPU 사용률은 기준이 다를 수 있어 container quota와 함께 본다.
  • GC나 JIT가 CPU를 쓰는지, application handler가 CPU를 쓰는지 JFR과 thread dump로 나눈다.
  • CPU bound endpoint는 thread pool을 늘려도 처리량이 늘지 않고 context switch만 늘 수 있다.
  • Rate limit이나 비싼 API 격리는 CPU 포화 시 정상 API를 보호하는 완화책이 될 수 있다.

코드와 설정 예시

date -Is
uptime
top -H -p <pid>
jcmd <pid> Thread.print
vmstat 1 5
ps -L -p <pid> -o tid,stat,pcpu,comm

예시는 load, thread별 CPU, JVM stack, run queue를 같은 시각에 묶는 흐름이다. top -H에서 CPU가 높은 thread를 찾고 thread dump의 nid와 연결해야 Java 코드 경로까지 내려갈 수 있다.

관찰 명령

uptime
top -H -p <pid>
vmstat 1 5
ps -L -p <pid> -o tid,stat,pcpu,comm

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

지표 해석

신호먼저 의심할 것다음 확인
CPU 높고 %wa 낮음CPU bound, busy codetop -H, thread dump
load 높고 CPU 낮음I/O wait 또는 blocked taskvmstat, iostat
특정 LWP만 높음hot Java threadLWP id nid
GC CPU 동반allocation/GC workGC log, JFR

CPU 분석은 CPU 사용률, hot thread, load, %wa, GC CPU의 순서를 같이 본다. load만 높고 CPU가 낮으면 계산 병목이 아니라 I/O 대기나 quota 문제일 수 있다.

장애 상황에서 보는 순서

  1. uptime과 CPU 사용률을 보고 load와 CPU 포화를 분리한다.
  2. top -H -p <pid>로 CPU를 쓰는 thread를 찾는다.
  3. LWP id를 thread dump nid와 맞춰 Java stack을 확인한다.
  4. GC/JIT/application 코드 중 CPU 주체를 JFR이나 GC log로 분리한다.
  5. Thread pool 증설보다 CPU heavy 작업 격리, 알고리즘 개선, rate limit을 먼저 검토한다.

이 순서는 CPU 문제를 load 문제와 분리하기 위한 루틴이다. CPU 사용률, run queue, thread별 CPU, Java stack 중 최소 두 가지가 같은 방향을 가리킬 때 CPU bound로 판단한다.

실전 팁

  • Load average를 볼 때 core 수와 %wa를 같이 본다.
  • CPU가 높은 thread는 반드시 Java stack과 연결한다.
  • CPU bound 작업에는 thread 수 증설보다 작업량 감소나 격리가 더 효과적일 수 있다.
  • Container에서는 host CPU가 아니라 quota 기준 포화를 같이 본다.
  • 장애 회고에는 hot thread stack과 CPU 지표 시각을 함께 남긴다.

주니어 팁

  • Load average가 높다고 바로 CPU 문제라고 단정하지 않는다.
  • top -H의 thread id와 thread dump의 nid를 연결하는 연습을 한다.
  • CPU가 높을수록 thread pool을 늘리는 것이 항상 답은 아니다.

시니어 팁

  • CPU heavy endpoint는 별도 executor, rate limit, batch offload 기준을 둔다.
  • Container CPU quota와 JVM ActiveProcessorCount 인식 차이를 운영 표준에 포함한다.
  • CPU 포화 알림은 process CPU, load, run queue, p99 latency를 묶어 본다.

Guru급 팁

  • CPU heavy endpoint는 thread pool 격리만으로 충분하지 않다. request budget, rate limit, cache, batch offload까지 같이 설계한다.
  • ActiveProcessorCount와 container quota가 맞지 않으면 ForkJoinPool, GC thread, Tomcat tuning 판단이 모두 흔들릴 수 있다.
  • CPU flame graph나 JFR method sample은 hot path를 줄인 뒤 다시 떠야 한다. 최적화 후 병목이 GC나 lock으로 이동했는지 확인한다.

위험 신호!

  • Load average를 CPU 사용률과 같은 의미로 해석한다.
  • top 평균 CPU만 보고 hot thread를 찾지 않는다.
  • CPU bound 상황에서 thread pool만 늘려 context switch를 키운다.
  • GC CPU와 application CPU를 구분하지 않는다.
  • Container CPU quota를 보지 않고 host core 기준으로 판단한다.

확인 질문

확인 질문

  • Load average가 높을 때 CPU bound라고 바로 단정하면 안 되는 이유는 무엇인가?
    • Load average에는 runnable task뿐 아니라 uninterruptible I/O 대기도 섞일 수 있기 때문이다.
  • CPU를 많이 쓰는 Java 코드를 찾으려면 어떤 연결이 필요한가?
    • top -H의 LWP id를 thread dump의 nid와 연결해야 한다.
  • CPU bound 상황에서 thread pool 증설이 위험할 수 있는 이유는 무엇인가?
    • CPU core가 이미 포화된 상태에서는 동시성만 늘어 context switch와 대기만 증가할 수 있기 때문이다.

참고 문서