Mở top, gõ uptime, hay liếc góc màn hình giám sát, bạn luôn thấy ba con số: load average: 3.54, 1.34, 0.49. Đây có lẽ là chỉ số hệ thống bị hiểu sai nhiều nhất. Rất nhiều người đọc nó như "phần trăm CPU" và hoảng khi thấy số lớn, hoặc yên tâm sai khi thấy số nhỏ. Sự thật tinh tế hơn — và quan trọng hơn — thế. Bài này (phần 10 loạt Debug) chạy thật để trả lời: ba con số đó đo gì, đọc chúng ra sao khi so với số core, và vì sao một máy có load cao ngất vẫn có thể không nghẽn CPU.

Ba con số: không phải %CPU

Điều đầu tiên phải gỡ: load average không phải phần trăm CPU. Nó là số tiến trình trung bình ở một trong hai trạng thái, tính trung bình trượt qua ba cửa sổ thời gian:

  • R (runnable/running): đang chạy trên CPU, hoặc đã sẵn sàng và đang xếp hàng chờ tới lượt CPU.
  • D (uninterruptible sleep): đang kẹt trong kernel chờ một thao tác không thể ngắt — điển hình là I/O (đọc/ghi đĩa, mạng) chưa xong.

Ba số là trung bình của tổng R + D trong 1 phút, 5 phút, 15 phút gần nhất. Vì là trung bình trượt theo hàm mũ (EMA), con số 1-phút phản ứng nhanh với thay đổi, còn 15-phút mượt và trễ — nên load "leo dần" chứ không nhảy tức thì.

Ảnh chụp đoạn mã nền tối minh hoạ load average ba con số 1 5 15 phút thực sự đo gì, đọc load average bằng cat proc loadavg và uptime 0.11 0.03 0.01 1-phút 5-phút 15-phút runnable trên tổng PID cuối, ba con số là trung bình trượt theo hàm mũ EMA không phải phần trăm CPU mà là số tiến trình trung bình ở trạng thái R runnable running đang chạy hoặc chờ tới lượt CPU D uninterruptible đang chờ I O đọc ghi đĩa mạng trong kernel con số 1-phút phản ứng nhanh 15-phút mượt và chậm nên load leo dần, so load với số core nproc bằng 10 load nhỏ hơn core còn CPU rỗng khỏe load xấp xỉ core đầy tải load lớn hơn core quá tải có tiến trình phải xếp hàng chờ CPU ví dụ load 8 trên 4 core quá tải trên 16 core nhàn rỗi, cạm bẫy load cao chưa chắc CPU nghẽn vì load đếm cả tiến trình D chờ I O một hệ load 10 nhưng phần trăm CPU thấp có thể là đĩa mạng chậm phải xem kèm phần trăm CPU top và số tiến trình D-state ps grep

Hình 1: load average là số tiến trình trung bình ở trạng thái R (chạy/chờ CPU) hoặc D (chờ I/O), tính EMA qua ba cửa sổ 1/5/15 phút; thước đo là so với số core (nproc); cạm bẫy là load cao có thể do I/O chứ không phải CPU.

Đo thật: 4 tiến trình CPU-bound, load leo tới ~4

Máy lab có 10 core. Baseline nhàn rỗi cho load average: 0.11, 0.03, 0.01. Mình bật 4 tiến trình CPU-bound (yes > /dev/null, mỗi cái ăn trọn một core) rồi đọc /proc/loadavg mỗi 15 giây:

Ảnh chụp bảng kết quả chạy thật load average output thật go-lab 10 core, một baseline rồi bật 4 tiến trình CPU-bound load 1-phút leo dần theo EMA baseline nhàn rỗi loadavg 0.11 0.03 0.01 10 core bật 4 lần yes ghi vào dev null mỗi cái ăn trọn 1 core t bằng 0 giây loadavg 0.11 t bằng 15 giây 0.97 t bằng 30 giây 1.64 t bằng 45 giây 2.37 t bằng 60 giây 2.93 t bằng 75 giây 3.17 t bằng 90 giây 3.41 t bằng 95 giây 3.54 tiệm cận 4 bằng số CPU hog, hai 4 nhỏ hơn 10 core bằng khỏe và 1-phút phản ứng nhanh 15-phút vẫn thấp load 3.54 trên 10 core còn khoảng 6 core rỗng hệ thống không quá tải 1-phút đã lên 3.54 nhưng 15-phút mới 0.49 đúng bản chất EMA tải vừa bật con số dài hạn chưa ngấm kịp, ba 6 tiến trình dd ghi đồng bộ kẹt ở D-state gần 0 phần trăm CPU load vẫn tăng ps trạng thái 6 dd đều D uninterruptible đang chờ ghi đĩa xong loadavg 3.30 1.46 0.55 số D bằng 6 số R bằng 6 sáu dd hầu như không đốt CPU chờ I O nhưng vẫn tính vào load suy ra load khác phần trăm CPU load 10 có thể là đĩa chậm không phải CPU nghẽn

Hình 2: Chạy thật — 4 tiến trình CPU-bound làm load 1-phút leo từ 0.11 qua 0.97 → 1.64 → 2.37 → 2.93 → 3.17 → 3.41 tới 3.54 (tiệm cận 4 = số hog); rồi 6 tiến trình dd ghi đồng bộ kẹt ở D-state (gần 0% CPU) vẫn giữ load ở 3.30, #D=6.

  • Load 1-phút leo dần, tiệm cận số tiến trình bận CPU: từ 0.11 nó bò lên 0.97, 1.64, 2.37, 2.93... rồi 3.54 sau ~95 giây, đang hướng tới 4 — đúng bằng số tiến trình CPU-bound. Nó leo chứ không nhảy phắt lên 4 vì là trung bình trượt: hằng số thời gian của cửa sổ 1-phút cỡ 60 giây.
  • 1-phút nhanh, 15-phút chậm: cùng lúc 1-phút đã 3.54 thì 15-phút mới 0.49. Đây là bản chất EMA — tải vừa mới bật, con số dài hạn chưa "ngấm" kịp. Ngược lại, nếu bạn thấy 15-phút cao thì đó là tải đã kéo dài.
  • So với số core mới là thước đo: 3.54 trên 10 core nghĩa là còn ~6 core rỗng — hệ thống không quá tải, mỗi tiến trình vẫn được một core riêng. Cùng con số 3.54 đó trên máy 2 core thì đã quá tải nặng (mỗi core gánh gần 2 tiến trình).

Cạm bẫy lớn nhất: load cao ≠ CPU nghẽn

Đây là điều tách người đọc load đúng khỏi người đọc sai. Load đếm cả tiến trình D (chờ I/O), nên một load cao có thể hoàn toàn không phải chuyện CPU. Mình chứng minh: chạy 6 tiến trình dd ghi đồng bộ (oflag=dsync — mỗi lần ghi phải chờ dữ liệu xuống đĩa mới trả về). Kết quả ps cho thấy cả 6 kẹt ở trạng thái D:

$ ps -eo pid,stat,comm | grep -E " (R|D)"
 91774 D  dd     # uninterruptible: đang chờ ghi đĩa xong
 91775 D  dd
 ...            # tổng 6 tiến trình dd ở D-state
loadavg= 3.30 1.46 0.55   #D=6  #R=6

Sáu tiến trình dd này gần như không đốt CPU — chúng ngồi chờ đĩa. Vậy mà chúng vẫn được tính vào load. Nếu bạn chỉ nhìn con số load mà kết luận "CPU đang nghẽn", bạn sai hướng hoàn toàn: thủ phạm ở đây là I/O đĩa, không phải CPU. Đây chính là lý do một database server có thể hiện load average: 20 trong khi top cho thấy CPU chỉ 15% bận — 20 đó phần lớn là các query đang chờ đĩa.

Cách đọc đúng: khi thấy load cao, luôn xem kèm:

  • %CPU (qua top): nếu CPU cũng cao → đúng là nghẽn CPU.
  • Số tiến trình D (ps -eo stat | grep -c ^D): nếu nhiều → nghẽn ở I/O (đĩa/mạng), không phải CPU.

Đánh đổi cần cân nhắc

Trong container, /proc/loadavg là load của host, không phải của riêng container. Load average là con số cấp kernel, mà container chia sẻ kernel với host. Nên load bạn đọc trong một container phản ánh toàn bộ máy chủ, gồm cả tải từ các container khác. Đừng dùng nó để đo tải riêng của service trong container — hãy dùng docker stats (đọc theo cgroup) hoặc số liệu ứng dụng. Con số trong bài này là load của host lab (đang chạy nhiều thứ), nên baseline 0.11 và các mức đo phản ánh cả nền hệ thống, không chỉ tiến trình mình bật.

Load là chỉ số sàng lọc, không phải chẩn đoán cuối. Nó trả lời "hệ thống có bị dồn việc không" chứ không nói "việc gì, ở đâu". Load cao chỉ là tín hiệu để đào tiếp: CPU-bound thì dùng top/ps --sort=-%cpu (bài trước) rồi perf/profiling (bài sau); I/O-bound thì dùng /proc/<pid>/io/iotop. Đừng dừng ở con số load.

"Load bằng số core là ranh giới" chỉ đúng gần đúng. Quy tắc load < core = khỏe là hướng dẫn tốt, nhưng thực tế mượt hơn: một hệ thống I/O-bound có thể chịu load cao hơn số core mà vẫn phản hồi tốt (vì các tiến trình D không tranh CPU), trong khi một hệ CPU-bound đã bắt đầu trễ ngay khi load tiệm cận số core. Dùng số core làm mốc tham chiếu, nhưng luôn soi kèm bản chất tải (CPU hay I/O).

Ba ý mang về

  1. Load average = số tiến trình trung bình ở trạng thái R hoặc D, không phải %CPU: đo thật 4 tiến trình CPU-bound làm load 1-phút leo từ 0.11 tới 3.54 (tiệm cận 4 = số hog), theo đường EMA — 1-phút nhanh, 15-phút vẫn 0.49 vì tải mới bật.
  2. So với số core mới ra ý nghĩa: 3.54 trên 10 core là khỏe (còn ~6 core rỗng); cùng số đó trên 2 core là quá tải. load < core khỏe, load > core có tiến trình phải xếp hàng chờ CPU.
  3. Load cao chưa chắc CPU nghẽn: đo thật 6 tiến trình dd kẹt ở D-state (chờ đĩa, gần 0% CPU) vẫn đẩy load lên — vì load đếm cả tiến trình chờ I/O. Luôn xem kèm %CPU và số tiến trình D để biết nghẽn ở CPU hay ở I/O.

Nguồn

Phần sau ta đo một thứ mà con số trung bình luôn giấu đi: latency và percentile — vì sao "trung bình 20ms" có thể che một p99 tệ hại 500ms, và cách đo p50/p95/p99 cho đúng.