이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • IO Wait와 Disk 병목에서 붙잡아야 할 핵심 판단 기준은 무엇인가?
  • disk 병목은 응답 지연으로 번진다라는 기준은 어떤 JVM/OS 신호로 확인되는가?
  • Java/Spring 코드에서 IO Wait와 Disk 병목 판단 기준은 어떤 장애 신호로 드러나는가?

개요

이 문서의 주제는 IO Wait와 Disk 병목이다. iowait, disk queue, latency를 보고 로그와 파일 I/O 병목을 판단하는 방법을 정리한다.

먼저 붙잡을 기준은 다음 문장이다. disk 병목은 CPU가 놀고 있는 것처럼 보이는 순간에도 응답 시간을 밀어 올릴 수 있다. 로그 flush, 파일 업로드, dump 생성, swap, 로컬 DB I/O가 같은 장치를 공유하면 Java 코드의 한 줄보다 storage latency가 API 지연을 더 크게 만든다.

핵심 모델

  • 판단 기준: disk 병목은 응답 지연으로 번진다.
  • iowait는 CPU가 I/O 완료를 기다린 시간 비율이지, 어떤 파일이나 코드가 느린지 직접 말해 주는 지표는 아니다.
  • await, queue depth, device utilization을 함께 봐야 장치 latency와 요청 적체를 분리할 수 있다.
  • 로그, heap dump, GC log, upload, swap, DB 파일이 같은 disk를 쓰면 한 작업의 I/O가 다른 요청의 tail latency로 전파된다.
  • Java thread dump에서 RUNNABLE로 보이는 thread도 native I/O나 logging append 경로에서 실제로는 disk 완료를 기다릴 수 있다.
  • 장애 대응은 I/O를 만드는 작업을 줄이는 임시 완화와 storage 경로를 바꾸는 근본 수정을 분리한다.

문서별 핵심 구분

  • iowait는 CPU가 I/O 완료를 기다리는 시간이다.
  • disk await와 queue depth는 latency 원인을 좁히는 지표다.
  • 로그, dump, DB, upload가 같은 disk를 공유하면 병목이 겹친다.

원리

Disk 병목은 CPU bound와 다르게 CPU 사용률이 낮아도 요청 시간이 길어질 수 있다. 커널이 block I/O 완료를 기다리는 동안 task가 runnable하게 계산하지 못하면 %wa와 load average가 같이 움직일 수 있다.

iostatawait는 장치 관점의 평균 응답 시간이고, aqu-sz나 queue 관련 값은 밀린 요청의 크기를 보여 준다. %util이 높고 await도 같이 높으면 장치가 포화됐을 가능성이 커지지만, storage 종류와 가상화 계층에 따라 %util 하나만으로 결론을 내리면 위험하다.

Java/Spring 장애에서는 disk 병목이 단독으로 나타나기보다 logging, file upload, report export, heap dump, GC log, swap, 로컬 DB I/O와 얽힌다. 따라서 같은 시각의 iostat, vmstat, pidstat -d, thread dump를 맞춰 봐야 disk 대기가 어떤 요청 경로와 연결되는지 좁힐 수 있다.

Java/Spring 연결

  • 동기 log appender가 느린 disk에 묶이면 요청 thread가 로그 기록 시간까지 함께 부담한다.
  • 대용량 upload/download, report export, batch file write는 CPU보다 disk queue를 먼저 밀어 올릴 수 있다.
  • heap dump, thread dump 대량 저장, GC log rotation은 장애 분석 도구이면서 동시에 같은 disk에 추가 부하를 만들 수 있다.
  • Actuator의 JVM 지표만으로 disk 병목을 확정할 수 없으므로 OS disk 지표와 application log 시각을 반드시 맞춘다.
  • async logging을 쓰더라도 queue가 가득 차면 drop, blocking, memory 증가 중 하나의 형태로 애플리케이션에 되돌아온다.

코드와 설정 예시

date -Is
iostat -xz 1 5
pidstat -d -p <pid> 1 5
vmstat 1 5
jcmd <pid> Thread.print > thread-$(date +%s).txt

예시는 disk 지표와 Java 실행 위치를 같은 시간대에 묶어 남기기 위한 최소 묶음이다. pidstat -d로 해당 JVM이 실제 read/write를 만드는지 보고, thread dump에서 logging, upload, report, dump 생성 경로가 같은 시각에 묶여 있는지 확인한다.

관찰 명령

iostat -xz 1 5
vmstat 1 5
pidstat -d -p <pid> 1 5
jcmd <pid> Thread.print

IO Wait와 Disk 병목 관찰 명령은 결론이 아니라 가설 확인 도구다. 운영 중에는 반복 간격, 대상 PID, 수집 시각을 함께 남기고 출력이 큰 명령은 범위를 제한한다.

지표 해석

신호먼저 의심할 것다음 확인
%wa와 load 동반 증가disk 또는 swap 대기iostat, vmstat si/so, D-state task
await 증가storage latency 상승device, mount, cloud volume, 로그/DB 경로
pidstat -d write 급증특정 JVM 또는 batch가 disk write 생성log rotation, dump, upload, export
thread dump에 logging/file 경로 집중요청 thread가 I/O 경로에 묶임appender 설정, 파일 저장소, async queue

Disk 분석에서는 로그 쓰기량, await, queue, API latency의 순서를 맞춘다. 쓰기 폭주가 먼저였는지, 이미 느려진 storage에 로그가 뒤따랐는지에 따라 조치가 달라진다.

장애 상황에서 보는 순서

  1. API 지연이 오른 시각과 disk 관련 작업이 시작된 시각을 맞춘다.
  2. vmstat%wa, si/so, runnable/block 상태를 먼저 본다.
  3. iostat -xz로 device별 await, queue, utilization을 확인한다.
  4. pidstat -d와 로그 시각으로 어떤 프로세스가 read/write를 만드는지 좁힌다.
  5. 같은 시각의 thread dump에서 logging, upload, dump, report, DB file I/O 경로를 찾는다.
  6. 임시 완화는 verbose log 축소, dump 중지, batch 지연, upload 제한, 다른 volume 사용 순서로 검토한다.

이 순서는 disk가 의심될 때의 루틴이다. %wa가 높다는 이유만으로 disk를 확정하지 말고, 해당 JVM이 실제 I/O를 만들었는지와 같은 시각의 요청 지연이 함께 움직였는지를 확인한다.

실전 팁

  • iowait가 높을 때는 먼저 swap과 disk I/O를 분리한다. memory pressure가 원인인 swap I/O라면 disk 증설보다 heap, cache, batch 크기 조정이 먼저다.
  • disk 지표는 평균값에 속기 쉽다. 짧은 쓰기 폭주가 p99 지연을 만들 수 있으므로 1초 간격 샘플과 애플리케이션 latency 타임라인을 같이 본다.
  • 장애 중 heap dump를 떠야 한다면 dump 경로가 서비스 로그와 같은 volume인지 확인한다.
  • 로그 레벨을 올리는 대응은 원인 분석에는 도움이 되지만, disk 병목 상황에서는 장애를 키울 수 있다.
  • 같은 서버에 DB나 message broker가 같이 있으면 JVM만 보지 말고 장치 공유 관계를 먼저 그린다.

주니어 팁

  • %wa가 높다는 말은 “CPU가 느리다”가 아니라 “I/O 완료를 기다린 시간이 있다”에 가깝다.
  • iostat 출력에서 어느 device가 문제인지 보지 않고 전체 평균만 보면 원인을 놓친다.
  • thread dump에 파일 경로가 직접 보이지 않을 수 있으므로 로그 시각, 파일 크기 증가, pidstat -d를 함께 본다.

시니어 팁

  • 서비스 로그, audit log, access log, dump, upload 임시 파일의 volume을 분리하면 장애 시 쓰기 폭주가 한 지점에 몰리는 위험을 줄일 수 있다.
  • async appender는 성능 기능이 아니라 backpressure 정책까지 포함한 운영 설계다. queue overflow 때 block, drop, fail 중 무엇을 선택할지 정해야 한다.
  • container 환경에서는 host disk, overlay filesystem, persistent volume의 latency 특성이 다르므로 배포 형태별 기준선을 따로 둔다.

Guru급 팁

  • Disk 병목은 mount, volume type, container overlay, host 공유 관계를 알아야 해석된다. device 이름만 기록하면 애플리케이션 경로와 연결하기 어렵다.
  • Log, dump, upload, batch output은 volume을 나누고 quota를 둬야 한 작업의 쓰기 폭주가 전체 서비스 latency를 흔들지 않는다.
  • Storage를 빠르게 바꾼 뒤에는 iowait가 줄었는지뿐 아니라 bottleneck이 CPU, DB, network로 이동했는지도 다시 확인한다.

위험 신호!

  • %wa만 보고 disk 병목이라고 확정한다.
  • await가 높은데 어떤 device와 어떤 mount가 서비스 경로인지 모른다.
  • 장애 중 heap dump와 debug log를 같은 disk에 추가로 쓴다.
  • 로그 write 폭주를 줄이지 않고 작업자 스레드 풀 크기만 늘린다.
  • storage latency가 계속 높은데 API timeout만 길게 늘린다.
  • iostat, pidstat, thread dump의 수집 시각이 서로 맞지 않는다.

확인 질문

확인 질문

  • iowait가 높을 때 CPU 증설보다 먼저 확인해야 할 것은 무엇인가?
    • 어떤 device의 await와 queue가 증가했는지, swap I/O가 있는지, 해당 JVM이 실제 read/write를 만들었는지 확인해야 한다.
  • Java/Spring 서비스에서 disk 병목이 요청 지연으로 번지는 대표 경로는 무엇인가?
    • 동기 logging, file upload/download, report export, heap dump, GC log, 로컬 DB I/O가 요청 thread나 shared disk를 점유하는 경로다.
  • disk 병목 상황에서 debug log 확대가 위험한 이유는 무엇인가?
    • 원인 관찰을 위해 쓰기량을 늘리는 순간 같은 disk의 queue와 latency가 더 커져 장애를 증폭할 수 있기 때문이다.

참고 문서