Mười một phần qua, ta đã đi từ "ba trụ cột là gì" tới chi phí của việc vận hành chúng — mỗi bài một demo chạy thật, mỗi kết luận một con số đo được. Nhưng observability không phải ba công cụ rời rạc để dùng riêng lẻ; sức mạnh thật của nó đến khi ba trụ cột nối với nhau thành một hệ thống điều tra liền mạch. Bài cuối này (phần 12, khép lại loạt Observability) làm đúng điều đó: chạy một demo tổng hợp cho một request duy nhất phát cả metric, trace và log cùng một trace_id, chứng minh bằng số thật rằng chúng thực sự dính vào nhau — rồi đúc kết toàn bộ bài học và đưa một checklist thực chiến.

Ba trụ cột trả lời ba câu hỏi khác nhau

Trước khi ghép, nhắc lại vì sao cần cả ba — không cái nào thay được cái nào:

  • Metric trả lời "hệ thống có khoẻ không?" — rẻ, tổng hợp, luôn bật, hợp để cảnh báo. Nhưng nó là số gộp, không chỉ ra request cụ thể nào hỏng.
  • Trace trả lời "request này chậm ở đâu?" — chi tiết một request xuyên nhiều service, chỉ đúng span thủ phạm. Nhưng đắt, phải lấy mẫu.
  • Log trả lời "chuyện gì đã xảy ra?" — ngữ cảnh chi tiết từng sự kiện. Nhưng rời rạc nếu không có định danh chung.

Mắt xích nối ba cái lại là trace_id đóng vai correlation id: khi metric báo động, bạn mở dashboard thấy service nào lỗi (RED), lấy một dòng log lỗi của nó, copy trace_id, dán vào Jaeger — và thấy chính xác request đó đã đi qua đâu, kẹt ở span nào. Ba tín hiệu rời rạc biến thành một đường điều tra.

Đo thật: một request, ba tín hiệu, một trace_id

Mình dựng một service hợp nhất: handler /order khi nhận một request sẽ đồng thời tăng metric Prometheus, tạo span OpenTelemetry (gửi Jaeger), và ghi log JSON — tất cả mang cùng trace_id:

c, span := tr.Start(r.Context(), "POST /order")
tid := span.SpanContext().TraceID().String()
_, s2 := tr.Start(c, "queryDB"); /* ... */ s2.End()   // TRACE
reqs.WithLabelValues(status).Inc(); dur.Observe(d)      // METRIC
jlog.Info("xử lý đơn hàng", "trace_id", tid, "status", status) // LOG

Ảnh chụp output thật nền tối một request phát ba trụ cột cùng một trace_id 127ab909, POST order chạy một lần phát đồng thời ba tín hiệu, metric Prometheus orders_total status 200 bằng 24 status 500 bằng 6 histogram_quantile 0.99 bằng 0.0988s p99, trace Jaeger trace 127ab909 có thật service checkout-unified 2 span POST order tới queryDB, log slog JSON msg xử lý đơn hàng trace_id 127ab909 status 200 duration_ms 69, trace_id 127ab909 là sợi chỉ nối cả ba metric báo động tới log tìm trace_id tới Jaeger

Hình 1: Một request POST /order phát đồng thời ba tín hiệu, cùng trace_id 127ab909…. METRIC: orders_total 200=24/500=6, p99=0.0988s (Prometheus đã scrape). TRACE: Jaeger xác nhận trace 127ab909… có thật (service checkout-unified, 2 span). LOG: dòng JSON mang đúng trace_id đó. Correlation được kiểm chứng thật, không bịa.

Đây là toàn bộ loạt bài gói trong một hình: trace_id 127ab909… xuất hiện trong log, và khi mình hỏi Jaeger bằng chính id đó, Jaeger trả về trace thật của request; cùng lúc metric cho bức tranh tổng. Metric chỉ cho bạn biết "có 6 request lỗi"; log cho biết "request này lỗi, trace_id X"; trace cho biết "request X kẹt ở queryDB". Ba câu hỏi, ba công cụ, một sợi chỉ.

Toàn bộ loạt bài: những con số đo thật

Điều mình tự hào nhất ở loạt này là không có con số nào bịa — mỗi kết luận đến từ một lần chạy thật trong container. Tổng hợp lại:

Ảnh chụp bảng tổng kết 12 phần toàn số đo thật nền tối, 01 ba trụ cột metric trace log mỗi cái một câu hỏi, 02 bốn metric type counter 150 gauge đỉnh 51 histogram summary, 03 PromQL rate 10 5 2 req mỗi giây increase 600 tỉ lệ lỗi 4.4 phần trăm, 04 percentile trung bình 36ms nhỏ hơn nhiều p99 778ms đuôi chậm, 05 RED tổng lỗi 3.9 phần trăm che checkout 9.3 phần trăm, 06 USE util 100 phần trăm mơ hồ saturation 20 trên 20 cộng 41 rớt mỗi giây mới rõ, 07 cardinality một nhãn sai 729 lên 10732 chuỗi, 08 tracing POST 127ms queryDB 91ms 72 phần trăm, 09 structured log JSON cộng trace_id jq lọc 4 ERROR 16 INFO, 10 alerting for 30s pending rồi firing không for firing ngay, 11 chi phí 78 samples mỗi giây ngoại suy 7.3GB cardinality sai 730GB, 12 tổng kết ba trụ cột nối bằng trace_id, checklist gắn observability RED cho mọi endpoint trace_id trong log alert trên triệu chứng canh cardinality

Hình 2: Tổng kết 12 phần — mọi con số đều đo thật trong loạt bài. Từ bốn loại metric, percentile phơi bày đuôi chậm (p99 778ms), RED/USE phát hiện và định vị, cardinality nổ (729→10.732), tracing chỉ span thủ phạm (queryDB 91ms), tới chi phí ngoại suy (730GB khi cardinality sai). Bên dưới là checklist gắn observability vào service.

Checklist thực chiến

Gộp bài học thành việc cần làm khi gắn observability cho một service mới:

  1. Instrument RED trước tiên — một counter {route, status} và một histogram {route} cho mọi endpoint. Tách theo route (chuẩn hoá /user/{id}, đừng để id thật), vì số tổng hợp che mất endpoint hỏng (bài 05).
  2. Chọn metric type đúng — counter cho cái tích luỹ, gauge cho trạng thái tức thời, histogram cho độ trễ (ưu tiên histogram hơn summary để gộp được p99 nhiều instance), và đặt bucket khớp vùng giá trị thật (bài 02, 04).
  3. Structured log + trace_id — log JSON (slog), nhúng trace_id vào mọi dòng để nối sang trace; đẩy dữ liệu cardinality cao (user_id, request_id) vào log chứ không làm nhãn metric (bài 07, 09).
  4. Trace các bước chính — auto-instrument hạ tầng (HTTP/gRPC/DB), span thủ công cho logic nghiệp vụ; propagate context qua mọi ranh giới service; lấy mẫu để kiểm soát chi phí (bài 08).
  5. Alert trên triệu chứng, không trên nguyên nhân — cảnh báo theo RED/USE (tỉ lệ lỗi, p99, saturation) người dùng cảm nhận được, kèm for chống nhiễu và một runbook cho mỗi alert (bài 05, 06, 10).
  6. Canh chi phí từ đầu — theo dõi cardinality ngay khi đặt nhãn, chọn scrape_interval/retention theo tốc độ thay đổi của metric, downsample cho dài hạn (bài 11).

Đánh đổi cần cân nhắc

Observability là đầu tư, không miễn phí — nhưng rẻ hơn downtime. Gắn đủ ba trụ cột tốn công instrument, tốn tiền lưu trữ, tốn kỷ luật duy trì. Nhưng cái giá của không có nó là những giờ mò mẫm lúc sự cố, quy tội sai, sửa nhầm chỗ. Cân bằng thực tế: bắt đầu bằng RED (rẻ, giá trị cao nhất), thêm trace và log có cấu trúc khi hệ phức tạp lên, và luôn hỏi "tín hiệu này có giúp tôi trả lời câu hỏi thật lúc 3h sáng không?" trước khi thêm.

Công cụ không thay được văn hoá. Dashboard đẹp và trace chi tiết vô dụng nếu không ai nhìn, không ai viết runbook, không ai điều tra alert. Observability là thực hành — review dashboard định kỳ, cập nhật alert khi hệ đổi, và biến mỗi sự cố thành một bài học gắn thêm tín hiệu còn thiếu. Loạt bài này dạy cơ chế; biến nó thành thói quen đội ngũ là phần việc của bạn.

Đừng quan sát mọi thứ — quan sát điều quan trọng. Cám dỗ lớn nhất sau khi học observability là instrument tất cả. Nhưng metric không ai nhìn, alert không ai xử lý, trace không ai mở chỉ tạo nhiễu và chi phí. Mục tiêu không phải nhiều dữ liệu nhất mà là trả lời được câu hỏi vận hành nhanh nhất với lượng tín hiệu tối thiểu. Ít mà đúng thắng nhiều mà loạn.

Ba ý mang về

  1. Ba trụ cột nối bằng trace_id thành một hệ thống: đo thật một request phát metric (orders 200=24/500=6, p99 99ms), trace (Jaeger xác nhận trace 127ab909…) và log (JSON cùng trace_id) — correlation id biến ba công cụ rời rạc thành một đường điều tra liền mạch.
  2. Mỗi trụ cột một câu hỏi, dùng đúng chỗ: metric cho "có khoẻ không" (cảnh báo, rẻ, luôn bật), trace cho "chậm ở span nào" (chi tiết, lấy mẫu), log cho "chuyện gì xảy ra" (ngữ cảnh) — không cái nào thay được cái nào, và chúng mạnh nhất khi dùng cùng nhau.
  3. Checklist: RED trước, trace_id trong log, alert trên triệu chứng, canh cardinality: toàn bộ 12 phần đo thật quy về vài nguyên tắc thực chiến — bắt đầu rẻ với RED, nối log↔trace bằng trace_id, cảnh báo cái người dùng cảm nhận kèm runbook, và kiểm soát chi phí/cardinality từ lúc đặt nhãn.

Nguồn