CPU 활용 함정: 평균 메트릭이 성능 파괴자를 숨기는 이유
많은 엔지니어에게 시스템이 느려질 때 가장 먼저 하는 일은 CPU 활용 그래프를 확인하는 것입니다. 그래프가 40% 혹은 60% 수준에 머물러 있다면, 즉각적인 결론은 보통 CPU가 병목이 아니라는 것입니다. 하지만 평균 활용도에 의존하는 것은 특히 Kubernetes와 같은 컨테이너 환경에서는 위험한 함정이 될 수 있습니다.
프로덕션에서 Go 함수가 context deadline exceeded 오류로 실패하기 시작하지만 대시보드에는 CPU가 정상적인 수준으로 표시될 때, 이는 전체 용량 부족 때문이 아니라 Linux 커널이 자원 제한을 적용하는 미묘한 방식 때문일 가능성이 높습니다.
평균의 오류
평균 CPU 활용도는 비용 메트릭이며, 성능 메트릭이 아닙니다. 이는 “우리가 사용량보다 더 많이 비용을 지불하고 있는가?”라는 질문에 답하지만, “내 애플리케이션이 지금 필요한 CPU 시간을 받고 있는가?”라는 질문에는 답하지 못합니다.
지연 민감형 워크로드에서는 활용도와 대기 시간 사이의 관계가 비선형입니다. M/M/1 대기열 모델에 따르면, 활용도가 80%에서 81%로 상승하는 것이 10%에서 11%로 상승하는 것보다 대기 시간을 훨씬 크게 증가시킵니다.
| CPU 활용도 | 10 ms 요청에 대한 대기 시간 |
|---|---|
| 10% | ~11 ms |
| 80% | ~50 ms |
| 95% | ~200 ms |
활용도가 상승함에 따라 “여유 공간”이 사라지고, CPU가 100%에 도달하기 훨씬 전에 지연 시간이 급증합니다. 하지만 이것만으로는 가장 교묘한 문제인 “보이지 않는” 스로틀링을 설명하지 못합니다.
추상화의 누수: CFS 스로틀링
Docker와 Kubernetes에서는 CPU 제한이 Completely Fair Scheduler(CFS) 할당량 시스템을 통해 적용됩니다. 2000m(2 vCPU) 제한을 설정하면, 커널은 컨테이너를 두 코어에 엄격히 제한하지 않고, 대신 스케줄링 기간당 시간 예산을 부여합니다(보통 100 ms).
여기서 추상화가 새는 부분이 있습니다: 컨테이너는 전체 200 ms 예산을 호스트 노드의 모든 사용 가능한 코어에 걸쳐 사용할 수 있습니다. 호스트에 4개의 코어가 있다면, 자원 집약적인 요청이 네 코어 모두에 걸쳐 급증하면서 실제 시간 50 ms 만에 200 ms 예산을 소진할 수 있습니다.
예산이 소진되면 컨테이너는 스로틀링됩니다. 다음 100 ms 기간이 시작될 때까지 완전히 정지됩니다. 1분 동안 CPU를 평균하는 모니터링 도구에서는 사용량이 낮게 보이지만, 51 ms에 도착한 요청에게는 시스템이 다음 49 ms 동안 완전히 응답하지 않습니다.
이로 인해 p99 지연 시간이 급증하는 반면 평균 CPU 활용도는 현혹적으로 낮게 유지되는 패턴이 발생합니다. 이 현상은 Indeed Engineering이 유명하게 문서화했으며, 할당된 코어보다 사용량이 훨씬 낮은 애플리케이션도 대부분의 100 ms 기간에서 스로틀링을 경험했습니다.
기아 감지 및 완화 방법
표준 대시보드가 이러한 급증을 숨기기 때문에, 진실을 찾기 위해 커널 수준 메트릭을 확인해야 합니다.
1. Cgroup 통계 확인
스로틀링 여부를 가장 직접적으로 확인하는 방법은 /sys/fs/cgroup/cpu.stat를 확인하는 것입니다. 다음 항목을 찾아보세요:
nr_throttled: 컨테이너가 스로틀링된 횟수.throttled_usec: 컨테이너가 정지된 총 시간.
이 카운터가 증가하고 있다면, 현재 자원 제한이 급증 패턴에 비해 너무 빡빡한 것입니다.
2. Pressure Stall Information (PSI) 모니터링
커널 PSI(cpu.pressure)는 포화 신호를 제공합니다. 이는 작업이 실행 가능하지만 실행되지 못한 시간 비율을 보고하여, CFS 할당량 이하일 때도 경쟁 상황을 포착합니다.
3. 스틸 타임 감시
가상화 환경에서는 "스틸 타임"(top의 %st)을 확인하세요. 이는 하이퍼바이저가 다른 테넌트를 위해 물리 CPU를 VM에서 빼앗을 때 발생하며, 내부 제한과 무관하게 코드가 정지하게 됩니다.
4. 애플리케이션 수준 감지
가장 견고한 해결책은 애플리케이션 자체에서 기아를 감지하는 것입니다. Redpanda(“reactor stalls” 방식)와 CockroachDB와 같은 고성능 시스템은 goroutine이 실행 가능 상태가 된 시점과 실제 실행 시점 사이의 시간을 모니터링합니다. 이 지연 시간이 임계값(예: 1 ms)을 초과하면, 애플리케이션은 백그라운드 작업을 줄여 전면 요청을 우선시하도록 반응할 수 있습니다.
그래프를 넘어선 접근
개발자와 IT/운영 부서 사이에는 종종 긴장이 존재합니다. 개발자가 지연 스파이크를 해결하기 위해 더 많은 CPU를 요청하면, 관리자는 40% 활용도 그래프를 근거로 “효율성”이나 엄격한 제한을 요구하는 컴플라이언스 가이드에 따라 요청을 거절할 수 있습니다.
이 교착 상태를 깨기 위해서는 대화를 활용도에서 포화도로 전환해야 합니다. 프로덕션 시스템의 목표는 CPU 사용량을 최대화하는 것이 아니라 애플리케이션이 원활히 동작하도록 하는 것입니다. 스로틀링 메트릭과 PSI를 평균 활용도와 함께 공개함으로써, 팀은 오해를 일으키는 평균이 아니라 실제 성능을 기반으로 결정을 내릴 수 있습니다.
한 커뮤니티 기여자가 언급했듯이, 문제는 메트릭 자체가 아니라 해석에 있습니다: "토끼굴은 끝없이 이어지고, 메트릭은 올바르게 해석하지 못하면 당신을 속입니다."