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". Bài này 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.

Cùng một sự cố, ba cách tính SLI, ba kết luận trái ngược

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

Điều đáng nhớ

Nói lại theo cách khác, vì ba dòng in đậm là ba kết luận 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.

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à 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.

Vì sao

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 thực tế

  • 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.

Chỗ tôi không kết luận được

"Khoảng" của tôi là 2 giây, không phải một phút. Tôi rút gọn để chạy được ba lần trong thời gian hợp lý. Với cửa sổ thật dài hơn, tỷ lệ request trong mỗi cửa sổ sẽ mượt hơn và cách tính theo cửa sổ sẽ ít nhạy hơn với nhiễu — nhưng khoảng cách bản chất giữa hai cách thì không đổi, vì nó đến từ việc một cách đếm người còn cách kia đếm thời gian.

Con số 3,6 lần là do tôi chọn tỷ lệ tải. Tôi đặt 16 client ở kịch bản A và 2 client ở kịch bản B. Chọn tỷ lệ khác thì khoảng cách khác. Cái tôi tin là chiều hướng và cơ chế, không phải con số cụ thể.

Ở lần chạy thứ hai, kịch bản B cho SLI theo cửa sổ là 75% thay vì 80% — một khoảng lẽ ra tốt lại có đủ request chậm để bị đánh hỏng. Tôi lấy trung vị của ba lần là 80%, nhưng nhiễu đó có thật và nó nói lên một điều: cách tính theo cửa sổ nhạy với ngưỡng ở mép. Một khoảng có 98,9% request đạt thì bị tính hỏng hoàn toàn, y như khoảng chỉ có 10% đạt. Cách theo request không có vấn đề này.

Tôi không đo tốc độ đốt theo thời gian thực. Cảnh báo dựa trên ngân sách lỗi trong thực tế dùng nhiều cửa sổ chồng nhau — kiểu "đốt nhanh gấp 14 lần trong 1 giờ" cộng "gấp 6 lần trong 6 giờ" — để phân biệt sự cố cấp tính với suy giảm từ từ. Đó là một tầng nữa trên những gì tôi đo ở đây, và nó có cái bẫy riêng.

Thử ba mươi giây

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 đồ.