Hãy nghĩ về hai cách canh một cây cầu xem có gãy không. Cách thứ nhất: đứng chờ nghe tiếng người kêu khi có ai đó rơi — hiệu quả vào giờ tan tầm đông người, nhưng lúc ba giờ sáng vắng tanh, cầu có sập một nhịp rồi được sửa tạm cũng chẳng ai kêu, và bạn không hề hay. Cách thứ hai: cứ mỗi phút lại tự lái một chiếc xe qua cầu, bất kể có ai khác không — chậm hơn một chút vào giờ đông, nhưng ba giờ sáng nó vẫn phát hiện cầu gãy ngay. Giám sát thụ động là cách thứ nhất; kiểm tra tổng hợp là cách thứ hai. Bài này tôi đo khoảng cách giữa hai cách, và nó lớn đúng ở nơi bạn ngờ nhất.

Phần trước đo khoảng cách giữa thứ máy chủ thấy và thứ người dùng chịu. Bài này hỏi câu tiếp: nếu không có người dùng nào thì sao? Giám sát thụ động — mọi thứ sê-ri này đã đo cho tới giờ — chỉ nhìn thấy lỗi khi có ai đó gặp lỗi. Kiểm tra tổng hợp tự gọi dịch vụ theo lịch. Câu hỏi là khoảng cách giữa hai cách lớn tới đâu, và ở đâu. (Đo bằng mô phỏng một ngày lưu lượng có hình dạng thật, 500 lần chạy mỗi ô; không cần phần cứng đặc biệt.)

Bảng số liệu

Mô phỏng một ngày với lưu lượng có hình dạng thật, 500 lần chạy cho mỗi ô. Giám sát thụ động báo động khi thấy 3 request lỗi; kiểm tra tổng hợp tự gọi mỗi phút.

Lưu lượng theo giờ (yêu cầu mỗi phút):

Khung giờ Trung vị Số phút không có request nào
00h–06h 0 305 / 500
06h–09h 39 0 / 500
09h–18h 656 0 / 500
18h–23h 89 0 / 500

Sự cố kéo dài 5 phút:

Bắt đầu lúc Thụ động phát hiện sau Thụ động bỏ sót Tổng hợp
02:00 3 phút 54% 0 phút, bỏ sót 0%
04:00 3 phút 54% 0 phút, bỏ sót 0%
23:00 3 phút 52% 0 phút, bỏ sót 0%
08:00 / 14:00 / 20:00 0 phút 0% 0 phút, bỏ sót 0%

Sự cố kéo dài 15 phút:

Bắt đầu lúc Thụ động Tổng hợp
02:00 4 phút, bỏ sót 1% 0 phút, bỏ sót 0%
04:00 5 phút, bỏ sót 2% 0 phút, bỏ sót 0%
23:00 5 phút, bỏ sót 1% 0 phút, bỏ sót 0%

Toàn bộ giá trị nằm ở giờ không ai dùng

Ban ngày, hai cách ngang nhau. Cả hai phát hiện trong vòng 0 phút, không bỏ sót lần nào. Ở 656 yêu cầu mỗi phút, ngưỡng 3 lỗi bị vượt gần như tức thì.

Ban đêm thì khác hẳn: thụ động bỏ sót 54% số sự cố 5 phút. Hơn một nửa số lần, dịch vụ chết rồi sống lại mà không có báo động nào kêu. Lý do nằm ở hàng đầu bảng: lúc 2 giờ sáng, 305 trong 500 phút không có một request nào. Không có request thì không có lỗi để đếm — cầu sập lúc vắng người thì không ai kêu.

Sự cố dài hơn thì cuối cùng cũng bị bắt, nhưng chậm. Với sự cố 15 phút, thụ động phát hiện sau 4–5 phút và chỉ bỏ sót 1–2%. Đủ request tích luỹ để vượt ngưỡng — nhưng đó là bốn tới năm phút dịch vụ chết mà không ai biết.

Kiểm tra tổng hợp không nhanh hơn ban ngày. Nó cũng 0 phút, y như thụ động. Toàn bộ giá trị của nó nằm ở đúng một chỗ: giờ không ai dùng. Nếu chỉ nhìn ban ngày mà kết luận "synthetic check thừa", bạn đã bỏ qua đúng cái nó sinh ra để làm.

Vì sao thụ động mù trong khoảng trống

Giám sát thụ động đo lỗi, và lỗi là một sự kiện cần hai thứ: dịch vụ hỏng, và có ai đó chạm vào nó. Thiếu vế thứ hai thì không có gì để đếm. Đây không phải hạn chế của công cụ mà là bản chất của phương pháp — Prometheus, Datadog hay bất cứ thứ gì khác đều mù như nhau trong khoảng trống lưu lượng.

Kiểm tra tổng hợp lật ngược quan hệ đó: nó tự tạo ra vế thứ hai. Cứ mỗi phút nó gọi dịch vụ một lần, nên xác suất phát hiện chỉ phụ thuộc vào chu kỳ kiểm tra chứ không phụ thuộc vào việc có ai đang dùng hay không.

Con số 54% bỏ sót có nguồn gốc số học đơn giản. Sự cố kéo 5 phút; ban đêm mỗi phút có trung bình khoảng nửa request. Trong 5 phút đó, kỳ vọng khoảng 2,5 request đi qua — dưới ngưỡng 3 lỗi. Khoảng một nửa số lần mô phỏng, tổng cộng không đủ 3 request để vượt ngưỡng, và sự cố kết thúc trong im lặng. Điều đáng nói là hạ ngưỡng xuống không cứu được: ngưỡng 1 lỗi sẽ bắt được nhiều hơn, nhưng ban ngày ở 656 request mỗi phút thì một lỗi lẻ là chuyện bình thường và báo động sẽ kêu suốt. Ngưỡng phải đủ cao cho giờ cao điểm, và chính điều đó làm nó mù trong giờ thấp điểm.

Nghĩa là gì trong công việc hằng ngày

  • Nếu dịch vụ của bạn có khoảng trống lưu lượng, kiểm tra tổng hợp không phải thứ xa xỉ. Nó là thứ duy nhất nhìn thấy đêm hôm. Với dịch vụ nội bộ chỉ dùng trong giờ hành chính, khoảng trống đó là hai phần ba thời gian.
  • Đặt chu kỳ kiểm tra theo thời gian phát hiện bạn chấp nhận được. Kiểm mỗi phút cho thời gian phát hiện tối đa một phút. Kiểm mỗi năm phút thì sự cố ba phút vẫn có thể lọt.
  • Đừng chỉ kiểm trang chủ. Kiểm tra tổng hợp chỉ đi những đường bạn viết ra. Một đường đăng nhập hỏng trong khi trang chủ vẫn 200 là trường hợp mà kiểm tra tổng hợp làm bạn yên tâm sai.
  • Đừng bỏ giám sát thụ động. Ban ngày nó ngang bằng, và nó thấy được thứ mà kiểm tra tổng hợp không bao giờ thấy: các đường mà người dùng thật đi qua, với dữ liệu thật, ở trạng thái thật.
  • Kiểm tra tổng hợp từ nhiều vùng địa lý còn thấy được thứ ở phần trước: mạng, DNS, chứng chỉ, định tuyến — những thứ mà cả máy chủ lẫn giám sát thụ động đều không thấy vì chúng đứng ở phía bên kia của vấn đề.

Cùng một mù "chỉ thấy khi có ai chạm vào", đổi ngăn xếp

Chuyện "đo thụ động chỉ phản ánh được cái gì đang được dùng, nên im lặng ở nơi vắng lưu lượng" không riêng gì giám sát uptime — nó là cái bẫy chung của mọi phép đo dựa-trên-sự-kiện.

Trong thế giới Java, đúng bài học này là lý do Spring Boot Actuator có cả /health chủ động (liveness/readiness probe mà Kubernetes tự gọi theo lịch) bên cạnh các metric thụ động đếm request. Probe chính là synthetic check ở tầng hạ tầng: nó gọi dịch vụ bất kể có traffic không, nên phát hiện được một pod chết lúc 2 giờ sáng mà metric request-count sẽ không bao giờ thấy vì lúc đó count = 0.

Trong Go, cùng nguyên tắc là lý do các health-check library và uptime tool (nhiều cái viết bằng Go — như blackbox_exporter của Prometheus) chủ động probe endpoint theo interval. blackbox_exporter đúng là hiện thân của bài này: nó tự bắn HTTP/TCP/ICMP tới target theo lịch, tách hẳn khỏi việc có người dùng thật hay không.

Còn ở cơ sở dữ liệu, họ hàng gần nhất là chủ động chạy một SELECT 1 (hay một canary query) theo lịch để biết CSDL còn sống — thay vì đợi một truy vấn của người dùng thất bại mới biết. Connection pool nào cũng có "validation query" chạy định kỳ chính vì lý do này: một replica chết lúc không ai đọc nó sẽ không tự lộ ra qua traffic thật.

Mẫu số chung: đo thụ động chỉ nhìn thấy phần hệ thống đang được sử dụng, nên nó mù đúng ở nơi và lúc không có ai dùng — mà đó thường là nơi lỗi âm thầm tích tụ. Muốn thấy toàn bộ, bạn phải chủ động tạo ra lưu lượng của chính mình (probe, health check, synthetic transaction, canary query) độc lập với người dùng thật. Nên câu hỏi đúng cho bất kỳ hệ giám sát nào không phải "nó có bắt được lỗi khi có traffic không" mà là "vào lúc 3 giờ sáng không một ai gọi, có gì đang gọi thay để biết dịch vụ còn sống không".

Muốn tự tính tỷ lệ sự cố mà giám sát thụ động sẽ bỏ lọt ở giờ thấp điểm của bạn, chạy thử:

docker run --rm python:3.12-slim python - <<'EOF'
import random
random.seed(21)
NGUONG, LAN = 3, 2000

def thu(lam_moi_phut, dai_su_co):
    """Xac suat giam sat thu dong PHAT HIEN duoc su co."""
    bat = 0
    for _ in range(LAN):
        thay = sum(max(0, int(random.gauss(lam_moi_phut, lam_moi_phut)))
                   for _ in range(dai_su_co))
        if thay >= NGUONG: bat += 1
    return 100 * bat / LAN

print("%-22s %14s %14s" % ("luu luong/phut", "su co 5 phut", "su co 15 phut"))
for lam in (0.5, 2, 10, 100):
    print("%-22s %13.0f%% %13.0f%%" % ("%.1f req/phut" % lam, thu(lam,5), thu(lam,15)))
EOF

Cột đầu là lưu lượng lúc thấp điểm của bạn. Nếu dòng tương ứng không phải 100%, đó là tỷ lệ sự cố mà giám sát thụ động sẽ bắt được — phần còn lại đi qua trong im lặng.