Hãy tưởng tượng ba người cùng chấm điểm một quán phở sau một buổi tối trục trặc. Người thứ nhất đếm số khách bị bưng nhầm bát. Người thứ hai chỉ ghi "quán có mấy tiếng phục vụ trơn tru, mấy tiếng lộn xộn". Người thứ ba lấy thời gian chờ trung bình của cả tối. Cùng một buổi tối, ba bảng điểm — và chúng có thể mâu thuẫn đến mức một người bảo "thảm hoạ", người kia bảo "ổn mà". SLI đúng là ba người chấm điểm đó, và bài này là lúc tôi cho cả ba chấm cùng một sự cố để xem họ đá nhau ra sao.
Phần trước đo bốn tín hiệu vàng và kết luận rằng độ bão hoà báo trước. Nhưng cảnh báo chỉ là nửa việc — nửa còn lại là quyết định khi nào dừng phát hành tính năng mới. Đó là việc của SLO và ngân sách lỗi, và câu hỏi trung tâm không phải "đặt SLO bao nhiêu" mà là "tính SLI như thế nào". Tôi dựng cùng một sự cố hai lần và tính SLI ba cách. Ba cách cho ba kết luận trái ngược nhau. (Đo bằng dịch vụ HTTP tám worker trong container, không có phần cứng đặc biệt nào.)
Bảng số liệu
Vẫn dịch vụ HTTP tám worker của phần trước. SLO đặt là 99% request dưới 100 ms, tức ngân sách lỗi là 1%. Hai mươi khoảng thời gian, trong đó bốn khoảng bị hỏng ở cả hai kịch bản — khác nhau duy nhất ở chỗ lúc hỏng có bao nhiêu người đang dùng.
| A — sự cố giờ cao điểm | B — sự cố giờ thấp điểm | |
|---|---|---|
| Số khoảng hỏng | 4 trong 20 | 4 trong 20 |
| Tổng request | 11 693 | 11 429 |
| Request quá 100 ms | 376 | 104 |
| SLI theo request | 96,78% → vi phạm | 99,09% → đạt |
| Ngân sách đã đốt | 3,2 lần | 0,9 lần |
| SLI theo cửa sổ | 80,00% | 80,00% |
| Ngân sách đã đốt | 20 lần | 20 lần |
| SLI theo độ trễ trung bình | 100% | 100% |
| Độ trễ trung bình | 56,1 ms | 46,8 ms |
Ba dòng in đậm không thể cùng đúng
Theo request, hai kịch bản khác hẳn nhau. A là 96,78% — vi phạm SLO và đã tiêu hết 3,2 lần ngân sách lỗi cho cả kỳ. B là 99,09% — vẫn đạt SLO, thậm chí còn dư ngân sách. Chênh lệch phản ánh đúng sự thật: 376 người bị ảnh hưởng so với 104, tỷ lệ 3,6 lần.
Theo cửa sổ, hai kịch bản giống hệt nhau: 80,00%. Cách này chỉ đếm bốn khoảng hỏng trên hai mươi, và nó không quan tâm trong bốn khoảng đó có bao nhiêu người đang gặp lỗi. Một sự cố lúc ba giờ sáng bị phạt y hệt một sự cố cùng độ dài lúc cao điểm — người thứ hai chấm điểm chỉ nhìn đồng hồ, không nhìn số khách.
Theo độ trễ trung bình, không có chuyện gì xảy ra. Trung bình là 56,1 ms ở kịch bản A, thấp hơn ngưỡng 100 ms, nên chỉ số này báo 100% đạt trong khi 376 request thật sự bị chậm. Đây không phải sai số — đây là mù hoàn toàn, và tôi muốn gằn giọng vì nó là cái bẫy dễ mắc nhất.
Và khoảng cách giữa hai cách đúng cũng rất lớn: ở kịch bản B, cách theo request nói "còn dư ngân sách" (0,9 lần) trong khi cách theo cửa sổ nói "đã tiêu gấp hai mươi lần". Hai con số đó dẫn tới hai quyết định ngược nhau về việc có nên dừng phát hành hay không. Đây không phải chuyện sai một chút — nó là hai thế giới.
Vì sao ba cách đá nhau
Ba cách trả lời ba câu hỏi khác nhau, và chỉ có một trong ba là câu hỏi người dùng quan tâm.
Theo request hỏi: bao nhiêu phần trăm lượt phục vụ đạt yêu cầu. Mỗi request bị chậm đóng góp đúng một đơn vị vào phần vi phạm, nên chỉ số này tỷ lệ với số người bị ảnh hưởng. Đó là lý do nó phân biệt được A và B.
Theo cửa sổ hỏi: bao nhiêu phần trăm thời gian dịch vụ ở trạng thái tốt. Mỗi khoảng đóng góp một đơn vị bất kể nó phục vụ mười hay mười nghìn request. Cách này không sai — nó đúng khi bạn cam kết về thời gian sống, chẳng hạn hợp đồng ghi "dịch vụ khả dụng 99,9% thời gian". Nhưng dùng nó rồi diễn giải như "99,9% người dùng hài lòng" là hiểu sai chính con số của mình.
Theo độ trễ trung bình thì hỏng ở tầng khái niệm. SLI phải là một tỷ lệ — số sự kiện tốt chia tổng sự kiện — chứ không bao giờ là một giá trị gộp so với ngưỡng. Phần 12 của sê-ri đã đo chuyện này: trung bình bị 99% mẫu bình thường ghim xuống và không phản ứng với đuôi. Đưa nó vào công thức SLI là xây toàn bộ hệ thống cam kết trên một chỉ số đã được chứng minh là mù.
Về ngân sách lỗi: với SLO 99%, ngân sách là 1% số request được phép hỏng trong kỳ. Con số "đã đốt 3,2 lần" nghĩa là phần vi phạm thực tế (3,22%) gấp 3,2 lần phần được phép (1%). Cách tính giống nhau cho cả ba cách tính SLI — nhưng vì SLI khác nhau nên kết quả khác nhau tới hai mươi lần.
Nghĩa là gì trong công việc hằng ngày
- Chốt cách tính SLI trước khi viết câu truy vấn đầu tiên, và ghi nó vào tài liệu SLO. Nếu điều này chưa được chốt, hai người trong cùng một đội sẽ dựng hai bảng điều khiển cho hai con số khác nhau và cả hai đều tin mình đúng.
- Với dịch vụ phục vụ người dùng, hầu như luôn chọn theo request. Nó đo đúng thứ bạn quan tâm: số lượt phục vụ tồi. Cách theo cửa sổ chỉ hợp lý khi cam kết của bạn thật sự nói về thời gian.
- Nếu lưu lượng của bạn rất không đều, hãy biết rằng cách theo request có nhược điểm riêng: một sự cố dài lúc đêm khuya gần như không hiện ra. Đó là đánh đổi có ý thức, không phải lỗi — nhưng phải biết mình đang chọn gì.
- Đừng bao giờ đặt SLI trên một giá trị gộp. Không dùng trung bình, và cũng đừng dùng "p99 dưới 100 ms" làm SLI: p99 là một giá trị gộp và nó che mất 1% tệ nhất y như trung bình che mất đuôi. SLI đúng là tỷ lệ request dưới 100 ms.
- Ngân sách lỗi chỉ có nghĩa khi nó dẫn tới hành động. Đốt hơn một lần ngân sách nghĩa là dừng phát hành tính năng và chuyển sang sửa độ tin cậy. Nếu không ai làm gì khi con số vượt ngưỡng thì cả hệ thống chỉ là một biểu đồ đẹp.
Cùng một cái bẫy "chọn nhầm thước đo", đổi ngăn xếp
Chuyện "cùng một dữ liệu, đo cách khác nhau ra kết luận ngược nhau, và một trong số đó gộp mất thông tin quan trọng" không riêng gì SLO — nó là cái bẫy đo lường lặp lại ở mọi tầng.
Trong thế giới Java, ai từng đọc số của Micrometer/Actuator đều gặp đúng phân biệt này: http.server.requests cho bạn cả count (đếm từng request, giống the-request) lẫn các percentile, và cái bẫy y hệt là báo cáo mean latency của một Timer rồi tưởng đã mô tả trải nghiệm người dùng — trong khi cái đuôi p99 mới là chỗ người dùng rời đi. Chọn "mean" hay "tỷ lệ vượt ngưỡng" là đúng hai cách tính SLI ở đây.
Trong Go, Prometheus (viết bằng Go, và chuẩn de-facto cho SLO) cho hai kiểu metric phản chiếu đúng bài này: một Histogram với bucket ở đúng ngưỡng SLO cho bạn tỷ lệ request dưới 100 ms (theo-request, đúng), trong khi một Summary với quantile tính sẵn lại là giá trị gộp không cộng gộp được giữa các instance. Cộng đồng SRE khuyên histogram-theo-ngưỡng chính vì lý do bài này chỉ ra.
Còn ở cơ sở dữ liệu, cùng cái bẫy hiện ra khi đánh giá hiệu năng truy vấn: AVG(response_time) trong một dashboard che mất đúng cái đuôi mà một COUNT(*) WHERE response_time > 100ms phơi ra. Một DBA đọc mean của pg_stat_statements có thể yên tâm trong khi 1% truy vấn chậm nhất đang làm khách hàng khổ sở — cùng sự mù của "SLI theo độ trễ trung bình".
Mẫu số chung: một thước đo tốt phải đo đúng thứ bạn cam kết, và mọi phép gộp (trung bình, thậm chí một percentile đơn lẻ) đều vứt bỏ chính cái thông tin mà cam kết nói về. SLI đúng gần như luôn là một tỷ lệ — số sự kiện tốt chia tổng — vì chỉ tỷ lệ mới đếm được từng người dùng bị ảnh hưởng. Nên trước khi vẽ bất kỳ bảng điều khiển độ tin cậy nào, câu hỏi đầu tiên không phải "ngưỡng đặt bao nhiêu" mà là "con số này đếm từng lượt phục vụ tồi, hay đã gộp chúng lại thành một số làm mất dấu chúng".
Muốn tự thấy ba cách đá nhau thành con số, chạy thử:
docker run --rm python:3.12-slim python - <<'EOF'
# 20 khoang, 4 khoang hong. Khac nhau duy nhat: luu luong luc hong.
NGUONG_TOT, MUC_TIEU = 0.99, 0.99
def sli(khoang): # khoang = [(tong, so_request_hong), ...]
tong = sum(t for t, _ in khoang); hong = sum(h for _, h in khoang)
theo_req = 1 - hong/tong
theo_win = sum(1 for t, h in khoang if (t-h)/t >= NGUONG_TOT) / len(khoang)
return theo_req, theo_win
A = [(600, 0)]*16 + [(600, 90)]*4 # su co gio cao diem
B = [(600, 0)]*16 + [(75, 26)]*4 # su co gio thap diem, it request hon
for ten, kb in (("A cao diem", A), ("B thap diem", B)):
r, w = sli(kb)
print("%-12s theo request %7.3f%% (dot %4.1f lan) theo cua so %6.2f%% (dot %4.1f lan)"
% (ten, 100*r, (1-r)/(1-MUC_TIEU), 100*w, (1-w)/(1-MUC_TIEU)))
EOF
Hai dòng ra khác nhau ở cột đầu và giống hệt nhau ở cột sau. Cột nào đúng phụ thuộc vào việc bạn đã hứa gì — và nếu chưa từng ai viết ra lời hứa đó, thì bạn chưa có SLO, bạn chỉ có một biểu đồ.