Linux Load Average 원인 분석: 장애 증거 자동 수집 방법
Linux Load Average 원인 분석을 하다 보면 CPU 사용률은 높지 않은데 Load Average만 비정상적으로 올라가는 상황을 만날 때가 있습니다. 실제 운영 서버에서도 비슷한 현상이 간헐적으로 발생했고, 장애가 지나간 뒤에는 당시 상태를 확인하기 어려워 원인 추적에 어려움이 있었습니다.
이번 글에서는 실제 운영 서버에서 발생했던 사례를 바탕으로 D-State와 WCHAN, 디스크 I/O를 확인하고 장애 순간의 서버 상태를 자동으로 남기도록 구성했던 과정을 정리합니다.
Linux Load Average 원인 분석이 CPU 확인만으로 끝나지 않는 이유
Load Average가 높으면 가장 먼저 CPU 사용률부터 확인하게 됩니다.
uptime
top
하지만 Linux의 Load Average는 단순히 CPU 사용률만 의미하지 않습니다.
CPU를 사용하기 위해 대기하는 프로세스뿐만 아니라 디스크 I/O 등 작업 완료를 기다리는 D-State(Uninterruptible Sleep) 상태의 프로세스도 Load에 영향을 줍니다.
따라서 CPU 사용률이 높지 않더라도 디스크나 파일시스템에서 작업이 지연되고 있다면 Load Average가 올라갈 수 있습니다.
이번 사례에서도 CPU 사용률만 확인해서는 원인을 특정하기 어려웠습니다.
실제 운영 서버의 Load Average 상승 현상
운영 중인 서버에서 평소보다 Load Average가 크게 상승하는 현상이 간헐적으로 발생했습니다.
문제는 장애가 계속 유지되는 것이 아니었다는 점입니다.
담당자가 서버에 접속했을 때는 이미 Load가 정상으로 내려가 있는 경우가 많았기 때문에 top이나 ps만으로는 당시 상황을 확인하기 어려웠습니다.
우선 서버에 변경 작업을 하기 전에 현재 상태부터 확인했습니다.
uptime
free -m
df -h
df -i
ps aux --sort=-%cpu | head
ps aux --sort=-%mem | head

확인 당시 CPU와 메모리 사용률만 보면 서버 전체가 계속 과부하 상태라고 보기는 어려웠습니다.
그래서 프로세스 상태를 추가로 확인했습니다.
ps -eo pid,ppid,state,wchan:30,cmd | awk '$3=="D"'
여기서 D-State 상태의 프로세스가 확인됐고, 단순한 CPU 사용률보다는 I/O 대기 쪽으로 분석 범위를 좁혀보기로 했습니다.
D-State와 WCHAN으로 Linux Load Average 원인 분석
해당 서버에서는 SSHFS를 사용하는 구간이 있었기 때문에 처음에는 원격 파일시스템 응답 지연을 의심했습니다.
SSHFS나 NFS와 같은 네트워크 파일시스템은 상대 서버 또는 네트워크 응답이 지연되면 I/O를 요청한 프로세스가 D-State 상태에 머무를 수 있습니다.
하지만 D-State 프로세스가 실제로 어느 지점에서 대기하고 있는지 확인해 보니 다음과 같은 커널 대기 함수도 확인됐습니다.
xlog_state_get_iclog_space
flush_work
특히 xlog_state_get_iclog_space는 XFS 로그 처리와 관련된 대기 지점이기 때문에 SSHFS만을 원인으로 단정하기는 어려웠습니다.
분석 범위는 다음과 같이 넓어졌습니다.
Load 상승
→ D-State 확인
→ SSHFS 의심
→ WCHAN 확인
→ XFS / 로컬 디스크 Write 지연 가능성 확인
처음 눈에 들어온 증상이 실제 원인처럼 보이는 경우가 많지만, 장애 분석에서는 프로세스가 실제로 어디에서 대기하고 있는지 확인하는 과정이 중요합니다.
이번 사례에서도 SSHFS를 먼저 의심했지만 WCHAN을 확인하면서 로컬 파일시스템과 디스크 I/O까지 함께 확인하게 됐습니다.
디스크 I/O 상태 확인
다음으로 디스크 응답 상태를 확인했습니다.
iostat -x 1 5
주로 확인한 값은 다음과 같습니다.
%utilawaitr_awaitw_await- 읽기/쓰기 처리량
Load가 상승하는 시점에 await 값도 함께 증가하거나 특정 디스크의 %util이 계속 높게 유지된다면 Storage 계층의 지연 가능성을 확인할 수 있습니다.
커널 로그도 함께 확인했습니다.
dmesg -T | tail -100
journalctl -k --since "-30 min"
디스크 오류나 I/O timeout, XFS 관련 메시지가 발생하지 않았는지 확인하기 위한 과정입니다.
여기까지 확인하면서 Linux Load Average 원인 분석은 단순 CPU 사용률 확인만으로 끝내기 어렵다는 것을 다시 확인할 수 있었습니다.
장애가 끝나면 당시 정보도 사라진다
이번 장애에서 가장 어려웠던 점은 Load Average가 계속 높은 것이 아니라 특정 시점에만 상승했다가 다시 정상화된다는 것이었습니다.
장애가 발생했다는 연락을 받고 서버에 접속하면 이미 정상 상태인 경우가 많았습니다.
결국 다음 장애가 발생할 때 사람이 직접 접속해서 확인하는 방식보다는 문제가 발생한 순간의 서버 상태를 자동으로 저장하는 방법이 필요했습니다.
그래서 별도의 Load 감시 스크립트를 구성했습니다.
공개용 예제에서는 수집 결과를 다음 경로에 저장하도록 했습니다.
/var/log/load-spike-captures/
Load Average와 D-State를 기준으로 자동 수집
실제 구성에서는 CPU 코어 수를 기준으로 Load 임계치를 설정했습니다.
다음과 같이 CPU 코어 수의 2배를 기본 Load 임계치로 사용하고, D-State 프로세스가 일정 수 이상 발생하는 경우에도 자료를 저장하도록 했습니다.
CORES="$(nproc 2>/dev/null || echo 1)"
TRIGGER_LOAD="$(
awk -v n="$CORES" \
'BEGIN { printf "%.1f", n * 2.0 }'
)"
BLOCKED_THRESHOLD=5
현재 Load와 blocked process 수를 확인해 조건을 만족하면 스냅샷을 저장합니다.
if awk -v value="$load1" -v threshold="$TRIGGER_LOAD" \
'BEGIN {exit !(value >= threshold)}'
then
should_capture=1
fi
if [ "$blocked" -ge "$BLOCKED_THRESHOLD" ]; then
should_capture=1
fi
if [ "$should_capture" -eq 1 ]; then
capture_snapshot "$reason"
fi
여기서 중요한 점은 Load가 몇 이상이면 무조건 장애다라는 기준을 만들기 위한 것이 아닙니다.
CPU 코어 수와 서비스 특성에 따라 정상 Load 범위가 다르기 때문에 실제 운영 서버의 평소 상태를 확인한 뒤 임계치를 조정해야 합니다.
이번 환경에서는 원인을 바로 판단하기보다는 이상 징후가 발생한 순간의 정보를 놓치지 않는 것을 우선했습니다.
장애 발생 시 어떤 정보를 저장했나?
단순히 top 결과만 저장하면 이후 원인을 판단하기 어렵습니다.
그래서 CPU뿐만 아니라 프로세스, 디스크, 네트워크, 웹 서버, PHP-FPM, MySQL 상태까지 함께 확인할 수 있도록 수집 범위를 넓혔습니다.
<details> <summary><strong>장애 발생 시 수집 항목 자세히 보기</strong></summary>
- uptime / Load Average
- CPU / Memory 상태
- top 프로세스 및 Thread
- R-State / D-State Thread
- WCHAN / syscall / Kernel Stack
- vmstat
- mpstat
- pidstat
- iostat
- 네트워크 인터페이스 및 연결 상태
- SSHFS 관련 프로세스
- Apache 프로세스 및 server-status
- PHP-FPM 프로세스 및 주요 설정
- MySQL 상태 및 Processlist
- 최근 Journal / Apache Error Log
- 최근 접속 상위 IP 및 URL
</details>
이렇게 자료를 남겨두면 장애가 종료된 이후에도 당시 어떤 프로세스가 실행 중이었는지, D-State가 몇 개였는지, 어느 커널 함수에서 대기했는지 다시 확인할 수 있습니다.
특히 D-State 프로세스는 wchan, syscall, /proc의 Stack 정보까지 같이 확보하면 단순히 “I/O가 느렸다”에서 끝나지 않고 어느 영역에서 대기했는지 범위를 좁히는 데 도움이 됩니다.
운영 서버 적용 전 수동 검증
운영 서버에서 사용하는 스크립트이기 때문에 바로 자동 실행하도록 구성하지 않았습니다.
기존 파일이 있다면 먼저 백업합니다.
cp -a /usr/local/sbin/load-spike-watch.sh \
/usr/local/sbin/load-spike-watch.sh.bak
현재 파일과 경로도 먼저 확인합니다.
ls -al /usr/local/sbin/load-spike-watch.sh
ls -ld /var/log/load-spike-captures/
이후 자동 감시 전에 수동 수집 모드부터 실행합니다.
/usr/local/sbin/load-spike-watch.sh --once
생성된 결과도 확인합니다.
ls -alh /var/log/load-spike-captures/
여기서는 단순히 파일이 생성됐는지만 확인하지 않았습니다.
- 필요한 정보가 정상적으로 저장되는지
- 특정 명령어에서 지나치게 오래 대기하지 않는지
- 수집 작업 자체가 서버 부하를 높이지 않는지
- 로그가 계속 누적돼 디스크를 채우지는 않는지
등을 함께 확인했습니다.
실제 구성에서는 일정 기간이 지난 수집 자료를 자동으로 삭제하도록 보존 기간도 설정했습니다.
문제가 발생하면 자동 실행을 중지하고 백업해 둔 기존 파일로 원복할 수 있도록 준비하는 것이 좋습니다.
전체 Load 감시 스크립트
실제 운영 환경에서 사용한 스크립트는 여러 상태를 동시에 수집하다 보니 코드가 상당히 길어졌습니다.
본문에 전체 코드를 넣으면 장애 분석 내용보다 코드가 더 길어지기 때문에 여기서는 핵심 동작만 정리했습니다.
전체 구조가 필요한 경우 글에 첨부된 공개용 예제 파일을 참고할 수 있습니다.
첨부파일: load-spike-watch-example.txt
공개용 파일에서는 실제 운영 서버의 도메인과 로그 경로 등은 일반적인 예제 값으로 변경했습니다.
또한 서버마다 Apache, PHP-FPM, MySQL 설치 경로나 로그 위치가 다르기 때문에 그대로 적용하기보다는 현재 서버 환경을 먼저 확인한 뒤 필요한 부분을 수정해야 합니다.
Linux Load Average 원인 분석을 하며 확인한 점
이번 사례에서는 처음에는 SSHFS 문제를 의심했지만 D-State 프로세스와 WCHAN을 확인하면서 XFS 및 로컬 디스크 Write 처리까지 분석 범위를 넓혔습니다.
그리고 원인을 찾는 것만큼 중요했던 것은 장애가 발생한 순간의 데이터를 남겨두는 것이었습니다.
서버 장애는 엔지니어가 보고 있을 때만 발생하지 않습니다.
특히 Load Average처럼 일시적으로 상승했다가 정상화되는 현상은 장애가 끝난 뒤 현재 상태만 확인해서는 원인을 찾기 어렵습니다.
이번 Linux Load Average 원인 분석에서는 다음과 같은 순서로 접근했습니다.
Load Average 확인
→ CPU / Memory 확인
→ D-State 확인
→ WCHAN 확인
→ Disk I/O 확인
→ Kernel 로그 확인
→ 장애 시점 자동 수집
한 번의 장애에서 바로 원인을 찾지 못하더라도 당시의 프로세스와 I/O 상태를 제대로 남겨두면 다음 장애가 발생했을 때 원인을 훨씬 빠르게 좁힐 수 있습니다.
실제 경험을 바탕으로 ai를 활용하여 작성하였습니다.





