Ba con số quen thuộc ở góc trên top hay uptimeload average: 2.31, 1.84, 0.97 — là thứ ai cũng liếc để đoán máy "bận" hay "rảnh". Nhưng chúng đo cái gì chính xác? Hầu hết mọi người tin "load = mức bận CPU", và trên Linux điều đó sai theo một cách có thể dẫn tới chẩn đoán lệch hẳn. Bài này đo load average phản ứng thế nào với hai loại tải khác nhau — và phát hiện hai lầm tưởng của chính tôi về con số quen thuộc này.

Load average

Load average đo cái gì

Load average là số tiến trình trung bình đang "cần chạy" trong 1, 5, và 15 phút vừa qua. Nhưng "cần chạy" gồm những gì? Đây là chỗ Linux khác các Unix khác. Trên hầu hết hệ thống, load chỉ đếm tiến trình R-state — đang chạy hoặc đang chờ CPU (nhu cầu CPU thuần túy). Nhưng Linux đếm thêm các tiến trình D-state — "uninterruptible sleep", ngủ không ngắt được, thường là đang kẹt chờ I/O (đọc/ghi đĩa, NFS, thiết bị chậm).

Nghĩa là trên Linux, load = (số tiến trình dùng/chờ CPU) + (số tiến trình kẹt chờ I/O). Một hệ quả lớn: load cao không chắc là CPU quá tải — nó có thể là một đống tiến trình đang đứng chờ một cái đĩa chậm, trong khi CPU nhàn rỗi. Và có một đặc tính nữa: load là trung bình trượt làm mượt theo hàm mũ (hằng số thời gian ~1 phút cho con số 1-phút), nên nó dâng và hạ chậm, không phản ánh tức thời. Tôi muốn đo cả hai điều này.

Đo: load dâng chậm, và D-state cũng đẩy load

Máy đo có 10 nhân, load lúc rảnh ~0,26. Kịch bản A: tôi bật 4 tiến trình ngốn CPU (vòng lặp bận, ở trạng thái R), rồi đọc load 1-phút sau mỗi 20 giây:

t=20s: load 1,32   t=40s: load 2,08   t=60s: load 2,63   (đang bò dần về 4)
CPU busy = 41% (4 tiến trình trên 10 nhân)

Điều đầu tiên đập vào mắt: load không nhảy ngay lên 4. Nó bò từ từ — 1,32 rồi 2,08 rồi 2,63 — và sau một phút vẫn chưa tới 4, vì phép làm mượt hàm mũ cần vài phút để hội tụ. Đúng như kỳ vọng "4 tiến trình cần CPU trên máy dư nhân": CPU chỉ bận 41% (bốn phần mười số nhân), và load tiến tới 4.

Kịch bản B: tôi bật 4 tiến trình kẹt chờ I/O — ghi đĩa đồng bộ qua một thiết bị bị bóp băng thông xuống 512 KB/s, nên mỗi tiến trình dành gần hết thời gian ở trạng thái D, đứng chờ ghi:

load tăng thêm ~4 (ví dụ 4,04 -> 8,34), cả 4 tiến trình đều ở trạng thái D

Đây mới là điều đáng nói. Bốn tiến trình này không tính toán gì — chúng chỉ nằm chờ I/O hoàn tất, ngủ không ngắt được. Vậy mà chúng đẩy load average tăng thêm 4, y như bốn tiến trình ngốn CPU. Load không phân biệt "bận CPU" với "kẹt chờ I/O" — nó đếm cả hai như nhau.

Một lần tôi đo hớ: hai lầm tưởng về load

Tôi vấp hai lần hớ trong bài này. Lần thứ nhất, khi bắt đầu: tôi bật các tiến trình rồi đọc load ngay, thấy nó vẫn thấp, và suýt kết luận "load không phản ứng với tải này". Sai — vì load là trung bình trượt làm mượt ~1 phút, không phải giá trị tức thời. Phải quan sát đủ dài (một phút) mới thấy nó dâng về giá trị thật; đọc ngay lập tức là đo một con số còn đang "nhớ" quá khứ. Đây đúng là kỷ luật "đo hiện tượng có mốc thời gian phải quan sát đủ dài" — load có một hằng số thời gian, và bỏ qua nó là đọc sai.

Lần thứ hai, sâu hơn: tôi mặc định "load cao nghĩa là CPU đang quá tải". Kịch bản B bác bỏ thẳng: 4 tiến trình kẹt chờ đĩa, CPU không hề bão hòa, mà load vẫn tăng 4. Trên Linux, load đếm cả tiến trình D-state, nên một máy load 8 có thể đang ở hai tình huống hoàn toàn khác nhau: CPU cháy với 8 tiến trình tranh nhau tính toán, hoặc CPU nhàn rỗi với 8 tiến trình đứng chờ một hệ thống lưu trữ nghẽn. Cùng một con số load, hai căn bệnh trái ngược, hai cách chữa khác hẳn. Bài học đo lường: một con số tổng hợp như load average gộp nhiều hiện tượng vào một; nhìn nó một mình có thể dẫn tới chẩn đoán sai — phải bóc ra CPU% và trạng thái tiến trình (R so với D) mới biết bệnh gì.

(Có một chi tiết đo lường nhỏ tôi cũng gặp: load "có trí nhớ" — nó lì và decay chậm, nên các phép đo liên tiếp của tôi còn dính load của lần trước, làm số nền trôi từ 0,26 lên mấy đơn vị. Càng nhắc: hiện tượng có mốc thời gian phải để lắng đủ giữa các lần đo, không chỉ quan sát đủ dài lúc đo.)

Vì sao điều này quan trọng khi lập trình

Hệ quả đầu tiên là đừng chẩn đoán "CPU quá tải" chỉ từ load average. Khi thấy load cao, câu hỏi tiếp theo phải là "cao vì CPU hay vì I/O?". Xem CPU% (từ top, mpstat): nếu CPU gần 100% thì đúng là nghẽn CPU; nếu CPU nhàn mà load vẫn cao, thủ phạm là I/O — tìm các tiến trình D-state (ps cột STAT có D) đang chờ đĩa/mạng. Nhầm hai cái này là đi mua thêm CPU cho một vấn đề I/O, hoặc ngược lại.

Hệ quả thứ hai là so load với số nhân, và nhớ nó trễ. Load 4 trên máy 1 nhân là quá tải nặng; load 4 trên máy 16 nhân là nhàn. Luôn chia load cho số nhân để biết mức bận tương đối. Và vì load làm mượt theo phút, nó trễ so với thực tế: một đợt tải đột ngột phải vài phút mới hiện đủ vào con số 1-phút, còn con số 15-phút thì mượt tới mức chỉ hợp để nhìn xu hướng dài. Muốn thấy tình trạng ngay lúc này, đừng nhìn load — nhìn CPU% và độ dài hàng đợi tức thời.

Hệ quả thứ ba, về đo lường: một chỉ số tổng hợp có thể trộn lẫn nhiều nguyên nhân — đừng đọc nó một mình. Con số mang theo: trên Linux, load average đếm CẢ tiến trình dùng/chờ CPU (R-state) LẪN tiến trình kẹt chờ I/O không ngắt được (D-state) — nên 4 tiến trình chờ đĩa đẩy load lên 4 dù CPU rảnh, y như 4 tiến trình ngốn CPU; và vì là trung bình trượt làm mượt ~1 phút, load dâng/hạ chậm và trễ. Load cao là tín hiệu "có gì đó đang chờ nhiều", không phải "CPU đang cháy"; muốn biết bệnh gì, phải bóc CPU% và trạng thái R/D.

Thử ba mươi giây

Xem load và bóc nguyên nhân: uptime cho ba con số load; nproc cho số nhân để chia; rồi top (hay mpstat 1) xem CPU% có thật sự cao không. Đếm tiến trình đang kẹt I/O: ps -eo stat,comm | grep '^D' — mỗi dòng bắt đầu bằng D là một tiến trình ngủ không ngắt được đang góp vào load mà không dùng CPU. Nếu load cao nhưng CPU% thấp và có nhiều D, bạn đang gặp nghẽn I/O chứ không phải nghẽn CPU. Và nhớ: bật một tải rồi nhìn load ngay sẽ thấy nó chưa phản ứng — chờ một phút, vì nó là trung bình trượt, đúng cái độ trễ bài này đo.