Sê-ri đã đo chi phí của việc quan sát và cách chọn chỉ số. Bài này về bốn cách viết truy vấn sai mà tôi thấy thường xuyên nhất — không phải lỗi khái niệm mà lỗi cú pháp, và điểm chung của cả bốn là không cái nào sinh ra lỗi. Biểu đồ vẫn vẽ, truy vấn vẫn trả 200, không có dòng log nào.
Bảng số liệu
Prometheus 2.54.1, 900 chuỗi, thu thập mỗi 1 giây. Dữ liệu cố tình có hai thứ mà hệ thống thật luôn có: counter bị đặt lại (mô phỏng tiến trình khởi động lại) và đột biến gấp 20 lần kéo dài 5 giây.
Lỗi 1 — cửa sổ rate() ngắn hơn hai lần thu thập:
| Biểu thức | Số điểm |
|---|---|
rate(...[1s]) |
0 — biểu đồ trống |
rate(...[2s]) |
31 |
rate(...[5s]) |
31 |
Lỗi 2 — rate(sum()) thay vì sum(rate()):
| Trung bình | Đỉnh | |
|---|---|---|
sum(rate(...)) — đúng |
59 769 req/s | 76 985 |
rate(sum(...)) — sai |
205 543 req/s | 407 015 |
Cao gấp 3,44 lần.
Lỗi 3 — vẽ counter thô: sum(http_requests_total) đi từ 2 379 619 lên 9 977 845 trong 150 giây, luôn tăng.
Lỗi 4 — step lớn hơn cửa sổ rate:
| step / cửa sổ | Số điểm | Đỉnh nhìn thấy | Bỏ sót |
|---|---|---|---|
step=1s, rate[10s] |
151 | 208 286 | 0% |
step=5s, rate[10s] |
31 | 208 286 | 0% |
step=15s, rate[10s] |
11 | 206 778 | 1% |
step=60s, rate[10s] |
3 | 53 026 | 75% |
Điều đáng nhớ
Không lỗi nào trong bốn lỗi này sinh ra thông báo lỗi. Đó là điều làm chúng sống lâu: bảng điều khiển trông bình thường, ai đó đã duyệt nó, và nó nằm đó nhiều tháng cho tới lúc có sự cố mà nó không giúp được gì.
Lỗi 1 cho biểu đồ trống rỗng. rate() cần ít nhất hai mẫu trong cửa sổ để tính được độ dốc. Với chu kỳ thu thập 1 giây, cửa sổ 1 giây thường chỉ chứa một mẫu — và Prometheus trả về không có gì, không phải lỗi.
Lỗi 2 cho con số cao gấp 3,44 lần. Đây là lỗi nguy hiểm nhất vì kết quả trông hợp lý — chỉ là sai. Đỉnh 407 015 so với 76 985 thật.
Lỗi 4 giấu 75% chiều cao đỉnh. Với step=60s trên cửa sổ rate[10s], mỗi điểm chỉ nhìn 10 giây trong mỗi 60 giây; 50 giây dữ liệu giữa hai điểm không được nhìn tới lần nào. Đột biến rơi vào khoảng đó thì biến mất hoàn toàn.
Vì sao
Lỗi 2 đáng giải thích kỹ nhất. rate() của Prometheus biết cách xử lý counter bị đặt lại: khi thấy giá trị tụt xuống, nó hiểu là tiến trình khởi động lại và cộng bù phần đã mất. Nhưng nó chỉ làm được điều đó trên từng chuỗi riêng lẻ.
Khi bạn viết rate(sum(...)), phép sum chạy trước và gộp 900 chuỗi thành một. Nếu một chuỗi trong đó bị đặt lại, tổng chỉ tụt xuống một chút — không đủ để trông giống một lần đặt lại, nhưng đủ để rate coi đó là dữ liệu hợp lệ rồi bù vào bằng một cú tăng giả ở điểm sau. Với 180 chuỗi của dịch vụ worker bị đặt lại mỗi 30 giây, những cú bù giả đó cộng dồn thành 3,44 lần.
Quy tắc: rate() luôn phải là phép trong cùng. Gộp sau, không bao giờ gộp trước.
Lỗi 3 thì đơn giản hơn: counter là tổng tích luỹ từ lúc tiến trình khởi động. Vẽ nó ra chỉ cho một đường dốc lên mãi mãi — nó không nói được tốc độ hiện tại, không nói được lúc nào tăng lúc nào giảm, và một lần khởi động lại sẽ thành cú sụt thẳng đứng trong khi không có gì hỏng.
Lỗi 4 đến từ việc step và cửa sổ rate là hai thứ độc lập. step quyết định bao nhiêu điểm được tính; cửa sổ rate quyết định mỗi điểm nhìn lại bao xa. Khi step lớn hơn cửa sổ, giữa các điểm có khoảng trống mà không điểm nào chạm tới.
Nghĩa là gì trong thực tế
- Cửa sổ
ratephải ít nhất gấp đôi chu kỳ thu thập, và an toàn hơn là gấp bốn. Với thu thập 15 giây, dùngrate(...[1m]). rate()luôn nằm trong cùng.sum(rate(x[5m])), không bao giờrate(sum(x)[5m:]). Đây là lỗi dễ mắc nhất khi viết truy vấn cho một chỉ số có nhiều nhãn.- Không bao giờ vẽ counter thô. Nếu tên chỉ số kết thúc bằng
_total, nó cầnrate()hoặcincrease(). - Đặt
stepbằng$__intervalcủa Grafana và cửa sổratebằng$__rate_interval. Grafana tính hai biến đó sao cho cửa sổ luôn phủ được khoảng giữa các điểm. Gõ cứng con số là tự chuốc lỗi 1 hoặc lỗi 4. - Kiểm tra bảng điều khiển cũ bằng cách so đỉnh. Chạy cùng truy vấn với
stepnhỏ hơn nhiều rồi so đỉnh; nếu đỉnh cao hơn hẳn, bảng của bạn đang giấu mất phần đó.
Có một điểm chung giữa bốn lỗi này đáng nói riêng. Cả bốn đều là lỗi cấu hình đúng cú pháp nhưng sai ngữ nghĩa — Prometheus không có cách nào biết ý bạn là gì. rate(sum(x)) là một truy vấn hoàn toàn hợp lệ và có ý nghĩa trong vài trường hợp hiếm; step=60s với rate[10s] cũng vậy nếu bạn thật sự chỉ muốn lấy mẫu thưa. Hệ thống không thể từ chối chúng.
Điều đó đặt toàn bộ trách nhiệm lên người viết truy vấn, và trong thực tế truy vấn thường được sao chép từ một bảng điều khiển khác rồi sửa tên chỉ số. Lỗi lan theo cách đó, và nó lan cùng với sự tự tin — vì bảng gốc "vẫn chạy tốt".
Chỗ tôi không kết luận được
Con số 3,44 lần phụ thuộc vào tần suất counter bị đặt lại. Tôi cho một phần năm số chuỗi khởi động lại mỗi 30 giây — cao hơn thực tế nhiều. Với hệ thống ổn định, sai số của rate(sum()) nhỏ hơn hẳn, và đó chính là lý do lỗi này sống sót: nó vô hại cho tới lúc có tiến trình khởi động lại, tức đúng lúc đang triển khai hoặc đang có sự cố.
Con số 75% cũng do tôi đặt. Đột biến của tôi kéo 5 giây trong chu kỳ 40 giây; với step 60 giây thì phần lớn đột biến rơi vào khoảng mù. Đột biến dài hơn sẽ bị bỏ sót ít hơn — phần 19 đã đo quan hệ đó khi bàn về giảm mẫu.
Tôi không đo increase() và irate(), hai hàm có cùng họ cạm bẫy. increase() trên khoảng ngắn cho kết quả bị ngoại suy và thường ra số lẻ vô lý; irate() chỉ nhìn hai điểm cuối nên rất nhiễu.
Một lỗi trong chính công cụ vẽ sơ đồ của bài này, và nó cùng loại với bốn lỗi trên. Sơ đồ xuất ra lần đầu chỉ có 17 phần tử chữ thay vì khoảng 45 — nửa dưới biến mất hoàn toàn: cả mục "Lỗi 4", cả bảng số liệu của nó, cả khối kết luận.
Nguyên nhân là một ô trong sơ đồ chứa chuỗi <- day moi la con so co nghia, với dấu < chưa được thoát. Trong XML, dấu đó mở một thẻ mới, và mọi thứ sau nó bị nuốt. Công cụ vẫn xuất ra tệp SVG hợp lệ và báo thành công — y hệt bốn lỗi truy vấn ở trên.
Cách phát hiện: tôi đếm số phần tử <text trong tệp SVG và thấy 17, trong khi các sơ đồ khác cùng độ phức tạp đều cho 40 tới 70. Đây là bước kiểm mà tôi chạy sau mỗi lần xuất sơ đồ, và đây là lần nó thực sự cứu một bài viết.
Thử ba mươi giây
# Chay tren Prometheus cua ban - so hai con so
P=http://localhost:9090
M=http_requests_total # doi thanh mot counter co that cua ban
END=$(date +%s); START=$((END-3600))
echo "sum(rate(...)) - dung:"
curl -s -G "$P/api/v1/query" --data-urlencode "query=sum(rate(${M}[5m]))" \
| python3 -c 'import sys,json;r=json.load(sys.stdin)["data"]["result"];print(" ",r[0]["value"][1] if r else "khong co du lieu")'
echo "rate(sum(...)) - sai:"
curl -s -G "$P/api/v1/query" --data-urlencode "query=rate(sum(${M})[5m:])" \
| python3 -c 'import sys,json;r=json.load(sys.stdin)["data"]["result"];print(" ",r[0]["value"][1] if r else "khong co du lieu")'
Nếu hai con số bằng nhau, hệ thống của bạn hiện chưa có tiến trình nào khởi động lại — và lỗi này đang chờ lần triển khai tiếp theo để lộ ra.