LabHub
배우기 러닝패스 코스

CPU・メモリリークの見極め

load averageはCPU使用率ではない

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

리눅스의 load average 는 실행 가능한 프로세스 + 끊기지 않는 대기(D 상태) 프로세스의 개수다. 디스크가 느려도 올라간다. CPU 사용률과는 다른 것을 잰다.

概念マップ: 실행 가능한 프로세스 + 끊기지 않는 대기(D 상태) 프로세스・CPU 지표가 아니라 시스템 수요 지표・top 의 %CPU 가 100 을 넘는 것은 버그가 아니다.・코어 하나 기준

왜 이게 필요했나

"load 가 20이다, CPU 를 늘리자" 는 흔한 오진이다. 코어가 8개인 기계에서 load 20 은 CPU 가 모자란 신호일 수도 있고, NFS 가 멎어서 20개 프로세스가 I/O 대기에 묶인 신호일 수도 있다. 후자에 CPU 를 늘리면 아무 일도 일어나지 않는다.

다른 유닉스는 load 에 CPU 대기만 센다. 리눅스만 D 상태(uninterruptible sleep)를 함께 센다. 이건 1993년에 "시스템이 바쁘다"를 더 잘 나타내려고 넣은 변경이고, 그 뒤로 리눅스의 load 는 CPU 지표가 아니라 시스템 수요 지표가 됐다.

어떻게 동작하나

숫자 세 개는 1분·5분·15분 지수이동평균이다. 방향이 정보다.

cat /proc/loadavg
# 8.24 4.10 2.05 3/512 12345
#  └1분 └5분 └15분 └실행중/전체 스레드 └마지막 PID

1분 > 15분이면 지금 나빠지는 중, 반대면 회복 중이다.

무엇이 load 를 올리는지 보려면 상태별로 센다.

ps -eo state,comm --no-headers | awk '{print $1}' | sort | uniq -c | sort -rn
# R = 실행 가능, D = 끊기지 않는 대기(대개 I/O), S = 잠듦

D 가 많으면 CPU 문제가 아니다.

CPU 자체를 보려면 /proc/stat 을 두 번 읽어 차이를 낸다.

awk '/^cpu /{print $2+$3+$4+$6+$7+$8, $5}' /proc/stat   # (busy, idle)

두 시점의 차이로 사용률을 낸다. top 이 하는 일이 정확히 이것이다.

흔한 착각

top 의 %CPU 가 100 을 넘는 것은 버그가 아니다. 기본 모드에서 %CPU 는 코어 하나 기준이다. 4스레드가 각각 꽉 돌면 400% 다. top 에서 Shift+I 를 누르면 전체 코어 기준(Irix 모드 해제)으로 바뀐다.

컨테이너 안의 top 은 호스트 전체를 본다. /proc 이 호스트 것이라 코어 수도 load 도 호스트 값이다. 컨테이너에 실제로 할당된 몫은 cgroup 에서 봐야 한다.

cat /sys/fs/cgroup/cpu.max        # "쿼터 주기" — max 면 제한 없음
cat /sys/fs/cgroup/cpu.stat       # nr_throttled, throttled_usec

nr_throttled 가 늘어나면 CPU 가 모자란 게 아니라 제한에 걸린 것이다. 이 둘은 대처가 완전히 다르다.

부하 평균은 CPU 지표가 아니다

리눅스의 load average 는 실행 중이거나 실행을 기다리는 프로세스 수 인데, 여기에 D 상태(중단 없는 대기, 대개 디스크 I/O)가 포함됩니다. 다른 유닉스와 다른 점이고, 오해가 여기서 나옵니다.

$ uptime
 load average: 8.42, 6.10, 4.33     ← 코어가 4개인데 8?
$ vmstat 1 3
 r  b   ...     ← r 은 실행 대기, b 는 I/O 대기
 1  7   ...     ← 실행 대기는 1뿐, 나머지 7은 디스크를 기다린다

r 이 작고 b 가 크면 CPU 를 늘려도 아무것도 나아지지 않습니다. 디스크가 범인입니다. load average 만 보고 인스턴스를 키우는 것이 이 지표의 가장 비싼 오해 입니다.

사용률이 같아도 원인이 다르다

top 의 한 줄에서 어느 칸이 큰지가 조사 방향을 정합니다.

큰 칸 먼저 볼 곳
us (user) 애플리케이션 코드 프로파일러, 뜨거운 함수
sy (system) 커널 — 시스템 호출이 많다 strace -c, 파일·소켓 사용 패턴
wa (iowait) 디스크를 기다린다 iostat -x, await
si (softirq) 네트워크 인터럽트 /proc/softirqs, RSS/RPS 설정
st (steal) 하이퍼바이저가 뺏어 갔다 클라우드 인스턴스 등급, 이웃 문제

st 가 5% 를 넘으면 내 문제가 아닙니다. 버스트 가능 인스턴스(t 계열)에서 크레딧이 떨어졌거나, 물리 호스트가 과밀합니다. 코드를 아무리 고쳐도 안 됩니다.

무엇이 CPU 를 쓰는지 찾는 순서

  1. 누가top -H -p <pid> 로 스레드까지 내려갑니다. 프로세스 전체가 아니라 특정 스레드 하나만 도는 경우가 많습니다(GC 스레드, 폴링 루프).
  2. 어디서perf top -p <pid> 로 뜨거운 심볼을 봅니다. 심볼이 [unknown] 투성이면 디버그 심볼이 없는 것이니 언어별 프로파일러를 씁니다.
  3. — 그 함수가 왜 그렇게 자주 불리는지는 코드에서 봅니다. 대개 O(n²) 이거나, 캐시가 안 먹거나, 로그를 동기로 쓰고 있습니다.

perf 가 없는 컨테이너에서는 /proc/<pid>/stack 이나 언어 런타임의 스레드 덤프 (jstack, py-spy dump)로도 절반은 알 수 있습니다.

실무에서 진짜 중요한 것

쿠버네티스에서 CPU limit 을 걸면 cgroup 쿼터가 걸린다. 요청이 몰릴 때 스로틀링이 발생해 p99 지연이 튀는데, CPU 사용률 그래프는 limit 근처에서 평평해 보여서 "여유 있다" 로 오독하기 쉽다. 스로틀링은 사용률이 아니라 cpu.stat 으로 확인한다.