I/O, Lưu trữ & Tài nguyên/Đo I/O bằng iostat — và vì sao %util nói dối
22/26
Bài 22 / 26~14 phútTài nguyên & Đo lườngMiễn phí lượt xem

Đo I/O bằng iostat — và vì sao %util nói dối

await, độ sâu hàng đợi và %util nghĩa là gì; vì sao %util 100% trên NVMe không có nghĩa thiết bị bão hoà; cách chỉ đúng mặt tiến trình đang nghiến đĩa.

TL;DR: %util chỉ đo tỉ lệ thời gian có ít nhất một yêu cầu đang chạy trên thiết bị, chứ không đo phần công suất đã dùng hết. Với ổ quay chỉ phục vụ được một việc một lúc thì hai thứ đó trùng nhau, nên thói quen cũ vẫn đúng. Với NVMe hay RAID phục vụ hàng chục yêu cầu song song thì %util chạm 100% từ rất sớm trong khi thiết bị còn thừa sức, và chính man page iostat(1) nói rõ điều đó. Hai chỉ số thay thế là await (một yêu cầu mất bao lâu, gồm cả thời gian nằm chờ) và aqu-sz (trung bình có bao nhiêu yêu cầu đang xếp hàng).

Dashboard báo đỏ: "disk utilization 98%". Cả nhóm chuẩn bị đề xuất nâng cấp ổ. Rồi ai đó mở iostat -x ra và thấy await là 0,3 mili-giây — nhanh gấp mấy chục lần một ổ cứng quay lúc nhàn nhất.

Cả hai con số đều đúng. Chúng chỉ đang trả lời hai câu hỏi khác nhau, và câu hỏi mà %util trả lời thì không còn mấy ý nghĩa trên phần cứng mười năm nay.

1. Analogy — một quầy thu ngân và ba mươi hai quầy

Một cửa hàng tạp hoá có đúng một quầy thu ngân. Nếu suốt tám tiếng lúc nào cũng có người đứng ở quầy, bạn kết luận ngay: quầy chạy hết công suất, muốn phục vụ nhanh hơn thì phải mở thêm quầy. Kết luận đó đúng, vì "có người ở quầy" và "quầy hết công suất" là cùng một chuyện khi chỉ có một quầy.

Bây giờ đổi sang siêu thị 32 quầy. Camera trần nhà chỉ ghi được một điều: có ít nhất một quầy đang phục vụ. Con số ấy chạm 100% cả ngày, kể cả những lúc 31 quầy trống trơn. Nó không sai, nó chỉ không còn nói được gì về mức bận.

Muốn biết siêu thị có quá tải không thì phải hỏi hai câu khác: một khách mất bao lâu từ lúc xếp hàng tới lúc xong, và trung bình có bao nhiêu người đang đứng chờ.

Ở siêu thịTrong hệ thống
Có ít nhất một quầy đang phục vụ%util
Thời gian trọn vẹn của một khách, tính cả lúc xếp hàngawait
Số người đang đứng chờ trung bìnhaqu-sz
Số quầy mởĐộ sâu hàng đợi phần cứng

Cung mot muc util nhung mot thiet bi qua tai con thiet bi kia con thua suc

2. Đọc một dòng iostat -x

iostat -x 1

Tham số 1 bảo nó in lại mỗi giây. Bỏ luôn dòng đầu tiên — dòng đó là trung bình kể từ lúc máy khởi động, tức là trung bình của cả những giờ máy ngồi chơi, nên nó lúc nào cũng êm đẹp tới mức vô dụng.

Bốn cột đáng đọc:

  • r/s, w/s — số thao tác đọc và ghi mỗi giây. Đây là IOPS, và nó khác hẳn rMB/s với wMB/s: 1.000 thao tác 4 KB và 4 thao tác 1 MB cùng cho 4 MB/s nhưng đặt lên thiết bị hai gánh nặng khác nhau hoàn toàn.
  • await — thời gian trung bình của một yêu cầu tính bằng mili-giây, gồm cả quãng nằm chờ trong hàng đợi chứ không chỉ quãng thiết bị làm việc. Đây là con số gần nhất với thứ người dùng cảm nhận.
  • aqu-sz — độ sâu hàng đợi trung bình, tức trung bình có bao nhiêu yêu cầu đang nằm trong hệ thống.
  • %util — tỉ lệ thời gian thiết bị có ít nhất một yêu cầu đang chạy.

Ba cột đầu ràng buộc nhau bằng một quan hệ đơn giản mà rất đáng nhớ:

aqu-sz  ~=  (r/s + w/s)  x  await

Số thao tác mỗi giây nhân với thời gian sống của mỗi thao tác thì ra số thao tác đồng thời. Quan hệ này hữu ích theo hai chiều: nó cho bạn kiểm tra chéo các con số vừa đọc, và nó cho thấy aqu-sz cao không nhất thiết là chuyện xấu — thiết bị nhanh với lưu lượng lớn cũng ra hàng đợi dài, miễn await vẫn thấp.

3. Vì sao %util nói dối trên NVMe?

Vì định nghĩa của nó chỉ có một bit thông tin: tại thời điểm lấy mẫu, có yêu cầu nào đang chạy không? Kernel cộng dồn khoảng thời gian trả lời "có", rồi chia cho tổng thời gian.

Trên ổ quay, thiết bị chỉ làm được một việc một lúc, nên "có việc" đúng bằng "hết công suất" và chỉ số này lành mạnh suốt hai mươi năm. Trên NVMe thì tiền đề ấy sập: chuẩn cho phép hàng chục nghìn hàng đợi, mỗi hàng đợi hàng chục nghìn ô, và thiết bị phục vụ rất nhiều yêu cầu cùng lúc. Một yêu cầu duy nhất chạy liên tục cũng đủ đẩy %util lên 100% trong khi phần cứng mới dùng vài phần trăm năng lực thật.

Chuyện này không phải suy đoán của cộng đồng: man page iostat(1) ghi thẳng rằng %util mất ý nghĩa với các thiết bị phục vụ nhiều yêu cầu song song. Chỉ số này không hỏng, nó chỉ đo một thứ mà ta đã hết cần đo.

Nhớ lại HDD, SSD và NVMe

Bảng số hàng đợi ở bài đó chính là gốc rễ ở đây: SATA cho một hàng đợi 32 ô, còn NVMe cho tới 64 nghìn hàng đợi. Chỉ số sinh ra cho phần cứng một-việc-một-lúc thì không sống sót qua thay đổi đó.

Vậy dấu hiệu bão hoà thật trông thế nào? Là await leo lên trong khi số thao tác mỗi giây đứng yên hoặc đi xuống. Thiết bị đã tới trần, yêu cầu mới chỉ còn biết xếp hàng, nên thời gian chờ phình ra mà thông lượng thì không nhúc nhích. Kèm theo đó, aqu-sz tăng đều đặn.

Ba con số này nói về thiết bị, chưa nói ai gây ra. Muốn gọi tên thủ phạm thì pidstat -d 1 cho lượng đọc ghi theo từng tiến trình, còn iotop -o bày danh sách theo thời gian thực và chỉ hiện tiến trình đang thật sự có I/O.

4. Tự điền — thiết bị nào đang bão hoà thật?

Bảng dưới là bảng dựng để so sánh, không phải log đo được trên một máy cụ thể; các con số được chọn để hai trường hợp đối lập nhau rõ nhất. Máy có hai thiết bị: sda là ổ SATA, nvme0n1 là NVMe.

Device     r/s    w/s  rMB/s  wMB/s  await  aqu-sz  %util
sda        5.2   12.0    0.3    0.8  120.5     1.8    87.0
nvme0n1  850.0  430.0   3300   1700    0.3     0.4    98.0

Ket luan cua ban:
  sda      -> ______________   vi ______________
  nvme0n1  -> ______________   vi ______________
Tự điền trước khi xem đáp án

Ba câu hỏi định hướng, không phải đáp án. Thứ nhất: một yêu cầu trên sda mất 120 mili-giây, con số đó nằm ở thang nào so với thời gian phục vụ của chính loại thiết bị ấy? Thứ hai: nhân số thao tác mỗi giây với await cho từng dòng, kết quả có khớp cột aqu-sz không, và nó nói gì về việc yêu cầu đang chờ hay đang được phục vụ? Thứ ba: nếu bỏ hẳn cột %util đi thì bạn còn kết luận được không? Viết hai kết luận của bạn ra trước khi đọc tiếp.

Đáp án cho sda: bão hoà thật. Một yêu cầu mất 120 mili-giây, trong khi một ổ SATA khoẻ mạnh phục vụ một thao tác ngẫu nhiên trong khoảng vài tới trên chục mili-giây. Phần chênh lệch ấy là thời gian nằm chờ. Kiểm chéo bằng quan hệ ở mục 2: (5,2 + 12,0) × 0,1205 s ≈ 2,1, xấp xỉ cột aqu-sz là 1,8 — nghĩa là gần như lúc nào cũng có khoảng hai yêu cầu trong hệ thống, mà thiết bị này chỉ phục vụ được một cái một lúc. Đáng chú ý là %util của nó chỉ có 87%, thấp hơn thiết bị khoẻ mạnh bên dưới.

Đáp án cho nvme0n1: chưa hề bão hoà, dù %util là 98%. await 0,3 mili-giây nghĩa là yêu cầu vào rồi ra gần như tức thì; (850 + 430) × 0,0003 s ≈ 0,38 khớp cột aqu-sz 0,4, tức là phần lớn thời gian trong hệ thống chỉ có chưa tới một yêu cầu. Thiết bị đọc ghi gần 5 GB mỗi giây mà chẳng ai phải chờ ai. Con số 98% chỉ nói rằng nó hiếm khi được nghỉ.

Câu hỏi thứ ba là câu quan trọng nhất: bỏ cột %util đi thì hai kết luận trên không đổi một chữ. Đó chính là thước đo giá trị thật của một chỉ số.

5. Pitfall của riêng concept này

Nhầm 1 — báo động vì %util chạm 100% trên NVMe.

✅ Chỉ số đó bão hoà rất sớm trên thiết bị phục vụ song song. Xem awaitaqu-sz rồi hãy kết luận; con số 100% một mình không đủ để đề xuất mua thiết bị mới.

Nhầm 2 — đọc dòng đầu của iostat rồi kết luận.

✅ Dòng đầu là trung bình từ lúc khởi động, gồm cả những giờ máy ngồi chơi. Luôn chạy dạng lặp và bỏ dòng đầu.

Nhầm 3 — chỉ nhìn MB/s để đánh giá tải.

✅ Băng thông và IOPS là hai gánh nặng khác nhau. Một triệu thao tác 4 KB làm ngộp thiết bị ở mức MB/s rất khiêm tốn, và ngược lại vài luồng tuần tự lớn có thể đầy băng thông với IOPS thấp.

Nhầm 4 — thấy await cao rồi đổ ngay cho phần cứng.

await gồm cả thời gian nằm chờ, nên nó cao khi tầng trên đẩy xuống quá nhiều yêu cầu, chứ chưa chắc thiết bị đã yếu. So await với thông lượng: nếu số thao tác mỗi giây vẫn tăng theo thì thiết bị vẫn còn dư địa, và thứ cần sửa là bên gửi.

6. 📚 Đào sâu (tuỳ chọn)

📚 Nguồn kỹ thuật
  • iostat(1) — man7.org — định nghĩa từng cột, kèm chính lời cảnh báo rằng %util mất ý nghĩa với thiết bị phục vụ nhiều yêu cầu song song.
  • Pressure Stall Information — docs.kernel.org — chỉ số áp lực I/O tính theo mức độ công việc bị đình trệ, thay vì theo mức bận của thiết bị.

PSI đáng đọc ngay sau bài này vì nó đo đúng thứ %util không đo được: bao nhiêu phần công việc đang bị đình lại vì I/O. Đó là góc nhìn từ phía người chờ, chứ không phải từ phía thiết bị.

7. Liên hệ các bài khác

8. Tóm tắt

  • Luôn chạy iostat -x 1 rồi bỏ dòng đầu; dòng đó là trung bình từ lúc khởi động.
  • %util trả lời "thiết bị có việc bao nhiêu phần thời gian", một câu hỏi đã hết ý nghĩa từ khi phần cứng phục vụ song song.
  • Dấu hiệu bão hoà thật là await leo lên trong khi số thao tác mỗi giây đứng yên.
  • Quan hệ aqu-sz ≈ IOPS × await cho phép kiểm chéo ba cột, và giải thích vì sao hàng đợi dài chưa chắc là xấu.
  • iostat chỉ nói về thiết bị; pidstat -diotop -o mới gọi được tên tiến trình.

9. Tự kiểm tra

Tự kiểm tra
Q1
Một NVMe hiện %util 100% suốt giờ cao điểm, await 0,4 ms, aqu-sz 0,6. Sếp hỏi có cần mua ổ nhanh hơn không. Bạn trả lời thế nào và dựa vào đâu?

Chưa cần. await 0,4 mili-giây nghĩa là yêu cầu vào ra gần như tức thì, còn aqu-sz 0,6 nghĩa là trung bình còn chưa tới một yêu cầu nằm trong hệ thống — không ai phải chờ ai. Thiết bị đang có việc gần như liên tục, nhưng có việc không đồng nghĩa hết sức.

%util chỉ đếm tỉ lệ thời gian có ít nhất một yêu cầu đang chạy, và chính man page iostat(1) cảnh báo chỉ số này mất ý nghĩa với thiết bị phục vụ song song. Bằng chứng cần trước khi mua ổ mới là await tăng trong khi IOPS ngừng tăng.

Q2
Vì sao dấu hiệu bão hoà đáng tin nhất là await tăng trong khi IOPS đứng yên, chứ không phải await tăng một mình?

await gồm cả thời gian nằm chờ trong hàng đợi. Nó tăng vì hai lý do hoàn toàn khác nhau: thiết bị đã hết sức, hoặc tầng trên vừa đẩy xuống nhiều yêu cầu hơn trong khi thiết bị vẫn còn dư địa.

Cột IOPS phân xử hai khả năng đó. Nếu số thao tác mỗi giây còn tăng theo thì thiết bị vẫn đang nhận thêm việc và làm được, tức chưa tới trần. Nếu IOPS phẳng ra hoặc đi xuống trong khi await leo, thì mọi yêu cầu thêm vào chỉ còn biết xếp hàng — đó mới là bão hoà đúng nghĩa.

Q3
Hai thiết bị cùng đạt 4 MB/s. Thiết bị A phục vụ 1.000 thao tác 4 KB mỗi giây, thiết bị B phục vụ 4 thao tác 1 MB. Vì sao chỉ nhìn MB/s là không đủ để so tải?

Vì cái tốn kém với thiết bị lưu trữ là số thao tác, không phải số byte. Mỗi thao tác kéo theo chi phí cố định ở nhiều tầng: dựng yêu cầu, xếp hàng, định vị trên thiết bị, báo hoàn tất. Nghìn thao tác nhỏ trả chi phí đó một nghìn lần.

Thiết bị A vì thế có thể đã sát trần IOPS trong khi con số MB/s trông rất khiêm tốn, còn B thì mới dùng một phần nhỏ năng lực. Muốn so đúng thì đọc r/s với w/s cạnh rMB/s với wMB/s, và nhìn await để biết thiết bị đang chịu đựng thế nào.

Q4
Ứng dụng của bạn đọc file liên tục nhưng iostat gần như không thấy hoạt động nào trên thiết bị. Giải thích, và bạn kiểm chứng bằng cách nào?

Phần lớn lần đọc đang được page cache phục vụ thẳng từ RAM, nên yêu cầu chưa bao giờ xuống tới block layer để iostat nhìn thấy. Đó là chuyện lành mạnh, và cũng chính là lý do mọi benchmark ổ đĩa không xoá cache đều đang đo RAM.

Kiểm chứng bằng cách xoá page cache rồi đo lại: nếu iostat lập tức sáng lên còn ứng dụng chậm hẳn đi, kết luận được xác nhận. Muốn nhìn từ phía tiến trình thì pidstat -d 1 cho thấy lượng đọc thật sự chạm tới thiết bị.

Q5
Một ổ SATA có await 120 ms, aqu-sz 1,8 và %util 87%. Vì sao con số %util thấp hơn NVMe khoẻ mạnh mà tình trạng lại tệ hơn hẳn?

%util đo thời gian thiết bị có việc, chứ không đo mức khổ sở của yêu cầu. Ổ SATA này chỉ phục vụ được một thao tác một lúc, nên với aqu-sz 1,8 thì luôn có yêu cầu nằm chờ tới lượt, và await 120 mili-giây phần lớn là thời gian chờ chứ không phải thời gian làm việc.

NVMe kia thì phục vụ hàng loạt yêu cầu song song với await nhỏ hơn hàng trăm lần; con số 98% chỉ nói nó hiếm khi được nghỉ. So hai thiết bị bằng %util là so hai đại lượng không cùng ý nghĩa — trục đúng để so là await cạnh thông lượng.

Bài tiếp theo: cgroups — trần CPU, RAM và I/O của một container

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

cgroups — trần CPU, RAM và I/O của một container