load average 4.0은 높은 걸까: 리눅스 부하 평균이 세는 것과 1·5·15분의 의미
리눅스 load average는 CPU 사용률이 아니라 실행 중·대기 중(R)과 끊을 수 없는 대기(D) 작업 수의 지수 평균입니다. 커널 소스와 man 페이지로 계산 방식, CPU 수로 읽는 법, PSI와의 차이를 정리했습니다.
uptime이나 top 첫 줄의 load average: 3.95, 2.10, 1.02는 퍼센트가 아닙니다. 리눅스에서 이 세 숫자는 CPU를 쓰고 있거나 CPU를 기다리는 작업, 그리고 끊을 수 없는 대기(D 상태)에 있는 작업을 합친 수를 1분, 5분, 15분 기준으로 평균 낸 값입니다. 같은 4.0이라도 CPU가 몇 개인지, 그 작업들이 무엇을 기다리는지에 따라 뜻이 달라집니다.
표준이 정한 값이 아니다
POSIX에는 load average의 정의가 없습니다. 값을 읽는 C 함수 getloadavg()도 리눅스 man 페이지에 표준이 “BSD”로 적혀 있고, 4.3BSD-Reno에서 처음 나왔습니다. 리눅스와 FreeBSD의 getloadavg(3)는 둘 다 “run queue에 있는 프로세스 수의 평균”이라고 설명하지만, 무엇을 run queue로 셀지는 각 커널이 정합니다. 이 글은 리눅스 커널 기준이며, 다른 운영체제의 셈법은 확인하지 않았습니다.
리눅스가 세는 것: R과 D
proc_loadavg(5)는 /proc/loadavg의 처음 세 필드를 run queue에 있는(state R) 작업과 디스크 I/O를 기다리는(state D) 작업 수의 평균이라고 설명합니다. 커널 소스 kernel/sched/loadavg.c의 주석은 더 직접적으로, 전역 load average가 nr_running + nr_uninterruptible의 지수 감쇠 평균이라고 적고 있습니다.
D 상태의 대표적인 원인은 디스크 대기지만, 커널이 보는 기준은 대기 이유가 아니라 상태 플래그입니다. 작업이 TASK_UNINTERRUPTIBLE로 잠들면 부하에 들어가고, TASK_NOLOAD가 붙은 상태(TASK_IDLE)나 동결된 작업은 빠집니다(kernel/sched/core.c의 block_task()). ps(1)이 D를 “uninterruptible sleep (usually I/O)“라고 ’보통’을 붙여 설명하는 것도 이 때문입니다.
그래서 CPU는 한가한데 load average만 높은 상황이 생깁니다. 응답이 느린 저장 장치를 기다리는 작업이 D 상태로 쌓이면 CPU 사용률이 낮아도 숫자는 올라갑니다. 값이 높을 때는 ps로 스레드까지 표시해(예: ps -eLo pid,lwp,stat,wchan,comm) R과 D 작업이 어느 프로세스에 몰려 있는지, D 작업이 커널의 어디에서 기다리는지 살펴보면 단서를 얻을 수 있습니다. 다만 ps로 센 한 순간의 개수는 커널이 표본을 모아 평균 내는 load average를 그대로 재현하지 않습니다.
1분 값은 최근 1분의 단순 평균이 아니다
커널은 LOAD_FREQ(5초 + 1틱)마다 그 순간의 작업 수를 표본으로 잡아 이전 값과 섞습니다.
새 값 = 이전 값 × e + 현재 작업 수 × (1 − e)
계수 e는 include/linux/sched/loadavg.h에 2048을 1.0으로 하는 고정소수점으로 들어 있습니다. 1분용 EXP_1은 1884, 5분용은 2014, 15분용은 2037이며, 각각 e^(−5초/1분), e^(−5초/5분), e^(−5초/15분)에 2048을 곱한 값입니다.
이 계수로 계산하면, 0이던 시스템에 계속 실행 가능한 작업 4개가 생겼을 때 1분(표본 12번) 뒤의 1분 값은 4 × (1 − (1884/2048)^12) ≈ 2.5입니다. 새 상태의 약 63%만 반영되고, 1분보다 오래된 표본도 작은 비중으로 남습니다. 5분 값과 15분 값은 이보다 천천히 움직이므로, 세 숫자를 나란히 보면 부하가 오르는 중인지 내려가는 중인지 짐작할 수 있습니다. 커널은 정수로 계산하므로 소수점 아래는 이 계산과 조금 다를 수 있습니다.
CPU 수와 함께 읽기
uptime(1)은 load average가 CPU 수로 정규화되지 않는다고 밝힙니다. man 페이지의 예로는 같은 1이 CPU 1개 시스템에서는 줄곧 바빴다는 뜻입니다. CPU 4개 시스템에서는 평균적으로 CPU 용량의 75%, 즉 CPU 3개분이 유휴였다는 뜻입니다. 따라서 R 작업이 대부분인 4.0은 논리 CPU 16개 서버라면 여유가 있고, 2개 서버라면 실행을 기다리는 줄이 생겼다고 볼 수 있습니다. “CPU 수를 넘으면 위험” 같은 기준은 경험칙이며, D 상태가 섞인 리눅스 값에는 그대로 맞지 않습니다.
/proc/loadavg에는 숫자가 두 개 더 있습니다. 다음은 예시 값입니다.
0.52 0.48 0.40 2/613 12345
네 번째 필드에서 / 앞은 지금 실행 가능한 스케줄링 단위(프로세스·스레드) 수, 뒤는 존재하는 전체 수이고, 다섯 번째는 가장 최근에 만들어진 PID입니다. 커널 소스에서 / 앞 값은 nr_running()으로 채우므로 평균이 아닌 순간값이고 D 상태는 들어가지 않습니다.
무엇을 기다리는지 보려면 PSI
커널이 CONFIG_PSI로 빌드됐다면 /proc/pressure/cpu, memory, io에서 자원별로 작업이 멈춰 있던 시간의 비율을 10초·60초·300초 평균으로 볼 수 있습니다. some은 일부 작업이라도 그 자원 때문에 멈춘 시간의 비율, full은 유휴가 아닌 작업 전부가 동시에 멈춘 시간의 비율입니다. 시스템 전체 cpu의 full은 정의되지 않으며, 5.13부터 호환을 위해 0으로 출력됩니다. load average가 작업 수를 섞어 하나로 보여 준다면, PSI는 기다린 시간을 자원별로 나눠 보여 줍니다.
열린 파일 때문에 디스크 공간이 돌아오지 않는 문제는 unlink와 열린 파일에서 다뤘습니다.
참고한 자료
- Linux man-pages 6.19, proc_loadavg(5), getloadavg(3), proc_pid_stat(5)
- procps-ng, uptime(1), ps(1)
- Linux 커널 소스(v7.2 태그에서 확인): kernel/sched/loadavg.c, include/linux/sched/loadavg.h, kernel/sched/core.c, include/linux/sched.h, fs/proc/loadavg.c
- Linux 커널 문서, PSI - Pressure Stall Information
- FreeBSD 15.1, getloadavg(3)