3장. Load Average와 시스템 부하
Last updated
Last updated
man proc 이 정의하는 loadavg
2장에서 확인한 프로세스의 상태 중 "R" 과 "D" 상태에 있는 프로세스 개수의 1분, 5분, 15분마다의 평균 값이라고 한다.
Load Average가 높다면 많은 수의 프로세스가 실행중이거나 I/O 등을 처리하기 위한 대기 상태에 있다는 것이다.
하지만, 프로세스의 수를 세는 것이기 때문에 시스템에 있는 CPU Core 수에 따라 각각의 값의 의미는 상대적이다.
"
fs/proc/loadavg.c d
파일의loadavg_proc_show()
분석하기"
avnrun
이라는 unsigned long 타입의 3개짜리 배열을 선언한 뒤 get_aven_run(avnrun, FIXED_1/200, 0);
함수를 통해 avnrun
배열에 값을 입력한다.
avnrun
배열에 있는 값을 토대로 Load Average 값을 출력
이 함수를 통해 실제 계산되는 과정을 찾을 수 없다. 이 함수는 내부적으로 계산된 값을 보여주는 함수이다.
"
get_aven_run()
분석하기"
unsigned long
형태의 배열을 인자로 받아서 해당 배열에 값을 넣어주는 함수인데, 해당 함수 내부에 사용중인 avenrun
배열이 인자로 받은 loads
배열에 계산한 값을 넣어주고있다.
아쉽지만 이 함수도 Load Average를 계산 해주는 함수가 아니었다. 그래도 avenrun
배열이라는 힌트를 얻었으니, 이번엔 avenrun
배열을 찾아보자.
"
avenrun
을 사용하고 있는 코드를 찾아라"
calc_load_tasks
값을 atomic_long_read()
라는 매크로를 통해서 읽어온 뒤 active 변수에 할당한다.
이 active
값을 기준으로 avenrun[]
배열에 있는 값들을 calc_load()
함수를 이용하여 계산한다.
이 코드에서 얻을 수 있는 정보는 active
변수와 calc_load()
함수이며, 이 값을 알기 위해서 선행 되었던 calc_load_tasks
가 어떤 값을 가지게 되는지 분석해야한다.
"calc_load_tasks 분석하기"
nr_active
변수에 Run Queue
를 기준으로 nr_running
상태의 프로세스 개수를 adjust
값을 뺀 뒤 입력한다. (R 상태 프로세스)
nr_active
변수에 nr_uninterruptible
상태의 프로세스 개수를 더한다. (D 상태 프로세스)
nr_active
값이 기존에 계산된 값과 다르다면 그 차이 값을 calc_load_active
에 입력한다.
kernel/sched/core.c
의 void sched_tick(void)
함수가 Tick 주기마다 깨어나서 calc_global_load_trick()
함수를 실행 하면, 현재 Run Queue 에 있는 R 상태의 프로세스 개수와 D 상태의 프로세스 개수를 세어 calc_load_tasks
변수에 넣어준다.
책에서 가이드하는 커널 버전과 달라 사용되는 함수 명칭이나 위치가 변경 되었는데, 사진에서 달라진 함수 명칭을 최신 소스 코드에서 최대한 찾아 반영하여 내용을 작성함
calc_load_account_active() -> calc_global_load_tick() 로 변경 되었으며 내부적으로 calc_load_fold_active() 함수를 호출함
결국 프로세스의 개수를 세어 Load Average 를 계산하는 과정을 거치고 있다.
Load Average는 단순히 CPU를 사용하려는 프로세스(R 상태)가 많다는 것을 의미하는 것이 아니고 I/O 병목이 생겨서 I/O 작업을 대기하는 프로세스(D 상태)가 많을 수도 있다는 의미다.
즉, Load Average 값만 가지고 시스템이 현재 어떤 부하를 감당하고 있는지 모른다.
두 스크립트를 실행 하고 uptime 명령어를 통해 확인해보면 Load Average가 올라가는 것을 확인할 수 있다.
Load Average가 비슷한 수준으로 증가하지만, 사실 일으키고 있는 부하는 전혀 다른 부하이며 어떤 부하인지가 중요한 이유는 부하의 종류에 따라 해결 방법이 달라지기 때문이다.
즉, Load Average가 높다고 해서 단순히 CPU 사용량이 많다라고 판단할 수 없다.
"그렇다면 어떻게 부하의 원인을 확인할 수 있을까?"
vmstat
Load Average 값은 시스템 부하가 "있다" 라는 것을 알려주긴 하지만 구체적으로 어떤 부하인지 알 수 없었다.
이 정보를 보기 위해선 vmstat 을 활용할 수 있다.
man
vmstat
FIELD DESCRIPTION FOR VM MODE r: The number of runnable processes (running or waiting for run time). b: The number of processes blocked waiting for I/O to complete.
vmstat
의 두 출력 값 사이의 차이점은 바로 첫 번째 컬럼인 r 열과 두 번째 열인 b 열의 값이다.
r: 실행되기를 기다리거나 현재 실행되고 있는 프로세스의 개수
b: I/O를 위해 대기열에 있는 프로세스의 개수
즉 각각이 위 Load Average 계산 과정에서 살펴보았던 커널 코드의 nr_running
, nr_uninterruptible
변수인 셈이다.
"같은 수준의 Load Average 라면 시스템에 끼치는 영향도 같을까?"
위에서 CPU Bound, I/O Bound는 비슷한 수준의 Load Average 를 증가시킨다고 했었다. 그럼 이렇게 시스템에 입혀지는 부하는 같은 영향을 주고 있는지 궁금할 수 있다.
하지만 언제나 그렇듯 "같을수도, 아닐수도 있다" 가 정답이다.
테스트 해보기
책에서는 nginx
와 java
를 통해서 간단한 GET 요청을 처리할 수 있도록 세팅한 뒤 파이썬 스크립트를 이용하여 10개의 프로세스를 생성한다.
그리고 클라이언트의 역할을 하는 서버에서 siege
툴을 이용하여 응답 시간을 측정한다.
I/O 를 일으키는 파이썬 스크립트들은 D 상태에 빠져있는 것을 볼 수 있다.
하지만, CPU 기반의 부하일 때와는 다르게 파이썬 스크립트보다 nginx
와 java
의 CPU Usage 가 더 많다.
CPU에 대한 경합이 전자의 경우보다 덜하기 때문에 더 빠른 응답 속도를 보여줄 수 있다.
즉, 수행하고 있는 프로세스가 어떤 시스템 자원을 많이 쓰느냐에 따라 부하가 시스템에 미치는 영향이 다르다.
하지만, CPU 기반의 부하가 I/O 기반의 부하보다 응답 속도가 더 빠르다는 것은 꼭 아니란 것을 명심하자.
Load Average는 실행 중 혹은 실행 대기 중이거나 I/O 작업 등을 위해 대기 큐에 있는 프로세스들의 수를 기반으로 만들어진 값이다.
Load Average 자체의 절대적인 높음과 낮음은 없으며 현재 시스템에 장착되어 있는 CPU 코어를 기반으로 한 상대적인 값으로 해석해야한다.
커널에도 버그가 있으며 Load Average 값을 절대 신뢰해서는 안된다.
vmstat
툴 역시 시스템의 부하를 측정하는 데 사용될 수 있으며 r 과 b 열이 어떤 부하인지 정보를 제공한다. 특히 b 열의 경우 I/O 작업 등의 이유로 대기 상태에 있는 프로세스의 수이며 전체적인 시스템의 성능을 떨어뜨릴 수 있는 프로세스들이다. b 열의 수가 높다면 I/O 처리 과정에 문제가 있지는 않은지 살펴봐야 한다.
Amazon Linux2 기준 /sys/kernel/debug/sched/debug
는 vmstat
툴을 통해 확인할 수 있는 것보다 더 자세한 정보를 제공해주며 특히 nr_running
과 runnable tasks
항목에서는 각 CPU에 할당된 프로세스 수와 프로세스의 PID 등의 정보를 확인할 수 있다.
지금 진행하는 과정부터 실제 Tovalds 의 linux 소스코드를 분석한다. []