하드웨어 중급 #9 실전: 느려진 서버 진단하기 — 시리즈 마무리

5 분 소요

8편까지로 자원별 부품이 모두 모였습니다. 마지막 글은 그 부품들을 하나의 사건에 끼워 봅니다. 운영 현장에서 가장 흔한 신고, “서비스가 느려요"에서 출발해 원인 확정과 처방 검증까지 가는 진단 워크스루입니다. 시나리오는 가상이지만, 각 단계의 판단은 시리즈에서 다룬 그대로입니다.

0단계 — 증상을 숫자로 바꾸기 #

신고가 들어왔습니다. “오후부터 API가 가끔 몇 초씩 걸려요.” 진단의 첫걸음은 서버에 접속하는 것이 아니라 증상을 숫자로 정의하는 것입니다.

  • 무엇이: API 응답 시간의 99분위가 평소 0.3초 → 오후부터 간헐적으로 3〜5초
  • 언제부터: 14시경부터, 몇 분 간격으로 수십 초씩
  • 무엇은 아닌지: 에러율은 그대로, 전체 평균은 소폭 상승

“간헐적"과 “꼬리 지연"이라는 두 단서가 벌써 방향을 줍니다. 자원이 상시 모자라면 증상도 상시입니다. 주기적이라면 주기적인 무언가(배치, 플러시, 백업)가 있습니다. 1편에서 평소 값의 기록이 중요하다고 한 이유가 여기서 바로 나옵니다. “평소 0.3초"를 모르면 비교 자체가 안 됩니다.

1단계 — 네 자원 훑기 #

1편의 점검표대로 사용률·포화·에러를 자원 순서로 훑습니다.

vmstat 5 (증상 시각)
procs ---------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  9  10240 802340 211456 4.1e+07    0    0    52 91240 980 4100  9  6 41 44  0

한 줄에서 읽히는 것이 많습니다.

  • CPU: us 9, sy 6으로 한가합니다. st 0이므로 2편의 스틸도 아닙니다. 그런데 wa 44 — CPU가 I/O를 기다리며 놀고 있습니다.
  • 메모리: si/so 0이므로 스왑 흐름이 없습니다. 3편의 기준으로 메모리 부족이 아닙니다.
  • 포화: b 열(I/O 대기로 잠든 작업) 9. 디스크 앞에 줄이 서 있습니다.
  • bo(블록 쓰기)가 평소 대비 수십 배입니다. 누가 갑자기 대량으로 쓰고 있습니다.

로드 애버리지가 높았다면 그 이유도 이제 설명됩니다. 1편에서 본 대로 리눅스 로드에는 I/O 대기가 포함되니까요. 네 자원 중 스토리지의 포화로 좁혀졌습니다.

2단계 — 스토리지로 줌인 #

iostat -x로 디스크 상태를, 그리고 누가 쓰는지를 봅니다.

iostat -x 5 (해당 디스크만)
Device   r/s    w/s   wkB/s  aqu-sz  w_await  %util
nvme0n1  12.0  3100  364000   28.4     9.2     98.7

%util 98.7에 큐 길이(aqu-sz) 28, 쓰기 지연(w_await) 9.2ms. 5편에서 본 그대로, 큐가 쌓이면 지연이 오릅니다. 평소 0.5ms 수준이던 쓰기 지연이 20배 가까이 늘었으니, 이 디스크를 기다리는 모든 요청(DB 커밋 포함)이 함께 느려집니다. API의 꼬리 지연과 모양이 맞습니다.

이제 “누가"입니다. 쓰기 폭주의 시간 패턴(몇 분 간격, 수십 초)이 3편의 더티 페이지 플러시와 닮았습니다. 확인해 보니 14시에 배포된 새 기능이 대용량 로그를 버퍼드 I/O로 쓰기 시작했고, 쌓인 더티 페이지를 커널이 주기적으로 한꺼번에 내려보내며 디스크를 포화시키고 있었습니다. 가설이 아니라 확인입니다. 더티 페이지 양(/proc/meminfo의 Dirty)이 플러시 직전마다 수 GB까지 차오르는 것을 지표로 확정했습니다.

3단계 — 처방, 그리고 재측정 #

처방은 층위별로 여러 개가 가능하고, 싸고 확실한 것부터 갑니다.

  1. 원인 제거(애플리케이션) — 로그를 같은 디스크에 쓰지 않게 분리하거나, 쓰기 양 자체를 줄입니다. 근본 처방입니다.
  2. 완충 조정(커널) — 분리가 당장 어렵다면 더티 비율을 낮춰 “조금씩 자주” 내려보내게 합니다. 폭주의 키를 줄이는 대증 처방입니다.
  3. 자원 증설(하드웨어) — 쓰기 IOPS 가 상시 부족한 것이라면 디스크 증설이 답이지만, 이 사건은 상시 부족이 아니라 폭주이므로 증설은 과잉입니다.

1번을 적용하고 0단계의 숫자로 재측정합니다. 99분위가 0.3초로 돌아오고 wa 와 디스크 큐가 평소 수준이면 종료입니다. 증상 정의를 숫자로 해 둔 덕에 “고쳐진 것 같다"가 아니라 “고쳐졌다"로 닫을 수 있습니다.

진단의 일반형 #

시나리오는 하나였지만 절차는 일반형입니다.

  1. 증상을 숫자로 정의한다(무엇이, 언제부터, 무엇은 아닌지).
  2. 네 자원을 사용률·포화·에러로 훑어 한 자원으로 좁힌다.
  3. 그 자원으로 줌인해 누가·왜를 지표로 확정한다.
  4. 싸고 확실한 처방부터 적용하고, 0단계의 숫자로 재측정한다.

다른 결말이었어도 길은 같습니다. st가 높았다면 2편의 이주 처방으로, si/so가 돌았다면 3편의 메모리 진단으로, 노드 쏠림이었다면 4편의 NUMA로, 경로 단절이었다면 7편의 멀티패스로 갈라질 뿐입니다.

마지막으로 튜닝의 원칙 하나를 시리즈의 결론으로 남깁니다. 커널 파라미터는 진단의 출발점이 아니라 종착점입니다. sysctl 손잡이들은 원인이 지표로 확정된 뒤에, 한 번에 하나씩, 전후 측정과 함께 만지는 도구입니다. 측정 없이 인터넷의 튜닝 모음을 적용하는 것은 진단이 아니라 복권입니다. 구체적인 손잡이들은 RHEL 고급 #2가 다룹니다.

자주 만나는 함정 #

  • 서버에 먼저 들어가고 증상은 나중에 정의한다 — 숫자 없는 진단은 “재부팅했더니 좋아진 듯"으로 끝납니다. 증상 정의가 0단계입니다.
  • 첫 가설에 정착한다 — wa 를 보고 “디스크가 노후했다"로 점프하면 증설로 끝나고 원인(쓰기 폭주)은 남습니다. 누가·왜까지 지표로 확정합니다.
  • 고치고 재지 않는다 — 처방 후 재측정이 없으면 다음 사건 때 같은 진단을 처음부터 다시 합니다. 전후 숫자를 기록으로 남깁니다.

정리 — 시리즈를 닫으며 #

아홉 편을 돌아보면 이렇습니다.

  • 모든 자원에 사용률·포화·에러를 묻고, 체감을 만드는 쪽은 포화입니다(1편).
  • CPU는 클럭의 변덕과 스틸을(2편), 메모리는 available과 더티 페이지와 cgroup 한도를(3편), 멀티소켓에서는 NUMA를 봅니다(4편).
  • 스토리지는 조건을 맞춰 실측하고(5편), RAID는 죽은 다음을 설계하고(6편), 네트워크 너머의 디스크는 길의 이중화를 챙깁니다(7편).
  • GPU는 다섯 번째 자원이지만 공급은 여전히 네 자원의 몫입니다(8편).
  • 그리고 진단은 언제나 증상의 숫자 정의에서 시작해 재측정으로 끝납니다(9편).

하드웨어 기초가 “느리다와 비싸다를 추측에서 진단의 대상으로” 바꾸는 시리즈였다면, 중급은 그 진단을 실제로 해내는 실전 감각을 만드는 시리즈였습니다. 이제 지표 앞에서 길을 잃지 않기를 바랍니다. 다음에 또 만나요.

X