iostat
고친 사람 github-actions[bot]
iostat 는 디스크가 지금 얼마나 바쁜지 보여 주는 명령입니다. 초마다 디스크가 몇 번 읽고 썼는지, 요청 하나가 얼마나 기다렸는지를 숫자로 찍습니다. 서버 응답이 늦어졌는데 원인이 디스크인지 가려야 할 때 먼저 꺼내 보는 진단 명령입니다.
쉽고 빠른 이해
iostat 는 디스크마다 바쁜 정도를 숫자로 보여 주는 명령입니다. iostat -xz 1 을 치면 1초마다 디스크 한 줄씩 「초당 읽기 몇 번, 쓰기 몇 번, 한 번에 얼마나 기다렸나」가 찍힙니다. -x 는 대기 시간 같은 칸을 더 찍게 하는 옵션입니다. -z 는 그동안 일이 없던 디스크를 빼는 옵션입니다.
디스크가 밀리면 프로그램은 느려지기만 합니다. 에러는 안 냅니다. 이 명령이 없으면 「디스크 탓인가」를 짐작으로 풀어야 합니다.
- 운영체제가 디스크마다 세어 둔 누적 횟수를 읽습니다
- 정한 간격을 두고 한 번 더 읽어 그 사이의 차이를 냅니다
- 차이를 초당 값과 평균 대기 시간으로 바꿔 찍습니다
대가가 있습니다. 어느 프로그램이 디스크를 쓰는지는 안 보입니다. 처음 찍히는 출력은 지금 값이 아니라 서버가 부팅된 뒤 전체의 평균입니다.
상세
iostat 는 input/output statistics(입출력 통계)를 줄인 이름입니다. 리눅스에서는 sysstat 라는 도구 묶음에 들어 있습니다. CPU(Central Processing Unit, 중앙 처리 장치)가 무엇을 하며 시간을 보냈는지와 디스크마다 오간 입출력을 한 화면에 찍습니다.
백엔드 개발자가 이 명령을 꺼내는 때는 대개 서버가 느려졌는데 CPU 는 한가할 때입니다. 데이터베이스 쿼리가 갑자기 느려졌을 때도, 로그를 많이 쓰는 서버가 밀릴 때도 꺼냅니다. 이런 때 iostat 는 「디스크가 일을 못 따라가고 있나」에 답합니다.
이 절은 먼저 디스크 입출력 요청이 어떻게 줄을 서는지 봅니다. 그 그림을 바탕으로 출력의 각 칸을 읽습니다. 그사이 숫자를 잘못 읽기 쉬운 세 곳을 짚습니다. 첫 출력, %iowait, %util 입니다. 끝으로 진단 순서를 봅니다.
디스크 요청과 대기열
프로그램이 파일을 읽거나 쓰면 커널이 그 일을 디스크에 보낼 요청으로 바꿉니다. 요청 하나는 「이 위치에서 이만큼 읽어라」 꼴입니다. 디스크는 요청을 한꺼번에 무한히 받지 못합니다. 그래서 요청은 디스크에 닿기 전에 대기열에 줄을 섭니다.
이 대기열을 관리하는 커널의 부분이 블록 계층입니다. 블록 계층은 줄 선 요청 가운데 붙어 있는 것을 하나로 합치기도 합니다. 디스크 위에서 이웃한 두 요청을 한 번에 보내면 그만큼 덜 오가기 때문입니다.
요청 하나가 끝나기까지 걸리는 시간은 두 토막입니다. 대기열에서 기다린 시간과 디스크가 실제로 처리한 시간입니다. 아래 그림은 이 두 토막이 어디서 생기는지 보입니다.
flowchart TD
A["프로그램 · 파일을 읽고 쓴다"] --> B["커널이 디스크 요청으로 바꾼다"]
subgraph W["iostat 가 찍는 대기 시간 = 기다림 + 처리"]
C["블록 계층의 대기열에서 기다린다"] --> D["디스크가 처리한다"]
end
B --> C
D --> E["프로그램에 결과가 돌아간다"]
iostat 가 찍는 대기 시간은 이 두 토막을 더한 값입니다. 대기열에 있거나 디스크가 처리 중인 요청 수의 평균도 따로 찍습니다. 이 값을 대기열 길이라 부릅니다.
첫 출력은 부팅 뒤 평균
커널은 디스크마다 읽은 횟수, 쓴 횟수, 기다린 시간을 서버가 부팅된 뒤로 계속 더해 둡니다. 리눅스에서는 이 값이 /proc/diskstats 라는 파일로 보입니다.
iostat 는 이 누적 값을 읽어서 계산할 뿐 직접 재지 않습니다.
그래서 iostat 를 간격 없이 치면 부팅 뒤 전체의 평균을 한 번만 찍고 끝납니다. 지금 디스크가 바쁜지 보려면 간격을 줘야 합니다.
간격을 줘도 첫 출력은 여전히 부팅 뒤 평균입니다. 둘째 출력부터가 방금 지난 1초 동안의 값입니다. 그래서 첫 출력을 빼는 -y 옵션이 있습니다.
iostat 1 # 1초마다 계속 찍는다
iostat 1 5 # 1초마다 다섯 번 찍고 끝
iostat -y 1 5 # 첫 출력을 빼고 다섯 번
진단할 때 첫 출력을 보고 「디스크는 한가하다」고 판단하는 것이 흔한 실수입니다. 석 달 켜 둔 서버라면 첫 출력은 석 달 치 평균이라 지금 몰린 일이 묻힙니다.
CPU 줄 읽기
옵션 없이 치면 iostat 는 먼저 CPU 줄을 찍습니다. CPU 시간을 쓰임새별로 나눈 비율입니다.
| 칸 | 뜻 |
|---|---|
%user |
일반 프로그램의 코드를 돌린 시간 |
%system |
커널이 프로그램 대신 일한 시간 |
%iowait |
할 일이 없어 쉬었는데 그동안 끝나지 않은 디스크 요청이 있던 시간 |
%steal |
가상 머신에서만 뜻이 있는 칸. 가상 머신들에 CPU 를 나눠 주는 관리 프로그램(하이퍼바이저)이 다른 머신에 CPU 를 내주느라 이 머신이 기다린 시간 |
%idle |
디스크 요청도 없이 그냥 쉰 시간 |
이 가운데 자주 잘못 읽히는 것이 %iowait 입니다. 이름 때문에 「디스크를 기다리느라 CPU 가 버린 시간」으로 읽기 쉽습니다.
실제로는 쉬는 시간의 한 갈래입니다. CPU 가 쉬던 그 순간에 디스크 요청이 걸려 있었는지로 쉬는 시간을 둘로 나눈 것입니다.
그래서 %iowait 는 디스크 말고 CPU 쪽 사정으로도 움직입니다. 디스크가 똑같이 밀려도 CPU 가 다른 일로 바쁘면 쉬는 시간이 없어서 %iowait 가 0 에 가깝게 나옵니다.
디스크가 밀리는지는 이 칸이 아니라 아래 장치 줄로 봅니다.
장치 줄 읽기
CPU 줄 다음에는 디스크마다 한 줄씩 찍힙니다. 장치 이름은 sda 나 nvme0n1 처럼 커널이 디스크에 붙인 이름입니다.
| 칸 | 뜻 |
|---|---|
tps |
초당 디스크에 보낸 요청 수. transfers per second(초당 전송 수)의 줄임 |
kB_read/s · kB_wrtn/s |
초당 읽은 양과 쓴 양. 단위는 킬로바이트 |
kB_read · kB_wrtn |
그 간격 동안 읽은 양과 쓴 양의 합 |
이 다섯 칸은 디스크가 일을 얼마나 했는지만 말합니다. 그 일을 버거워하는지는 말하지 않습니다.
같은 초당 50 메가바이트가 어느 디스크에는 여유입니다. 다른 디스크에는 한계입니다. 버거운지를 보려면 대기 시간과 대기열 길이가 필요합니다. 이 두 값은 -x 옵션을 붙여야 나옵니다.
확장 통계
-x 를 붙이면 장치 줄에 칸이 크게 늘어납니다. 진단에 주로 쓰는 칸은 아래와 같습니다. 읽기는 앞에 r, 쓰기는 앞에 w 가 붙습니다.
| 칸 | 뜻 |
|---|---|
r/s · w/s |
초당 끝낸 읽기 요청 수와 쓰기 요청 수 |
rkB/s · wkB/s |
초당 읽은 양과 쓴 양 |
rrqm/s · wrqm/s |
초당 대기열에서 이웃 요청과 합쳐진 요청 수 |
r_await · w_await |
요청 하나가 대기열에서 기다린 시간과 처리된 시간을 더한 평균. 단위는 밀리초 |
rareq-sz · wareq-sz |
요청 하나의 평균 크기. 단위는 킬로바이트 |
aqu-sz |
그 간격 동안 대기열에 있거나 디스크가 처리 중이던 요청 수의 평균 |
%util |
그 간격 가운데 디스크에 처리 중인 요청이 하나라도 있던 시간의 비율 |
aqu-sz 는 옛 판에서 avgqu-sz 라는 이름으로 찍힙니다. 옛 판에는 읽기와 쓰기를 합친 await 칸도 있습니다. 뜻은 같습니다.
아래는 iostat -xz 1 의 둘째 출력에서 칸 일부만 추린 모양입니다. -z 를 붙여 그동안 일이 없던 디스크는 뺐습니다. 값은 예로 지어낸 것입니다.
Device r/s w/s r_await w_await aqu-sz %util
sda 2.0 180 0.5 25.0 4.50 99.0
nvme0n1 900 300 0.2 0.1 0.21 85.0
sda 는 초당 쓰기 180 번을 받고 있습니다. w_await 를 보면 쓰기 하나가 끝나기까지 평균 25.0 밀리초가 걸립니다. 대기열에 있거나 처리 중인 요청은 늘 네댓 개입니다. 디스크가 일을 못 따라가 요청이 쌓이는 모양입니다.
nvme0n1 은 요청을 그보다 몇 배 많이 받습니다. 그런데도 두 대기 시간 칸이 1 에도 못 미칩니다. 대기열 길이도 0.21 로 거의 비어 있습니다.
두 디스크 모두 %util 이 높습니다. 그런데 사정은 정반대입니다. 이 차이는 아래 「%util 을 잘못 읽는 경우」 소절에서 봅니다.
대기열 길이와 대기 시간의 관계
aqu-sz 와 대기 시간은 따로 노는 값이 아닙니다. 대기열에 있거나 처리 중인 요청 수의 평균은 초당 들어오는 요청 수에 요청 하나가 머무는 시간을 곱한 것과 같습니다. 줄 서기에 두루 맞는 이 관계를 리틀의 법칙이라 부릅니다.
위 sda 줄로 확인해 보면 이렇습니다. 쓰기 쪽은 초당 180 번에 0.025 초씩 머무니 4.5 입니다. 읽기 쪽은 거의 0 입니다. 둘을 더하면 aqu-sz 4.50 과 맞습니다.
세 값 가운데 둘을 알면 나머지 하나를 어림할 수 있습니다. 초당 요청 수가 그대로인데 대기열이 길어졌다면 요청 하나가 머무는 시간이 늘어난 것입니다.
%util 을 잘못 읽는 경우
%util 은 디스크가 요청을 하나라도 처리하던 시간의 비율입니다. 요청을 몇 개 동시에 처리했는지는 이 값에 안 들어갑니다.
이렇게 디스크가 일한 시간의 비율을 사용률이라 합니다. 사용률과 짝을 이루는 말이 포화입니다. 디스크가 받을 수 있는 양을 넘어 일이 줄을 서기 시작한 상태를 가리킵니다. iostat 에서 포화를 말하는 칸은 %util 이 아니라 aqu-sz 와 대기 시간입니다.
하드 디스크처럼 요청을 하나씩 처리하는 장치라면 이 값이 꽉 찬 것이 곧 한계입니다. 읽는 헤드 하나가 한 위치씩 옮겨 다니기 때문에 처리 중인 요청이 늘 하나 있으면 더 받을 틈이 없습니다.
위 sda 가 그런 경우입니다. %util 이 꽉 찼습니다. 대기열도 길고 대기 시간도 깁니다.
SSD(Solid State Drive, 반도체 저장 장치)는 다릅니다. 특히 NVMe(Non-Volatile Memory Express)로 붙은 SSD 는 요청 여러 개를 동시에 처리합니다.
요청이 늘 하나라도 걸려 있으면 %util 은 꽉 찹니다. 그래도 장치는 동시에 더 받을 여유가 남아 있을 수 있습니다.
여러 디스크를 묶어 하나처럼 쓰는 RAID(Redundant Array of Independent Disks, 복수 디스크 묶음)도 같습니다. 묶인 디스크들이 따로따로 요청을 처리하기 때문입니다.
그래서 이런 장치에서는 %util 만 보고 포화라고 판단하지 않습니다. 대기 시간이 늘고 대기열이 길어지는지를 함께 봅니다.
성능 분석에서는 자원마다 사용률과 포화를 차례로 봅니다. 여기에 오류까지 더해 세 가지를 차례로 보는 점검 순서가 있습니다. 세 낱말의 영어 머리글자 USE(Utilization · Saturation · Errors)를 이름에 단 USE 방법입니다.
자주 붙이는 옵션
iostat 는 옵션 글자로 무엇을 볼지 고릅니다. 아래 표는 리눅스 기준입니다.
| 옵션 | 하는 일 |
|---|---|
-x |
확장 통계를 찍는다 |
-d · -c |
장치 줄만 · CPU 줄만 |
-z |
그 간격 동안 일이 없던 장치는 빼고 찍는다 |
-y |
부팅 뒤 평균인 첫 출력을 뺀다 |
-k · -m |
양을 킬로바이트로 · 메가바이트로 |
-t |
출력마다 찍은 시각을 붙인다 |
-p |
디스크 아래의 파티션까지 줄을 나눠 찍는다 |
-N |
LVM(Logical Volume Manager, 논리 볼륨 관리자)이 만든 장치를 커널이 붙인 dm-0 같은 번호 이름 대신, 볼륨을 만들 때 지어 준 이름으로 찍는다 |
옵션은 묶어서 한 번에 씁니다. 장치 이름을 끝에 적으면 그 장치만 봅니다.
iostat -xz 1 # 일하는 장치만 1초마다
iostat -xz 1 sda # sda 하나만
iostat -xzt 5 # 5초마다, 시각을 붙여
맥이나 BSD(Berkeley Software Distribution) 계열에도 iostat 라는 명령이 있습니다. 하지만 칸 이름과 옵션이 리눅스와 달라서 위 표가 맞지 않습니다.
진단 장면
데이터베이스 쿼리가 갑자기 느려졌고 CPU 사용률은 낮다고 해 봅시다. 디스크를 의심할 때 iostat 로 보는 순서는 아래와 같습니다.
flowchart TD
A["iostat -xz 1 · 둘째 출력부터 본다"] --> B{"대기 시간이 평소보다 길고 대기열이 쌓이나"}
B -->|"아니다"| C["디스크는 따라가고 있다 · 다른 자원을 본다"]
B -->|"그렇다"| D{"초당 요청 수나 양이 평소보다 많나"}
D -->|"그렇다"| E["일이 몰렸다 · 누가 쓰는지 찾는다"]
D -->|"아니다"| F["같은 일에 디스크가 느려졌다 · 장치를 의심한다"]
첫 갈림에서는 %util 이 아니라 대기 시간과 aqu-sz 를 봅니다. 앞 소절에서 본 대로 %util 은 SSD 에서 포화를 가리지 못하기 때문입니다.
기준이 디스크마다 달라서 그 디스크의 평소 값과 견줍니다. 같은 서버의 한가한 때 값을 미리 알아 두면 비교가 빨라집니다.
일이 몰렸다면 다음은 어느 프로세스가 디스크를 쓰는지 찾는 일입니다. iostat 는 장치 단위로만 세서 이걸 못 보여 줍니다.
이때는 pidstat 에 -d 를 붙이거나 iotop 을 씁니다. 둘 다 프로세스마다 읽고 쓴 양을 보여 줍니다.
요청 수가 그대로인데 대기 시간만 늘었다면 장치 쪽을 봅니다. 디스크가 고장 나기 시작했거나, 클라우드 디스크가 정해진 처리 한도에 닿아 속도를 늦춘 경우가 여기 듭니다.
vmstat · sar · iotop 과 나눠 맡는 일
iostat 가 보여 주는 것은 장치 단위의 바쁜 정도입니다. 같은 서버를 보는 다른 명령과는 아래처럼 나눠 맡습니다.
| 명령 | 알려 주는 것 |
|---|---|
| iostat | 디스크마다 요청 수 · 양 · 대기 시간 · 대기열 길이 |
| vmstat | 메모리 · 스와핑 · 실행 대기열 · CPU 를 한 줄에 모아 |
| sar | iostat 와 비슷한 값을 주기적으로 모아 두었다가 지난 시각의 것을 다시 보여 준다 |
| pidstat · iotop | 프로세스마다 읽고 쓴 양 |
iostat 와 sar 와 pidstat 는 모두 sysstat 묶음에 들어 있습니다. iostat 가 있는 서버에는 대개 나머지 둘도 있습니다. sysstat 는 배포판에 따라 기본으로 깔려 있지 않습니다. 그런 서버에서 iostat 를 치면 명령이 없다는 에러가 납니다.
관련 항목
iostat 가 줄마다 세는 대상
디스크 · 하드 디스크 · SSD · NVMe · RAID · LVM · 파티션 · 블록 장치 · CPU
iostat 칸이 재는 지표
IOPS · 처리량 · 지연 · 큐 길이 · 사용률 · 포화 · iowait · CPU steal
iostat 가 읽는 커널 쪽 부품
블록 계층 · I-O 스케줄러 · 페이지 캐시 · procfs · 커널 · 디바이스 드라이버
iostat 와 진단을 나눠 맡는 명령과 그 도구 묶음
sysstat · vmstat · sar · pidstat · iotop · top · netstat · blktrace
iostat 숫자를 해석하는 방법론과 법칙
USE 방법 · 리틀의 법칙 · 대기 행렬 이론 · 성능 · 병목
iostat 로 짚어 내는 서버 문제
다른 이름: input/output statistics · iostat 명령 · 아이오스탯