SLO thanh toán và cảnh báo burn rate: từ 337 lần page xuống 36
Đặt SLO 99,9% cho luồng thanh toán của BanHang, đổi cảnh báo ngưỡng cứng sang burn rate nhiều cửa sổ, và đếm page giả, sự cố bị lỡ trên 90 ngày mô phỏng.
02:58 rạng sáng thứ Tư 2026-09-09, điện thoại người trực thanh toán của BanHang reo: tỉ lệ lỗi thanh toán 5 phút là 50%. Năm phút đó có hai lệnh, và một lệnh hết giờ ở cổng thanh toán đối tác: đơn 10094, 2.190.000 đ. Cảnh báo tự tắt lúc 03:03. Sáng thứ Năm 17/09, từ 07:58 đến 11:22, cổng trả lỗi 0,96% số lệnh: 22 lệnh hỏng, bằng 8% số lệnh được phép hỏng của cả 30 ngày. Quy tắc "tỉ lệ lỗi 5 phút trên 1%" bắn 10 lần, lần nào cũng tự tắt sau 2 đến 22 phút như lúc 02:58, và không ai mở bảng điều khiển. Diễn biến lấy từ mô phỏng ở mục 4; mã đơn, số tiền và phản ứng của ca trực là minh họa.
Bài này đặt SLO cho luồng thanh toán, đổi cảnh báo sang tốc độ đốt error budget theo SRE Workbook, rồi đếm trên 90 ngày mô phỏng: mỗi cách cảnh báo page bao nhiêu lần, bao nhiêu lần là giả, sót bao nhiêu sự cố, kể cả chỗ quy tắc của Workbook vẫn đánh thức người lúc 3 giờ sáng.
Đọc nhanh
- SLI đếm lệnh thanh toán theo idempotency key, không đếm request. SLO 99,9% trong 30 ngày với 9.000 lệnh mỗi ngày cho phép 270 lệnh hỏng: đó là error budget. Burn rate là tốc độ tiêu budget chia cho tốc độ vừa tiêu hết đúng 30 ngày.
- Ở 9.000 lệnh mỗi ngày, "tỉ lệ lỗi 5 phút trên 1%" nghĩa là "có một lệnh lỗi". 90 ngày mô phỏng: 337 page, 261 page giả. Thêm
for: 10mcòn 21 page, nhưng trên 20 hạt giống nó sót 107 trong 334 sự cố đáng page. - Cảnh báo nhiều cửa sổ của Workbook (×14,4 trên 1 giờ và 5 phút, ×6 trên 6 giờ và 30 phút) bắt cả 12 sự cố đáng page, nhưng còn 22 page giả, 18 lần ban đêm: lúc 3 giờ sáng, một lệnh lỗi trong 27 lệnh là burn rate 37.
- Thêm sàn số lệnh hỏng (hơn 5,4 lệnh trong 1 giờ, hơn 13,5 lệnh trong 6 giờ) còn 36 page, 2 page giả, vẫn bắt cả 12. Trên 20 hạt giống, nó sót 3 trong 334, đều sát ngưỡng. Quy tắc Prometheus trong bài đã qua
promtool check rulesvàpromtool test rulesbản 3.15.0.
1. SLI, SLO và error budget cho luồng thanh toán
Cảnh báo cũ không nói một lỗi tốn bao nhiêu, cũng không nói bao nhiêu lỗi thì chấp nhận được. SRE Book định nghĩa SLI (service level indicator) là một số đo định lượng của một khía cạnh mức dịch vụ, và SLO (service level objective) là giá trị đích cho SLI đó. Với luồng thanh toán, SLI là số lệnh tốt chia tổng số lệnh.
Đơn vị đếm là lệnh thanh toán, tức một idempotency key, không phải request HTTP. App gửi lại ba lần cùng khóa vẫn là một lệnh (xem Idempotency key). Đếm request thì một lần cổng chậm sinh ra vài 409 và vài timeout, và tỉ lệ lỗi phóng đại đúng lúc cổng chậm.
| Kết quả của lệnh | Tính là |
|---|---|
| Thành công | Tốt |
| Ngân hàng hoặc ví từ chối: không đủ số dư, sai OTP, thẻ hết hạn | Tốt: hệ thống trả lời đúng |
| API trả 5xx, cổng trả lỗi kỹ thuật | Hỏng |
| Quá 10 giây chưa có kết quả, app hiện "đang xác nhận" | Hỏng, kể cả khi job đối soát sau đó thấy đã trừ tiền |
API tăng counter banhang_thanh_toan_lenh_total đúng một lần cho mỗi khóa, nhãn ket_qua là thanh_cong, tu_choi, loi_doi_tac hoặc loi_noi_bo. Hai nhãn lỗi cùng tính là hỏng; tách ra để biết gọi ai (mục 6).
SLO của bài: 99,9% lệnh tốt trong 30 ngày cuốn chiếu. Bài dùng 9.000 lệnh thanh toán mỗi ngày: phần trong khoảng 13.000 đơn mỗi ngày của BanHang trả qua cổng đối tác hoặc ví BHPay, không tính đơn COD và tiền mặt tại quầy (ước lượng của bài). Error budget là số lệnh được phép hỏng: (1 − SLO) × số lệnh trong 30 ngày = 0,001 × 9.000 × 30 = 270 lệnh.
Mức 99,99% chỉ cho 27 lệnh: cổng sập 2 phút lúc cao điểm, khoảng 32 lệnh, là hết (ước lượng). Mức 99% cho 2.700 lệnh, tức 90 lệnh hỏng mỗi ngày vẫn đạt, và CSKH kêu trước SLO. Ở 99,9%, lỗi nền giả định 0,03% tiêu khoảng 30% budget, phần còn lại dành cho sự cố và các lần triển khai.
Burn rate là tỉ lệ lỗi trong một cửa sổ chia cho 0,1%. Tỉ lệ lỗi 1,44% là burn rate 14,4. Phần budget tiêu trong cửa sổ bằng burn rate nhân độ dài cửa sổ chia 30 ngày (720 giờ):
burn rate 14,4 trong 1 giờ: 14,4 × 1 / 720 = 2% budget
burn rate 6 trong 6 giờ: 6 × 6 / 720 = 5% budget
burn rate 1 trong 3 ngày: 1 × 72 / 720 = 10% budget
sập hẳn, burn rate 1.000: hết budget sau 720 / 1.000 giờ ≈ 43 phút
2. Ngưỡng cứng 1% ở lưu lượng của BanHang
9.000 lệnh mỗi ngày không rải đều. Trong mô phỏng ở mục 4, lúc 03:00 có khoảng 0,5 lệnh mỗi phút, lúc cao điểm 10:00–11:30 khoảng 16. Cửa sổ 5 phút chứa khoảng 2 lệnh ban đêm và khoảng 80 lệnh lúc cao điểm. Một lệnh lỗi chỉ không vượt 1% khi nằm giữa ít nhất 100 lệnh, nên "tỉ lệ lỗi 5 phút trên 1%" thực chất là "có một lệnh lỗi trong 5 phút vừa qua", ở mọi giờ. Lỗi nền 0,03% đã là khoảng 2,7 lệnh lỗi mỗi ngày, tức 2 đến 3 page mỗi ngày khi không có sự cố nào.
Mỗi lệnh hỏng tiêu đúng 1/270 budget, khoảng 0,37%, dù xảy ra lúc 03:00 hay 10:30. Tỉ lệ lỗi thì khác hẳn: lệnh hỏng lúc 02:58 thành 50%, lệnh hỏng giữa 80 lệnh lúc 10:30 thành 1,25%. Ngưỡng trên tỉ lệ của cửa sổ ngắn đo lưu lượng lúc đó thấp hay cao, không đo khách bị ảnh hưởng bao nhiêu.
Cách vá hay gặp là thêm for: 10m: Prometheus giữ alert ở trạng thái pending và chỉ bắn khi biểu thức đúng ở mọi lần đánh giá trong 10 phút. Workbook không khuyên cách này cho cảnh báo SLO: chỉ số trở lại trong ngưỡng một lần là bộ đếm về 0. Ở lưu lượng thấp, tỉ lệ 5 phút rơi về 0% giữa hai lệnh lỗi thưa, nên đợt lỗi 1% kéo dài vài giờ có thể không bao giờ đủ 10 phút liên tục, còn đợt chập chờn vài phút lúc cao điểm thì hết trước khi đủ.
3. Cảnh báo nhiều cửa sổ, nhiều tốc độ đốt
Chương "Alerting on SLOs" của SRE Workbook đi qua sáu cách cảnh báo và chốt ở cách cuối. Mỗi tầng gắn với một phần budget: tiêu 2% trong 1 giờ hoặc 5% trong 6 giờ thì page, 10% trong 3 ngày thì mở ticket; burn rate suy ra bằng công thức ở mục 1. Mỗi tầng có hai cửa sổ cùng kết thúc ở thời điểm đánh giá, cửa sổ ngắn bằng 1/12 cửa sổ dài, và chỉ bắn khi tỉ lệ lỗi ở cả hai vượt ngưỡng.
| Tầng | Cửa sổ dài | Cửa sổ ngắn | Burn rate | Ngưỡng tỉ lệ lỗi | Budget đã tiêu |
|---|---|---|---|---|---|
| Page | 1 giờ | 5 phút | 14,4 | 1,44% | 2% |
| Page | 6 giờ | 30 phút | 6 | 0,6% | 5% |
| Ticket | 3 ngày | 6 giờ | 1 | 0,1% | 10% |
Cửa sổ dài trả lời "đã tiêu đủ nhiều budget chưa", cửa sổ ngắn trả lời "còn đang tiêu không". Thiếu cửa sổ ngắn, cửa sổ 1 giờ vẫn chứa đủ lỗi để bắn tới gần 1 giờ sau khi sự cố hết; Workbook ghi có cửa sổ ngắn thì alert tắt sau 5 phút.
Workbook tính thời gian phát hiện bằng (1 − SLO) / tỉ lệ lỗi × độ dài cửa sổ × burn rate. Cổng sập hẳn, tỉ lệ lỗi 100%: 0,001 / 1 × 60 phút × 14,4 ≈ 0,9 phút. Lỗi 1,8% kéo dài: 0,001 / 0,018 × 60 × 14,4 = 48 phút ở tầng 1 giờ, tính từ lúc cửa sổ chưa có lỗi nào. Mô phỏng bắt sự cố 1,8% sáng 01/08 sau 38 đến 41 phút.
Workbook cũng ghi giới hạn: cách này cần đủ lưu lượng, và hệ thống có giờ thấp điểm như ban đêm, cuối tuần thì phải điều chỉnh. Lúc 02:58 ngày 09/09, cửa sổ 1 giờ có 27 lệnh, 1 lệnh hỏng: tỉ lệ 3,7%, burn rate 37; cửa sổ 5 phút là 50%. Quy tắc nguyên bản cũng page, dù lệnh đó chỉ tiêu 0,37% budget. Workbook gợi ý sinh lưu lượng giả, gộp các dịch vụ nhỏ, sửa dịch vụ, hoặc hạ SLO và nới cửa sổ.
Bài này thêm một điều kiện không có trong Workbook, gọi là sàn: số lệnh hỏng trong cửa sổ dài phải vượt đúng phần budget của tầng đó, tức hơn 2% × 270 = 5,4 lệnh trong 1 giờ và hơn 5% × 270 = 13,5 lệnh trong 6 giờ. Lúc cao điểm, 1 giờ có khoảng 960 lệnh, ngưỡng 1,44% đã đòi khoảng 14 lệnh hỏng, nên sàn không đổi gì. Ban đêm, sàn chặn page vì vài lệnh lỗi lẻ.
4. Mô phỏng 90 ngày: đếm page, page giả và sự cố bị lỡ
Mô phỏng sinh số lệnh từng phút trong 90 ngày theo nhịp ngày ở mục 2, dao động khoảng 8% giữa các ngày, lỗi nền 0,03%, cộng bốn loại sự cố có giờ bắt đầu rải đều. Mọi phân phối là minh họa:
| Loại sự cố | Số lần kỳ vọng trong 90 ngày | Độ dài | Tỉ lệ lỗi trong sự cố |
|---|---|---|---|
| Chập chờn | 45 | 1–8 phút | 5–50% |
| Vừa | 6 | 10–60 phút | 2–15% |
| Kéo dài | 4 | 3–8 giờ | 0,5–1,5% |
| Sập | 2 | 5–30 phút | 60–100% |
Mức nặng của sự cố là số lệnh nó làm hỏng, tính bằng phần trăm budget. Từ 5% (khoảng 14 lệnh) là đáng page: sót là lỗi của cảnh báo. Từ 2% đến dưới 5% là vùng xám, page hay không đều được. Page không rơi vào khoảng từ đầu tới 60 phút sau cuối một sự cố từ 2% trở lên là page giả. Tiêu chí dựa trên budget nên phép so có lợi cho cảnh báo burn rate theo cách dựng; thứ đo được là giá của từng cách khi đã nhận tiêu chí đó.
Năm chiến lược được đánh giá mỗi phút trên số đếm chính xác trong từng cửa sổ, không tái tạo phép ngoại suy của rate(). "Burn 1 giờ > 14,4" là tầng đầu của Workbook khi bỏ cửa sổ ngắn và tầng 6 giờ. Cần Python 3.12 trở lên và numpy; số dưới đây chạy bằng Python 3.14.3, numpy 2.4.4, hạt giống 2026. numpy không bảo đảm cùng dãy số ngẫu nhiên giữa các bản X.Y. Lưu thành mo_phong.py:
import sys
import numpy as np
HAT = int(sys.argv[1]) if len(sys.argv) > 1 else 2026
rng = np.random.default_rng(HAT)
NGAY, T, B = 90, 90 * 1440, 0.001 # 90 ngày tính theo phút; SLO 99,9% nên budget 0,1%
# Lưu lượng 9.000 lệnh/ngày, tỉ trọng theo giờ (minh họa): cao điểm 10:00–11:30, đêm thấp
gio = np.repeat([3, 2, 1, 1, 1, 2, 4, 8, 14, 20, 34, 34, 18, 15, 15, 15, 15, 15, 16, 20, 22, 18, 10, 5], 60.0)
gio[690:720] = 20
lam = np.tile(gio / gio.sum() * 9_000, NGAY) * np.repeat(rng.normal(1, 0.08, NGAY), 1440)
lenh = rng.poisson(lam) # số lệnh mỗi phút
hong = rng.binomial(lenh, 0.0003) # lỗi nền 0,03%
# Sự cố: số lần kỳ vọng trong 90 ngày, độ dài (phút), tỉ lệ lỗi; phân phối đều, minh họa
LOAI = {"chập chờn": (45, (1, 8), (0.05, 0.5)), "vừa": (6, (10, 60), (0.02, 0.15)),
"kéo dài": (4, (180, 480), (0.005, 0.015)), "sập": (2, (5, 30), (0.6, 1.0))}
su_co = []
for loai, (k, (d0, d1), (p0, p1)) in LOAI.items():
for _ in range(rng.poisson(k)):
dai = int(rng.integers(d0, d1 + 1))
bd = int(rng.integers(0, T - dai))
h = rng.binomial(lenh[bd:bd + dai], rng.uniform(p0, p1))
hong[bd:bd + dai] += h
su_co.append((loai, bd, bd + dai, h))
hong = np.minimum(hong, lenh)
budget = B * lenh.sum() / 3 # số lệnh được phép hỏng trong 30 ngày
lon = [s for s in su_co if s[3].sum() >= 0.05 * budget] # đáng page
vua = [s for s in su_co if 0.02 * budget <= s[3].sum() < 0.05 * budget] # page hay không đều được
cl, ch = (np.concatenate([[0], np.cumsum(a)]) for a in (lenh, hong))
def tong(c, w): # tổng trong cửa sổ w phút, kết thúc ở từng phút
return c[1:] - c[np.maximum(0, np.arange(1, T + 1) - w)]
def ty_le(w):
n = tong(cl, w)
return np.divide(tong(ch, w), n, out=np.zeros(T), where=n > 0)
r = {w: ty_le(w) for w in (5, 30, 60, 360, 4320)}
def for_10m(dk): # đúng liên tục ở 11 lần đánh giá, mỗi phút một lần
ra, chuoi = np.zeros(T, bool), 0
for i in range(T):
chuoi = chuoi + 1 if dk[i] else 0
ra[i] = chuoi > 10
return ra
nhanh = (r[60] > 14.4 * B) & (r[5] > 14.4 * B)
cham = (r[360] > 6 * B) & (r[30] > 6 * B)
CHIEN_LUOC = {
"1% / 5 phút": r[5] > 0.01,
"1% / 5 phút, for 10m": for_10m(r[5] > 0.01),
"Burn 1 giờ > 14,4": r[60] > 14.4 * B,
"Nhiều cửa sổ": nhanh | cham,
"Nhiều cửa sổ + sàn": (nhanh & (tong(ch, 60) > 0.02 * budget)) | (cham & (tong(ch, 360) > 0.05 * budget)),
}
ticket = (r[4320] > B) & (r[360] > B)
def len_page(ban): # phút alert chuyển từ tắt sang bắn
return np.flatnonzero(ban & ~np.concatenate([[False], ban[:-1]]))
print(f"Hạt giống {HAT}: {lenh.sum() / NGAY:.0f} lệnh/ngày, budget {budget:.0f} lệnh/30 ngày, "
f"hỏng {hong.sum()} lệnh/90 ngày, {len(su_co)} sự cố: {len(lon)} đáng page, {len(vua)} vừa")
print(f"{'chiến lược':<22}{'page':>5}{'giả':>5}{'giả đêm':>8}{'lỡ':>6}{'phát hiện':>10}{'đã cháy':>8}{'tắt sau':>8}")
for ten, ban in CHIEN_LUOC.items():
page = len_page(ban)
gia = [p for p in page if not any(s[1] <= p <= s[2] + 60 for s in lon + vua)]
dem = [p for p in gia if p % 1440 < 360 or p % 1440 >= 1320] # 22:00–06:00
phut, chay, tat = [], [], []
for _, bd, kt, h in lon:
bat = np.flatnonzero(ban[bd:kt + 60])
if len(bat):
phut.append(bat[0]) # phút từ đầu sự cố tới lúc bắn
chay.append(h[:bat[0] + 1].sum() / budget * 100) # % budget sự cố đã đốt lúc bắn
tat.append(np.argmin(ban[kt:])) # phút từ cuối sự cố tới lúc tắt
print(f"{ten:<22}{len(page):>5}{len(gia):>5}{len(dem):>8}{len(lon) - len(phut):>4}/{len(lon):<2}"
f"{np.median(phut):>7.0f} p{np.median(chay):>7.1f}%{np.median(tat):>6.0f} p")
print(f"Ticket 3 ngày và 6 giờ > 1×: {len(len_page(ticket))} lần")
Kết quả trên laptop Intel Core Ultra 5 125U, Windows 11, chạy hết khoảng 0,3 giây (trung vị 3 lần, dao động theo máy):
Hạt giống 2026: 9049 lệnh/ngày, budget 271 lệnh/30 ngày, hỏng 737 lệnh/90 ngày, 59 sự cố: 12 đáng page, 12 vừa
chiến lược page giả giả đêm lỡ phát hiện đã cháy tắt sau
1% / 5 phút 337 261 33 0/12 0 p 0.7% 4 p
1% / 5 phút, for 10m 21 1 0 2/12 10 p 5.3% 0 p
Burn 1 giờ > 14,4 56 20 17 0/12 10 p 3.7% 54 p
Nhiều cửa sổ 53 22 18 0/12 10 p 3.5% 23 p
Nhiều cửa sổ + sàn 36 2 1 0/12 10 p 3.7% 23 p
Ticket 3 ngày và 6 giờ > 1×: 45 lần
page là số lần alert chuyển từ tắt sang bắn, giả đêm là page giả từ 22:00 đến 06:00, lỡ là sự cố đáng page mà alert không bắn lúc nào từ đầu tới 60 phút sau cuối. Ba cột cuối là trung vị trên các sự cố đáng page đã bắt được: phút từ đầu sự cố tới lúc bắn, phần budget sự cố đã đốt lúc bắn, phút từ cuối sự cố tới lúc alert tắt.
Thêm sàn vào cảnh báo nhiều cửa sổ: 36 page, 2 page giả, không sót sự cố đáng page nào
Bảng số liệu
| Lần page | Page giả | Sự cố đáng page bị lỡ | |
|---|---|---|---|
| 1% / 5 phút | 337 | 261 | 0 |
| 1% / 5 phút, for 10m | 21 | 1 | 2 |
| Burn 1 giờ > 14,4 | 56 | 20 | 0 |
| Nhiều cửa sổ | 53 | 22 | 0 |
| Nhiều cửa sổ + sàn | 36 | 2 | 0 |
Ngưỡng cứng không sót gì vì nó bắn với gần như mọi lệnh lỗi: 261 page giả, khoảng 2,9 mỗi ngày, và sự cố 17/09 lẫn trong đó thành 10 page tự tắt. for: 10m dọn gần hết page giả nhưng page muộn hơn, khi sự cố đã đốt 5,3% budget, và sót 2 trong 12. Burn rate một cửa sổ còn kêu trung vị 54 phút sau khi sự cố hết; cửa sổ ngắn của bản Workbook rút xuống 23 phút, nhưng 18 trong 22 page giả của nó rơi vào ban đêm. Sàn bỏ gần hết số đó, thời gian phát hiện giữ ở 10 phút, phần budget đã cháy lúc page chỉ nhích từ 3,5% lên 3,7%.
Một hạt giống là một quý. Chạy lại với hạt giống 1 đến 20 (1..20 | ForEach-Object { python mo_phong.py $_ } trong PowerShell), thứ tự giữ nguyên. Hai mươi lần chạy, mỗi lần 90 ngày, có tổng cộng 334 sự cố đáng page:
| Chiến lược | Page mỗi 90 ngày | Page giả mỗi 90 ngày | Sự cố đáng page bị lỡ |
|---|---|---|---|
| 1% / 5 phút | 290–394 | 229–274 | 0 / 334 |
| 1% / 5 phút, for 10m | 9–29 | 0–3 | 107 / 334, 100 trong số đó là đợt chập chờn |
| Burn 1 giờ > 14,4 | 41–79 | 10–30 | 7 / 334, cả 7 là sự cố kéo dài |
| Nhiều cửa sổ | 43–82 | 10–24 | 2 / 334 |
| Nhiều cửa sổ + sàn | 26–65 | 0–6 | 3 / 334 |
Ba sự cố bản có sàn bỏ lỡ làm hỏng 5,2% đến 5,5% budget, sát ngưỡng. Số page của nó gồm cả lần bắn lặp: sáng 01/08 có 6 lần trong 43 phút, khi tỉ lệ 1 giờ và 5 phút dao động quanh 1,44% còn tầng 6 giờ chưa đủ 13,5 lệnh hỏng. keep_firing_for (từ Prometheus 2.42) giữ alert bắn thêm một khoảng sau lần cuối điều kiện đúng; một biến thể của mô phỏng với keep_firing_for: 10m giảm 36 page xuống 27, đổi lại alert tắt chậm hơn 9 phút. Tầng ticket bắn 45 lần, rơi vào 34 trong 90 ngày, vì quý này đốt khoảng 91% budget mỗi 30 ngày. Đó là việc giờ hành chính, gộp theo ngày.
5. Quy tắc Prometheus
Recording rule tính sẵn tỉ lệ lỗi theo từng cửa sổ. Tử số và mẫu số được sum riêng rồi mới chia, đúng khuyến nghị của tài liệu Prometheus: không lấy trung bình của các tỉ lệ. Alert dùng đúng các ngưỡng ở mục 3, thêm sàn theo budget 270 lệnh. Lưu thành slo-thanh-toan.yml:
groups:
- name: slo-thanh-toan-ghi
rules:
- record: slo:thanh_toan_loi:ratio_rate5m
expr: sum(rate(banhang_thanh_toan_lenh_total{ket_qua=~"loi_.*"}[5m])) / sum(rate(banhang_thanh_toan_lenh_total[5m]))
- record: slo:thanh_toan_loi:ratio_rate30m
expr: sum(rate(banhang_thanh_toan_lenh_total{ket_qua=~"loi_.*"}[30m])) / sum(rate(banhang_thanh_toan_lenh_total[30m]))
- record: slo:thanh_toan_loi:ratio_rate1h
expr: sum(rate(banhang_thanh_toan_lenh_total{ket_qua=~"loi_.*"}[1h])) / sum(rate(banhang_thanh_toan_lenh_total[1h]))
- record: slo:thanh_toan_loi:ratio_rate6h
expr: sum(rate(banhang_thanh_toan_lenh_total{ket_qua=~"loi_.*"}[6h])) / sum(rate(banhang_thanh_toan_lenh_total[6h]))
- record: slo:thanh_toan_loi:ratio_rate3d
expr: sum(rate(banhang_thanh_toan_lenh_total{ket_qua=~"loi_.*"}[3d])) / sum(rate(banhang_thanh_toan_lenh_total[3d]))
- record: slo:thanh_toan_loi:increase1h
expr: sum(increase(banhang_thanh_toan_lenh_total{ket_qua=~"loi_.*"}[1h]))
- record: slo:thanh_toan_loi:increase6h
expr: sum(increase(banhang_thanh_toan_lenh_total{ket_qua=~"loi_.*"}[6h]))
- name: slo-thanh-toan-canh-bao
rules:
- alert: ThanhToanDotBudgetNhanh
expr: |
(
slo:thanh_toan_loi:ratio_rate1h > (14.4 * 0.001)
and slo:thanh_toan_loi:ratio_rate5m > (14.4 * 0.001)
and slo:thanh_toan_loi:increase1h > (0.02 * 270)
)
or
(
slo:thanh_toan_loi:ratio_rate6h > (6 * 0.001)
and slo:thanh_toan_loi:ratio_rate30m > (6 * 0.001)
and slo:thanh_toan_loi:increase6h > (0.05 * 270)
)
labels:
severity: page
annotations:
summary: "Thanh toán đang đốt error budget nhanh"
- alert: ThanhToanDotBudgetCham
expr: slo:thanh_toan_loi:ratio_rate3d > 0.001 and slo:thanh_toan_loi:ratio_rate6h > 0.001
labels:
severity: ticket
promtool lấy từ bản phát hành chính thức Prometheus 3.15.0 cho Windows:
> promtool check rules slo-thanh-toan.yml
Checking slo-thanh-toan.yml
SUCCESS: 9 rules found
Bài kiểm thử đơn vị bằng promtool test rules chạy hai chuỗi giả, đánh giá mỗi phút. Chuỗi đêm: nửa lệnh thành công mỗi phút, một lệnh lỗi ở phút thứ 60. Phút 62, tỉ lệ 1 giờ là 1/31 ≈ 3,2%, vượt 1,44%, nhưng ThanhToanDotBudgetNhanh không bắn vì chỉ có 1 lệnh hỏng; bỏ hai dòng sàn thì cùng phép thử bắn. Chuỗi cao điểm: 16 lệnh thành công mỗi phút, từ phút 61 thêm 8 lệnh lỗi mỗi phút; alert bắn ở phút 62.
Ba điều cần biết khi chạy thật. Counter phải có sẵn chuỗi giá trị 0 cho mọi nhãn ket_qua; tài liệu Prometheus khuyên xuất trước giá trị 0 cho chuỗi biết sẽ có. Không có chuỗi lỗi nào thì tử số rỗng, tỉ lệ rỗng, và alert im lặng. increase() ngoại suy tới mép cửa sổ nên số lệnh hỏng không nguyên: sàn là ngưỡng xấp xỉ. Cửa sổ 3d trên counter thô tốn tính toán; Workbook ghi cửa sổ dài là phép tính đắt.
6. Lỗi của ai, ai được page, và SLO độ trễ
Khách không phân biệt lỗi do cổng đối tác hay do BanHang, nên SLO đếm cả loi_doi_tac lẫn loi_noi_bo. Nhãn dùng để chọn người nhận và việc phải làm:
| Tín hiệu | Ai nhận | Làm gì |
|---|---|---|
| Tầng ×14,4 hoặc ×6 | Người trực thanh toán, gọi điện 24/7 | Tạm ẩn phương thức đang lỗi, hướng khách sang ví BHPay hoặc cổng khác |
Phần lớn lệnh hỏng là loi_doi_tac |
Người phụ trách tích hợp cổng | Gửi đối tác khung giờ, số lệnh, mã giao dịch |
| Tầng ×1 | Đội thanh toán, ticket giờ hành chính | Tìm lỗi lặp lại: timeout đặt sai, một loại thẻ hay lỗi |
| Budget 30 ngày âm | Trưởng nhóm thanh toán và sản phẩm | Áp chính sách error budget |
Page đi tới người trực của BanHang cả khi lỗi ở đối tác, vì việc giảm thiểu nằm ở phía mình. SRE Book mô tả cách dùng budget: còn budget thì được phát hành bản mới; budget cạn thì tạm dừng phát hành và dồn sức cho kiểm thử, độ bền. Trong mô phỏng, 30 ngày đầu cạn budget lúc 11:37 ngày 01/08.
Độ trễ cần SLO riêng. "p99 dưới 2 giây trong 30 ngày" cùng nghĩa với "ít nhất 99% lệnh trả lời dưới 2 giây", nên SLI là số lệnh nhanh hơn 2 giây chia tổng số lệnh, budget 1%, khoảng 2.700 lệnh chậm mỗi 30 ngày. Cảnh báo dùng cùng các tầng, ngưỡng nhân với 0,01 thay vì 0,001, trên tỉ lệ lệnh chậm tính từ histogram:
1 - sum(rate(banhang_thanh_toan_seconds_bucket{le="2"}[1h])) / sum(rate(banhang_thanh_toan_seconds_count[1h]))
Biểu thức đã qua promtool check rules. Histogram cổ điển phải có bucket biên đúng 2 giây, như ví dụ mốc 0,3 giây trong tài liệu Prometheus. Đừng cảnh báo thẳng trên p99 của cửa sổ 5 phút: lúc 03:00 cửa sổ đó có khoảng 2 lệnh. Phân vị tính sẵn cũng không gộp được qua nhiều máy; tài liệu Prometheus ghi lấy trung bình các phân vị cho ra giá trị vô nghĩa về thống kê. Số lệnh chậm thì cộng được.
Một lệnh quá 10 giây vừa hỏng ở SLI lỗi vừa chậm ở SLI độ trễ. Chiều ngược lại không đúng: suy giảm độ trễ thường đến trước lỗi. Bài Điều tra một truy vấn chậm có hai giờ rưỡi p95 khoảng 1 giây, gấp 12 lần bình thường, mà cảnh báo ngưỡng tuyệt đối 2 giây không kêu. Mốc của SLI độ trễ chọn theo trải nghiệm của luồng đó, không chọn ở mức chắc chắn đã hỏng.
Những chỗ hay hiểu sai
- "Ngưỡng tỉ lệ lỗi càng thấp càng an toàn." Ở 9.000 lệnh mỗi ngày, 1% trong 5 phút là một lệnh lỗi: 261 trong 337 page là giả.
- "
for: 10mlọc nhiễu mà không mất gì." Trên 20 hạt giống, nó sót 107 trong 334 sự cố đáng page. - "Burn rate là tỉ lệ nên không sợ lưu lượng thấp." Một lệnh lỗi trong 27 lệnh lúc 03:00 là burn rate 37.
- "Lỗi của cổng đối tác không tính vào SLO của mình." Khách thấy cùng một lệnh hỏng; nhãn chỉ dùng để chọn người nhận.
Đọc tiếp
- Idempotency key: để một đơn không bị trừ tiền hai lần: đơn vị đếm của SLI, lệnh "đang xác nhận" và job đối soát.
- Xác thực webhook thanh toán: kết quả cuối của cổng về
BanHangqua webhook. - Bài toán hai vị tướng: vì sao luôn có lệnh "chưa biết kết quả".
- Một request HTTPS mất bao lâu: thời gian lời gọi cổng nằm ở pha nào khi SLI độ trễ đang cháy.
- Điều tra một truy vấn chậm từ cảnh báo đến bản sửa: ngưỡng tuyệt đối để lọt hai giờ rưỡi suy giảm.
- Triển khai không gián đoạn: giữ lần triển khai khỏi tiêu budget, bằng cách rút khỏi load balancer và xả request trước khi dừng instance cũ.
- Migration không dừng hệ thống: một lệnh
ALTER TABLEgiữ khóaSch-Mlàm API đứng 18 giây, đủ để tầng cảnh báo nhanh bắn.
Nguồn
- Google SRE Workbook, chương 5, Alerting on SLOs: sáu cách cảnh báo, bảng 5-8, công thức thời gian phát hiện, mục dịch vụ lưu lượng thấp.
- Google SRE Book, chương 4, Service Level Objectives, và chương 3, Embracing Risk: định nghĩa SLI, SLO, error budget và cách dùng budget để quyết định phát hành.
- Prometheus, Alerting rules:
for,keep_firing_for. - Prometheus, Recording rules: cách gộp tỉ lệ.
- Prometheus, Histograms and summaries: bucket ở đúng mốc SLO, không lấy trung bình phân vị.
- Prometheus, Instrumentation: xuất sẵn giá trị 0.
- Prometheus, Unit testing for rules, bản phát hành v3.15.0 và v2.42.0 (thêm
keep_firing_for). - NumPy, Compatibility policy của
numpy.random.