CPU steal time이란 무엇인가
클라우드 VM에서 애플리케이션은 멀쩡한데 응답이 느려질 때가 있다. CPU 사용률은 낮은데 지연이 튀는 전형적인 증상의 원인 중 하나가 steal time이다. steal time은 게스트 VM(vCPU)이 실행할 준비가 되어 있는데도 하이퍼바이저가 물리 CPU를 다른 게스트에게 할당해서 실제로 대기한 시간의 비율이다.
즉 게스트 커널 입장에서는 "일할 준비가 됐지만 CPU를 뺏겼다"는 의미다. 이 시간은 게스트 내부의 user/system 사용률에는 잡히지 않으므로, VM 안에서만 보면 원인을 놓치기 쉽다.
왜 문제가 되는가
steal time이 높다는 것은 물리 호스트가 오버커밋(overcommit)되어 vCPU 수요가 물리 코어 공급을 초과했다는 신호다. 공유형/버스터블 인스턴스(예: 크레딧 소진 상태의 소형 인스턴스, 시끄러운 이웃 문제)에서 흔히 발생한다.
- 초당 처리량(TPS)은 떨어지는데 앱 프로파일러에는 병목이 안 잡힌다.
- GC pause, 락 경합, 타임아웃이 불규칙하게 증가한다.
- 스케줄러 지연으로 p99 레이턴시가 튄다.
경험칙으로 steal이 수 % 이하로 간헐적이면 무시할 만하지만, 지속적으로 10%를 넘으면 성능 저하가 체감되고, 20% 이상이면 심각한 상태로 본다.
어떻게 측정하는가
가장 빠른 확인은 top이나 vmstat의 steal 컬럼이다. top의 CPU 라인에서 st, vmstat의 st 컬럼을 본다.
# 1초 간격으로 steal(st) 컬럼 관찰
vmstat 1 5
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 2 0 0 512340 18240 204800 0 0 0 4 980 1520 25 8 52 1 14
# mpstat로 코어별 %steal 분해 (sysstat 패키지)
mpstat -P ALL 1 3
# /proc에서 raw steal 카운터(누적 tick, 8번째 필드) 직접 확인
awk '/^cpu / {print "steal ticks:", $9}' /proc/stat
수치가 튀는 시점을 잡으려면 순간값보다 시계열 수집이 낫다. 아래는 /proc/stat을 파싱해 steal 비율을 계산하는 예시다.
import time
def read_cpu():
with open("/proc/stat") as f:
parts = f.readline().split()[1:] # user nice system idle iowait irq softirq steal ...
vals = list(map(int, parts))
total = sum(vals)
steal = vals[7]
return total, steal
t0, s0 = read_cpu()
time.sleep(1)
t1, s1 = read_cpu()
dt, ds = t1 - t0, s1 - s0
print(f"steal: {100 * ds / dt:.1f}%")
진단 흐름: 앱인가 인프라인가
steal이 확인되면 원인이 VM 외부(하이퍼바이저)에 있다는 뜻이므로, 게스트 내부 튜닝으로는 해결되지 않는다. 아래 표로 증상을 구분한다.
| 증상 | steal 낮음 | steal 높음 |
|---|---|---|
| 지연 원인 | 앱/DB/IO 병목 | 호스트 CPU 경합 |
| us+sy 사용률 | 대체로 높음 | 낮은데도 느림 |
| 해결 위치 | 코드/쿼리/인덱스 | 인스턴스/배치 변경 |
어떻게 해결하는가
근본 해결은 CPU 경합에서 벗어나는 것이다.
- 전용(Dedicated) 인스턴스로 전환: 물리 CPU를 공유하지 않는 타입으로 이동한다.
- 버스터블 크레딧 모니터링: 크레딧 소진 시 성능이 급락하므로 크레딧 잔량과 baseline을 초과하는 CPU 패턴이면 상위 타입으로 올린다.
- 인스턴스 이전(마이그레이션): 시끄러운 이웃이 원인이면 stop/start로 다른 호스트에 재배치되며 완화되는 경우가 많다.
- 수평 확장: 부하를 분산해 개별 VM의 vCPU 수요를 낮춘다.
운영에서는 알람으로 자동 감지하는 것이 핵심이다. 아래는 Prometheus(node_exporter) 기준 알람 규칙이다.
groups:
- name: cpu-steal
rules:
- alert: HighCPUStealTime
expr: avg by (instance) (rate(node_cpu_seconds_total{mode="steal"}[5m])) * 100 > 10
for: 10m
labels:
severity: warning
annotations:
summary: "높은 CPU steal time ({{ $value | printf \"%.1f\" }}%)"
description: "{{ $labels.instance }} 호스트 CPU 경합 의심. 인스턴스 타입/배치 검토."
주의점
몇 가지 함정이 있다. 첫째, steal은 비율이므로 vCPU 하나가 100% 바쁜 상태에서만 의미가 크다. 코어가 대부분 idle이면 낮은 steal은 무해하다. 둘째, 게스트 내부 값은 하이퍼바이저 종류와 클록소스 구현에 따라 부정확할 수 있으니 클라우드 제공자의 호스트 측 메트릭(CPU credit, host CPU)과 교차 검증한다.
셋째, 오토스케일 판단을 게스트 CPU 사용률만으로 하면 steal이 높은 상황을 놓친다. 스케일 지표에 steal이나 레이턴시를 함께 넣어야 한다. 마지막으로, 컨테이너 환경에서는 cgroup CPU throttling(nr_throttled)이 steal과 유사한 증상을 만들므로 둘을 구분해서 봐야 한다.