1. Load Average 개념

TaekJun Jeong·6일 전

Linux

목록 보기
4/5

Load Average가 무엇인지, 어떤 방식을 통해 계산되는지 정리한 문서입니다.

1편은 개념을 설명하고 이어서 2편에 실습을 통해 정리합니다.


Load Average란?

리눅스 시스템에서 CPU와 디스크 I/O가 얼마나 바쁜지 나타내는 지표 중 하나입니다.

uptime이나 /proc/loadavg 를 살펴보면 세 개의 숫자가 나옵니다.

각각 최근 1분, 5분, 15분에 대한 값입니다.

흔히 “부하율”로 읽지만, 정확히는 시스템이 처리해야 할 작업이 평균적으로 몇 개나 쌓여 있었는가를 나타내는 수치입니다.

백분율(%)이 아니라 개수(태스크 수) 라는 점이 핵심입니다.

직관적으로 봤을 때, CPU가 1개인 시스템에서

  • load average = 1.0 : 항상 딱 한 개의 작업이 CPU를 꽉 쓰고 있고, 기다리는 작업은 없는 상태
    (포화 직전)
  • load average < 1.0 : CPU에 여유가 있는 상태
  • load average > 1.0 : 처리할 작업이 CPU가 감당할 수 있는 양을 넘어, 누군가는 줄을 서서 기다리는 상태

그래서 load average는 "얼마나 바쁜가(%)"가 아니라 "얼마나 밀려 있는가(대기 행렬의 길이)" 에 가깝습니다.

이 차이가 뒤에 나올 두 가지 오해의 출발점입니다.



오해 1 - Load Average는 CPU 사용률이 아니다

가장 흔한 오해입니다.

Load average를 CPU 사용률과 같다고 생각하면, 앞에서 본 "CPU는 노는데 load는 높은" 상황을 설명할 수 없습니다.

리눅스가 load에 반영하는 작업은 두 종류입니다.

  • R (Runnable) : CPU에서 실행 중이거나, 실행할 준비가 되어 CPU를 기다리는 태스크
  • D (Uninterruptible Sleep) : 주로 디스크 I/O 등이 끝나기를 기다리며 멈춰 있는 태스크 (TASK_UNINTERRUPTIBLE)

즉 커널이 세는 값은 대략 다음과 같습니다.

active = nr_running(R) + nr_uninterruptible(D)

바로 이 D 상태 포함이 리눅스 load average의 결정적 특징입니다.

전통적인 Unix는 실행 가능한(R) 태스크만 셌지만, 리눅스는 1993년 패치 이후에 I/O 대기(D)까지 포함하도록 바뀌었습니다.

그 결과 리눅스의 load average는 "CPU 수요"가 아니라 "시스템 전체 수요(CPU + I/O 대기)" 를 의미하게 되었습니다.

그래서 이런 일이 생깁니다.

  • 디스크가 병목이면, 프로세스들이 I/O 완료를 기다리며 D 상태로 쌓입니다.
  • 이 프로세스들은 CPU를 전혀 쓰지 않지만 load에는 그대로 반영됩니다.
  • 결과적으로 CPU 사용률은 낮은데 load average만 치솟는 그림이 나옵니다.

load가 높다 = CPU가 바쁘다가 무조건적으로는 아닙니다.

load는 CPU 경합I/O 대기를 한 숫자에 섞어 담습니다.

이 점은 뒤에서 다시 문제로 돌아옵니다.



오해 2 - Load Average 단순 평균이 아니다 (EWMA)

1분 평균이라고 하니 최근 60초 동안의 값을 산술평균한다고 생각하기 쉽지만, 아닙니다.

리눅스는 지수 가중 이동 평균(EWMA, Exponentially Weighted Moving Average) 으로 계산합니다.

EWMA (지수 가중 이동 평균)

최근 값에 가장 큰 가중치를 주고, 오래된 값일수록 가중치를 지수적으로 줄여 나가는 평균입니다.

최근 N개를 똑같은 비중으로 평균 내는 단순이동평균(SMA)과 달리, 새 값에 민감하게 반응하면서도 과거를 완전히 잊지는 않습니다.

게다가 이전 평균 하나만 기억하면 갱신되므로, 지난 데이터를 쌓아둘 필요가 없습니다.

동작은 이렇습니다.

  1. 커널이 약 5초마다(LOAD_FREQ = 5*HZ + 1) 위의 active 태스크 수를 샘플링합니다.
  2. 매 샘플마다 세 개의 값(1/5/15분)을 각각 정해진 감쇠 상수로 갱신합니다.

핵심 갱신식은 다음과 같습니다. 여기서 exp가 "과거 값을 얼마나 유지할지"를 정하는 감쇠 상수입니다.

새 load = (기존 load × exp) + (현재 active × (1 - exp))

과거 값에 exp만큼 가중치를 주고, 새 관측값에 나머지를 주는 전형적인 EWMA입니다.

exp가 1에 가까울수록 과거를 오래 기억합니다(= 값이 천천히 변합니다).

커널은 부동소수점 없이 계산한다

이 코드 경로에서는 부동소수점 연산을 쓰지 않기 때문에, 커널은 고정소수점(fixed-point) 으로 처리합니다.

실제 코드는 대략 이렇습니다.

5초 샘플 + 시정수 1/5/15분을 커널이 직접 명시

위 개념식과 일치

실제 구현 코드 (고정소수점)

부동소수점 없이 계산

(커널엔 부동소수점이 없어서 정수로 소수를 흉내낸다는 뜻)

11비트 소수부를 곱하면 22비트가 되고, 그래서 정수 10비트 + 소수 11비트 정밀도. → FSHIFT=11

"With 2-second counting freq, the EXP_n values would be 1981, 2034, 2043"
만약 2초마다 셌다면 상수가 1981/2034/2043이 됐을 것. → EXP 값이 샘플링 주기(5초)에 종속임

출처: https://elixir.bootlin.com/linux/v7.1.8/source/include/linux/sched/loadavg.h

15분 상수가 1분 상수보다 2048(=1.0)에 가깝다는 점에 주목하세요.
과거를 더 오래 기억하기 때문에 값이 그만큼 천천히 움직입니다.

그래서 1은 구간이 아니라 시간상수입니다.

여기서 가장 중요한 결론이 나옵니다. "1분/5분/15분"은 평균을 낸 구간이 아니라, 지수 감쇠의 시간상수(time constant) 입니다.

  • 1분 load는 "최근 60초의 평균"이 아니라, 60초 시정수를 가진 지수 감쇠 평균입니다.
  • 부하 스파이크가 값에 대부분 반영되거나, 사라지는 데 대략 그 시간이 걸린다는 뜻입니다.
  • 그래서 순간 부하에는 1분 값이 가장 민감하게 튀고, 15분 값은 굼뜨게 따라옵니다. 세 숫자를 함께 보면 부하가 오르는 추세인지 내리는 추세인지를 읽을 수 있습니다 (1분 > 15분이면 상승세).


마치며

Load Average는 익숙하지만 오해하기 쉬운 지표입니다.

이번 글에서 정리한 핵심은 세 가지입니다.

  • 개수이지, %가 아니다 — load는 "얼마나 바쁜가"가 아니라 처리 대기 중인 작업이 평균 몇 개인가를 나타냅니다.
  • CPU만 세지 않는다 — 실행 대기(R)뿐 아니라 I/O 대기(D) 까지 포함하므로, load가 높다고 CPU가 바쁜 것은 아닙니다.
  • 단순 평균이 아니다 — 5초마다 샘플링한 값을 지수 가중 이동 평균(EWMA) 으로 갱신하며, "1분/5분/15분"은 구간이 아니라 시간상수입니다.

한 줄로 줄이면, Load Average는 "시스템이 감당해야 할 일의 양(CPU + I/O 수요)을 완만하게 평활한 추정치" 입니다.

그런데 여기서 자연스러운 의문이 남습니다.

load가 R과 D를 한 숫자에 섞어 담는다면, 실제로 서버 load가 치솟았을 때 그것이 CPU 문제인지 I/O 문제인지 어떻게 가려낼 수 있을까요?

다음 글에서는 CPU-bound와 I/O-bound 상황을 직접 재현해 load가 어떻게 달라지는지 보고, vmstat로 그 부하의 정체를 진단하는 방법을 실습으로 다뤄보겠습니다.

감사합니다.




정택준
Team: https://nangman.cloud/ko
E-mail: taekjunnnn@nangman.cloud

0개의 댓글