리눅스 장애 대응 · 부하와 CPU · 이론
부하 평균은 CPU 사용률이 아니다
한 줄 요약
리눅스의 부하 평균은 CPU 대기만 세지 않는다. 디스크나 NFS 응답을 기다리는 D 상태 태스크도 함께 센다. 그래서 CPU 가 놀아도 로드가 24 가 될 수 있고, 그때 범인은 스토리지다.
왜 이게 필요했나
새벽 알람. 8코어 서버의 부하 평균이 24.31, 22.08, 14.95 인데 top 의 CPU 유휴는 60% 가 넘는다. 여기서 두 갈래로 갈린다 - "모니터링이 잘못됐다" 또는 "CPU 를 3배 늘려야 한다". 둘 다 틀렸다.
단위부터 다르다. 사용률은 비율(0~100%)이고 로드는 개수(태스크 수) 다. 그리고 "로드 = CPU 대기열 길이"는 다른 유닉스에서는 맞지만 리눅스에서는 틀리다.
어떻게 동작하나
커널은 5초에 한 번 각 CPU 런큐를 샘플링한다.
nr_active = 실행 중이거나 실행 대기(R) + 중단 불가 대기(D)리눅스는 1993년에 TASK_UNINTERRUPTIBLE(D 상태)을 이 계산에 더했다. 의도는 CPU 수요만이 아니라 시스템 자원 전반에 대한 수요를 재겠다는 것이었다. D 상태는 시그널로도 못 깨우는 커널 내부 대기다 - 블록 I/O 완료, 페이지 캐시 잠금, 하드 마운트 NFS 응답 대기, 일부 커널 뮤텍스. 이 목록에 CPU 는 없다.
그리고 이 값은 산술평균이 아니라 지수감쇠 평균이다. 계단처럼 부하가 오르면 1분 평균은 1분 뒤에 약 63% 만 반영되고 99% 까지 약 5분이 걸린다. 여기서 두 가지가 따라온다.
- "1분 > 5분 > 15분" 이면 오르는 중, 반대면 회복 중
- 장애가 끝난 직후 15분 평균만 높은 것은 정상이다. 그 숫자로 알람을 걸면 복구 후 20분간 계속 울린다
진단 순서는 이렇다.
cat /proc/loadavg # 네 번째 필드 '실행가능/전체'를 함께 본다ps -eo state --no-headers | sort | uniq -c # R 과 D 를 센다vmstat 1 5 # b 열이 D 상태 개수, wa 가 I/O 대기 비율iostat -x 1 3 # await 와 큐 깊이로 판단 (%util 100% 는 포화가 아닐 수 있다)로드가 24 인데 R 이 2 라면 나머지 22 는 D 다. 그러면 답은 CPU 가 아니라 스토리지 쪽에 있다.
"코어 수로 나눠라"가 무너지는 지점들도 알아야 한다.
1. D 상태 오염 - 위에서 본 그대로
2. 평균의 지연 - 30초짜리 스파이크는 1분 평균에 절반도 안 드러난다
3. SMT - nproc 16 이 물리 코어 8개일 수 있다
4. cgroup 스로틀링 - 스로틀당한 태스크는 런큐에서 아예 빠지므로 로드에 잡히지 않는다
5. 귀속 정보 부재 - 전역 스칼라 하나라, 알람으로 할 수 있는 일이 "들어가서 봐라" 뿐이다
현장에서 만나는 모습
컨테이너의 조용한 정지. cgroup 에 CPU 상한이 있으면 커널은 주기(기본 100ms)마다 쿼터를 배분하고, 주기 안에서 소진하면 남은 시간 동안 그룹 전체를 런큐에서 빼 버린다. limit 1코어에 스레드 8개면 100ms 쿼터를 12.5ms 만에 소진하고 나머지 87.5ms 는 완전히 멈춘다. 그런데 평균 사용률은 12.5% 근처로 보이고, 로드에도 안 잡히고, steal 에도 안 나온다. cpu.stat 의 nr_throttled / nr_periods 비율만이 이 사실을 알려 준다. 5% 를 넘으면 조사, 10% 를 넘으면 조치 대상이다.
steal time. top 의 st 는 "내가 쓰고 싶었는데 하이퍼바이저가 다른 게스트에게 준 시간"이다. 애플리케이션을 아무리 최적화해도 이 수치는 내려가지 않는다. 1% 미만은 정상, 5~10% 지속이면 조치, 10% 초과면 그 호스트에서는 어떤 튜닝도 무효다. 그리고 게스트 안에서 reboot 해도 물리 호스트는 바뀌지 않는다 - 클라우드 API 의 stop/start 라야 재배치된다.
PSI 가 답하는 질문. 로드는 "몇 개가 기다리는가"만 말하고 "얼마나 오래 멈춰 있었는가"는 말하지 않는다. /proc/pressure/{cpu,io,memory} 의 some 은 최소 한 개가 멈춘 시간 비율, full 은 전부가 동시에 멈춘 비율이다. io 의 full avg10=61 은 최근 10초 중 6초 이상 시스템 전체가 I/O 때문에 정지했다는 뜻이고, 이건 손실된 처리량으로 곧장 번역된다.
다음 실습에서 할 것
CPU 를 태우는 프로세스를 띄워 부하 평균이 실제로 오르는 것을 관찰하고, R 상태와 누적 CPU 시간을 /proc 에서 직접 읽는다. PSI 와 cgroup 스로틀링 카운터를 확인하되, 없는 환경이면 "없다"고 정확히 기록한다. 마지막에는 코어 수로 정규화해 부하를 판정하는 스크립트를 만든다.