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, vmstatst 컬럼을 본다.

# 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과 유사한 증상을 만들므로 둘을 구분해서 봐야 한다.