Load Average가 무엇인지, 어떤 방식을 통해 계산되는지 정리한 문서입니다.
1편에서는 Load Average가 무엇이고 어떻게 계산되는지를 다뤘습니다.
핵심은 load가 실행 대기(R)와 I/O 대기(D)를 한 숫자에 섞어 담는다는 것이었습니다.
그렇다면 실제로 load가 치솟았을 때, 그 원인이 CPU 경합인지 디스크 I/O 병목인지 어떻게 가려낼 수 있을까요?
이 글에서는 CPU-bound와 I/O-bound 상황을 직접 만들어 load average가 어떻게 달라지는지 관찰하고, vmstat으로 부하의 정체를 진단하는 방법을 실습으로 확인합니다.
| 항목 | 값 |
|---|---|
| 호스트 | dev VM (전용) |
| vCPU | 4 → load 4 = 포화 지점 (load ÷ 4 로 해석) |
| OS / 커널 | Ubuntu 24.04.4 LTS / 6.8.0-124-generic |
| idle load | 0.00, 0.00, 0.00 |
| 실습 도구 | stress-ng, vmstat, pidstat, fio |
# 1. idle 기준선 (5초)
vmstat 1 5

# 2. CPU 부하 - 코어 수(4)만큼, 3분간 백그라운드
stress-ng --cpu 4 --timeout 180s &
# 3. 부하 도는 중 관찰
vmstat 1 # r·us·id·b·wa 확인, 충분히 봤으면 Ctrl+C
uptime # 1분쯤 / 3분쯤 두 번 찍기


stress-ng로 코어 수(4개)만큼 CPU 부하를 걸자, vmstat의 r(run queue)이 즉시 4로 올라갔습니다.
실행 가능한(R) 태스크 4개가 4개의 코어를 꽉 채운 것입니다.
동시에 us(사용자 CPU)는 100%, id(유휴)는 0으로 떨어졌지만, b(블록된 태스크)와 wa(iowait)는 계속 0이었습니다.
즉 이 부하는 순수하게 CPU 경합이며 I/O 대기는 전혀 없습니다.
한편 load average는 즉시 4가 되지 않고 서서히 올라갔습니다.
부하 시작 약 1분 뒤 1분 load는 2.76(≈목표 4의 63%), 약 3분 뒤에는 3.83(≈95%) 까지 상승했습니다.
이는 1편에서 본 EWMA의 계단 응답과 정확히 일치합니다.
load average가 실시간 값이 아니라 시정수를 가진 지수 감쇠 추정치임을 실제로 확인할 수 있습니다.
# 1. 디스크 여유 확인 (파일 만들 거라)
df -h /var/tmp
free -h

# 2. 테스트 디렉토리
mkdir -p /var/tmp/fiotest
# 3. I/O 부하: 캐시 우회(direct) + 동기 읽기, 8병렬, 3분
# -> 각 워커가 디스크 대기로 D 상태에 빠짐
fio --name=iobound --directory=/var/tmp/fiotest \
--rw=randread --bs=4k --size=256m --numjobs=8 \
--direct=1 --ioengine=sync --time_based --runtime=180 \
--group_reporting > /tmp/fio.out 2>&1 &
# 4. 부하 도는 중 관찰
vmstat 1 # b·wa 확인! (Ctrl+C로 멈춤)
uptime # load 오르는데 CPU는 노는 거 확인


위 캡쳐에서 bo(블록 쓰기)가 크게 나온 이유는 fio가 테스트 파일을 먼저 생성하는 단계이기 때문.
그래서 bi(읽기)보다 bo(쓰기)가 크게 나왔다.
fio로 8개의 동기 디스크 I/O 워커를 띄우자, CPU-bound와는 정반대 그림이 나왔습니다.
vmstat의 b(블록된 태스크)가 1~3으로 계속 0을 넘었고, wa(iowait)는 25~49%까지 올랐습니다.
반면 us(사용자 CPU)는 0~3%에 불과했고 id(유휴)는 50~75%로, CPU는 대부분 I/O 완료를 기다리며 놀고 있었습니다.
그런데도 load average는 5.55까지 치솟았습니다.
코어가 4개인 시스템에서 load 5.55는 겉보기엔 심각한 과부하지만, CPU는 거의 쉬고 있습니다.
이 부하의 정체는 CPU 경합이 아니라 디스크 I/O 대기(D 상태) 입니다.
1편에서 말한 "load는 CPU 사용률이 아니다" 가 이 한 화면에 그대로 드러납니다.
두 실험을 한 표로 모으면, load라는 하나의 숫자가 실제로 무엇 때문에 올랐는지를 vmstat 몇 개의 칼럼으로 가려낼 수 있습니다.
| 관찰 항목 | CPU-bound | I/O-bound |
|---|---|---|
load average | 높음 | 높음 |
r (run queue) | 높음 (≈코어 수) | 낮음 |
b (blocked) | 0 | 높음 |
wa (iowait) | 0 | 높음 |
us / id | us↑ / id≈0 | us≈0 / id↑ |
핵심은 이것입니다.
load average는 R(실행 대기)과 D(I/O 대기)를 한 숫자에 섞어 담기 때문에 그 값만으로는 원인을 알 수 없지만, vmstat의 r·b·wa 세 칼럼이 그 둘을 분리해 줍니다.
진단 순서를 정리하자면:

부하의 성격(CPU인지 I/O인지)을 알았다면, 다음은 어떤 프로세스가 원인인지입니다.
pidstat(sysstat 패키지)이 프로세스별로 나눠 보여줍니다.
# CPU-bound일 때 — CPU 먹는 프로세스
pidstat -u 1 # %CPU 높은 놈이 범인
# I/O-bound일 때 — 디스크 때리는 프로세스
pidstat -d 1 # kB_rd/s, kB_wr/s, iodelay 높은 놈이 범인
# 지금 D 상태로 멈춰 있는 프로세스 바로 보기
ps -eo s,pid,comm,wchan | awk '$1=="D"' # 상태 D + 무엇을 기다리는지(wchan)


지금까지 vmstat로 부하의 정체를 가려냈지만, load average 자체에는 구조적인 한계가 남아 있습니다.
바로 이 한계를 메우기 위해 리눅스 커널에는 PSI(Pressure Stall Information) 라는 지표가 있습니다.
PSI는 "대기 태스크가 몇 개인가"가 아니라, 자원(CPU·메모리·I/O) 부족으로 작업이 정지한 시간의 비율(%) 을 직접 측정합니다.
즉 load average가 남긴 모호함(무엇 때문에, 얼마나 심각하게) 을 PSI가 정량적으로 보완합니다.
실제로 이 PSI 지표를 수집·시각화하는 관측 파이프라인을 직접 구축한 과정은 아래 글에 정리해 두었습니다.
두 편에 걸쳐 Load Average를 정리했습니다.
Load Average는 시스템에 이상이 있다는 신호를 가장 빠르게 알려주는 지표이지만, 그 신호가 무엇 때문인지까지는 말해주지 않습니다.
uptime으로 이상을 감지하고, vmstat로 정체를 좁히고, pidstat으로 범인을 특정하고, 더 정밀한 관측이 필요하면 PSI로 넘어가는 흐름.
이것이 load라는 익숙한 숫자를 제대로 읽는 방법이라고 생각합니다.
감사합니다.
정택준
Team: https://nangman.cloud/ko
E-mail: taekjunnnn@nangman.cloud