Hình dung một cái nhiệt kế báo "nhiệt độ trung bình mười lăm phút qua" thay vì nhiệt độ ngay lúc này. Bạn bước vào một căn phòng vừa nguội, nhiệt kế vẫn hiện 40°C — không phải nó hỏng, mà vì nó đang kể chuyện vừa rồi, không phải bây giờ. Và giả sử cái nhiệt kế ấy còn cộng thêm cả số người đang xếp hàng chờ thang máy ngoài sảnh vào con số "độ nóng" của phòng — thì đọc nó mà tưởng là nhiệt độ thuần thì sai kép. Load average của Linux đúng là cái nhiệt kế lạ đời đó, và bài này đo cả hai chỗ nó đánh lừa.

uptime trả về ba con số mà gần như ai cũng đọc sai. Ta đo xem chúng thực sự đếm cái gì.

Tải đĩa thuần làm load tăng, CPU thì không

8 tiến trình đọc đĩa ngẫu nhiên trên máy 16 nhân:

t=  0s   load1 = 8,80
t= 15s   load1 = 9,38    us=1%  wa=47%  b=8
t= 40s   load1 = 10,68   us=4%  wa=47%  b=8
t= 70s   load1 = 12,88   us=7%  wa=45%  b=8

Load tăng 4 đơn vị trong khi CPU người dùng gần như bằng không. Nếu bạn đọc load average như "phần trăm CPU", con số 12,88 trên máy 16 nhân nghe như CPU đang bận 80%. Thực tế CPU đang rảnh 45–50% và ngồi chờ đĩa.

Vì sao: Linux đếm cả tiến trình đợi đĩa

Trên Unix truyền thống, load average đếm số tiến trình sẵn sàng chạy — tức đang đợi CPU. Linux đếm thêm một nhóm nữa: tiến trình ở trạng thái D — ngủ không ngắt được, gần như luôn là đang đợi I/O. Đó là những người xếp hàng chờ thang máy được cộng vào "độ nóng" của phòng.

Cột b trong vmstat chính là nhóm đó: 8 tiến trình fio, và load tăng đúng khoảng đó. Đây là quyết định thiết kế của Linux từ những năm 1990, và nó khiến load average trở thành thước đo mức độ tắc nghẽn nói chung thay vì thước đo riêng CPU. Hữu ích khi bạn hiểu, gây hiểu nhầm khi không.

Và nó là lịch sử, không phải hiện tại

Sau khi tắt hoàn toàn mọi tải:

t=+  0s   load 1/5/15 = 12,65  10,52  9,15   wa=0  us=1
t=+ 15s   load 1/5/15 = 12,19  10,54  9,18   wa=0  us=2
t=+ 30s   load 1/5/15 = 11,52  10,47  9,18   wa=0  us=3
t=+ 60s   load 1/5/15 = 10,38  10,30  9,17   wa=0  us=1
t=+ 90s   load 1/5/15 =  9,42  10,05  9,12   wa=0  us=7

Ngay tại thời điểm tải dừng, load vẫn là 12,65 trên một máy hoàn toàn rảnh. Chín mươi giây sau, nó vẫn 9,42. Ba con số là trung bình trượt hàm mũ với hằng số thời gian 1, 5 và 15 phút. Chúng nói vừa rồi thế nào, không nói bây giờ thế nào.

Hệ quả thực hành: load average vô dụng cho việc chẩn đoán tức thời. Khi bạn đăng nhập vào máy đang có sự cố, load cao có thể là dấu vết của một sự cố đã kết thúc năm phút trước. Muốn biết hiện tại thì đọc vmstat 1, như phần trước.

Cái ba con số dùng để làm

Chúng có ích cho một việc duy nhất mà vmstat không làm được: cho biết xu hướng.

load 1/5/15 = 12,65  10,52  9,15

1 phút > 5 phút > 15 phút → tải đang tăng.

load 1/5/15 = 9,42  10,05  9,12

1 phút < 5 phút → tải đang giảm.

Một cái nhìn trong hai giây cho biết bạn đang ở đầu hay cuối một sự cố. Đó là giá trị thật của nó, và nó không cần bất kỳ công cụ nào khác.

"Load bao nhiêu là cao"

Câu hỏi này không có câu trả lời tuyệt đối, nhưng có một mốc: số nhân.

nproc

Load bằng số nhân nghĩa là trung bình mỗi nhân có đúng một tiến trình — về mặt CPU thì đó là dùng hết công suất mà chưa xếp hàng. Nhưng vì Linux đếm cả tiến trình đợi đĩa, quy tắc đó chỉ đúng khi wa gần 0. Với wa cao, load có thể vượt số nhân nhiều lần mà CPU vẫn rảnh — như đo ở trên.

Nên quy tắc thực dụng là: đừng cảnh báo trên load. Cảnh báo trên wa, trên r, trên độ trễ của chính ứng dụng. Load là thứ để nhìn khi đã biết có vấn đề, không phải thứ để phát hiện vấn đề.

Ba chỗ khác hay bị nhầm

Load trong container là load của cả máy. /proc/loadavg do nhân cung cấp, và nhân dùng chung. Container của bạn thấy tải của hàng xóm. Số nền 8,2 trong phép đo này đến từ các container khác trên cùng máy, không từ container tôi đang đo.

uptime và top đọc cùng một nguồn. Không có con số nào "chính xác hơn"; chúng đều đọc /proc/loadavg.

Số thứ tư và thứ năm trong /proc/loadavg ít người để ý:

9.42 10.05 9.12 3/3589 4480
                 ^     ^
                 |     PID cuối cùng được cấp
                 số tiến trình đang chạy / tổng số tiến trình

Cột 3/3589 hữu dụng hơn người ta nghĩ: nếu tổng số tiến trình tăng đều mà không giảm, bạn đang rò rỉ tiến trình — một dạng sự cố mà load average không thể hiện rõ.

Mẫu số chung: một trung bình trượt kể chuyện quá khứ, đừng dùng nó chẩn đoán hiện tại

Hai cái bẫy của load average — nó gộp nhiều thứ khác loại vào một số, và nó là trung bình trượt trễ so với hiện tại — là hai bẫy của gần như mọi chỉ số "trung bình theo cửa sổ thời gian". Một con số như vậy mô tả một quãng vừa qua, không phải khoảnh khắc này, nên dùng nó để bắt sự cố đang diễn ra là nhìn nhầm thời điểm.

Trong thế giới Java, đây đúng là các chỉ số "rate" của Dropwizard Metrics: m1_rate/m5_rate/m15_rate là EWMA y hệt load average — một cú tăng đột biến vừa dứt vẫn để m1_rate cao thêm một lúc; và cái quyết định cảnh báo tức thời phải là số đo ngay bây giờ (số request đang xử lý, độ sâu hàng đợi), không phải rate trung bình phút. Người ta báo động theo m1_rate rồi đuổi theo một đỉnh đã qua.

Trong Go, cùng chuyện với rate() của Prometheus trên cửa sổ 5 phút: nó làm mượt và trễ, hữu ích để thấy xu hướng nhưng tệ để bắt một spike ngắn — và một alert đặt trên rate cửa sổ dài luôn kêu muộn và tắt muộn. Muốn tức thời phải nhìn gauge hiện tại, không nhìn rate.

Còn ở cơ sở dữ liệu, đây là các con số tích luỹ trong pg_stat_*: chúng là tổng từ lần reset gần nhất, nên "cache hit ratio 99%" có thể là trung bình của nhiều tháng che mất một giờ tệ hại vừa rồi; muốn biết hiện tại phải lấy hiệu hai lần đọc cách nhau ngắn, đúng như đọc vmstat 1 thay vì loadavg.

Mẫu số chung: một chỉ số trung bình/tích luỹ theo cửa sổ thời gian là ảnh của quá khứ được làm mượt — nó trễ khi tải lên và trễ khi tải xuống, nên rất tốt để thấy xu hướng và hướng đi nhưng gần như vô dụng để chẩn đoán cái đang xảy ra ngay bây giờ; và nếu nó còn gộp nhiều nguồn khác loại, một con số cao không cho biết nguồn nào. Nên khi nhìn một chỉ số như thế lúc điều tra, câu hỏi đúng là "con số này trung bình trên bao lâu, và cái tôi cần là bây giờ hay xu hướng" — vì để bắt hiện tại phải đọc giá trị tức thời, còn trung bình trượt chỉ dùng để biết mình đang vào hay ra khỏi sự cố.

Muốn tự đọc load kèm bối cảnh thay vì đọc một mình:

echo "nhan: $(nproc)"
cat /proc/loadavg
vmstat 1 3 | tail -1 | awk '{print "r=" $1 "  b=" $2 "  us=" $13 "%  sy=" $14 "%  wa=" $16 "%"}'

Ba dòng, và cách đọc:

  • Load cao + wa cao → nghẽn đĩa, thêm CPU không giúp.
  • Load cao + us cao + wa = 0 → nghẽn CPU thật.
  • Load cao + cả hai đều thấp → tải đã kết thúc, bạn đang nhìn lịch sử.
  • Load 1 phút lớn hơn 15 phút nhiều → đang vào sự cố, không phải ra khỏi nó.

Trường hợp thứ ba xảy ra thường xuyên hơn người ta nghĩ, và nó là lý do nhiều cuộc điều tra bắt đầu ở sai chỗ.

Phần sau: top và htop — đọc từng cột cho đúng.