Observability cho lập trình viên: Log, Metric, Trace đo thật

Loạt bài nâng cao về khả năng quan sát hệ thống (observability) cho lập trình viên backend: log có cấu trúc, metric, và tracing phân tán. Mỗi bài demo THẬT bằng Go trong container, đo output thật, nêu rõ đánh đổi.

12/12 phần đã đăng Lập trình
1 Structured logging với slog: vì sao log chuỗi tự do là món nợ kỹ thuật bạn trả lúc 3 giờ sáng Lúc sự cố xảy ra, bạn không đọc log — bạn truy vấn nó. Và log chuỗi tự do (fmt.Printf) không truy vấn được: mỗi dòng một định dạng, muốn lọc phải viết regex. Bài này đo thật với log/slog của Go 1.23: cùng một sự kiện in ra chuỗi tự do và JSON có cấu trúc, rồi dùng jq lọc 1000 dòng log theo nhiều field (đếm ERROR theo service, tính latency trung bình). Kèm số đo chi phí thật — và một sự thật ngược đời: log chuỗi nhanh hơn slog gần 1.7 lần. 22/09/2026 · 8 phút đọc 2 Log correlation: gắn trace_id vào context để dựng lại hành trình một request giữa hàng nghìn dòng log Có log JSON đẹp rồi, nhưng giữa 150 dòng log của 50 request chạy song song, đâu là những dòng của riêng một request? Nếu không có ID chung, chúng đan xen theo lịch chạy goroutine và bạn không tách nổi. Bài này đo thật trong Go: một slog.Handler tùy biến tự gắn trace_id lấy từ context, để mỗi request mang một ID xuyên suốt; rồi một câu jq lọc đúng 3 bước của request trace-0007 giữa 150 dòng — và mọi trace_id đều xuất hiện đúng 3 lần, không sót không lẫn. 22/09/2026 · 7 phút đọc 3 Counter và gauge với expvar: hai loại metric nền tảng, và vì sao đếm không atomic mất 80% dữ liệu Log trả lời 'chuyện gì đã xảy ra với request này', metric trả lời 'hệ thống đang thế nào tổng thể'. Bài này đo thật hai loại metric cơ bản nhất bằng expvar của thư viện chuẩn Go: counter (tổng cộng dồn, chỉ tăng) và gauge (mức tức thời, lên xuống). Kèm một bằng chứng đanh thép về tính đồng thời: 50 goroutine cùng ++ một biến int thường làm mất 81.9% số đếm, trong khi expvar.Int (atomic) đếm đúng tuyệt đối. 22/09/2026 · 7 phút đọc 4 Histogram và percentile: vì sao latency trung bình 45ms là lời nói dối, và p99 mới là sự thật Sếp hỏi 'API nhanh không?', bạn trả lời 'trung bình 45ms'. Cả hai đều sai lầm — vì không ai trải nghiệm cái trung bình đó. Bài này đo thật trên 100.000 mẫu latency lệch: mean 44.9ms nhưng p50 chỉ 15.3ms và p99 tới 725ms — trung bình rơi vào khoảng trống không ai gặp. Kèm cách dựng histogram kiểu Prometheus để ước lượng percentile với O(1) bộ nhớ, và đo thẳng sai số của nó (p95 lệch tới -34%). 22/09/2026 · 7 phút đọc 5 Prometheus client trong Go: expose /metrics, đọc định dạng exposition, và hiểu mô hình pull expvar cho counter/gauge nhưng thiếu label và histogram — production cần nhiều hơn. Bài này đo thật với thư viện Prometheus client_golang: khai CounterVec (label method/status) và HistogramVec, tung 1000 request, rồi curl /metrics đọc output thật. Xem histogram tự sinh _bucket{le=...}/_sum/_count ra sao, kiểm số liệu khớp tải bằng grep, và hiểu vì sao Prometheus scrape (pull) thay vì nhận push. 22/09/2026 · 7 phút đọc 6 RED và USE: hai phương pháp chọn đúng metric, để khỏi đo tràn lan mà vẫn mù lúc sự cố Có công cụ metric rồi, câu hỏi khó hơn là ĐO CÁI GÌ. Đo tràn lan làm loãng tín hiệu và nổ cardinality; đo thiếu thì mù lúc sự cố. Hai phương pháp luận cắt gọn: RED (Rate, Errors, Duration) cho service, USE (Utilization, Saturation, Errors) cho tài nguyên. Bài này đo thật một service worker-pool ở tải thấp và tải cao: RED cho thấy lỗi nhảy từ 1.5% lên 6.9%, còn USE chỉ đúng nguyên nhân — pool utilization 100%, queue dồn tới 18, 103 request bị từ chối. 22/09/2026 · 7 phút đọc 7 Distributed tracing: metric nói 'request chậm 39ms', tracing chỉ đúng dbQuery mới là thủ phạm 80% Metric cho biết một request chậm, nhưng không nói chậm ở CHẶNG nào. Tracing giải quyết điều đó bằng span lồng nhau: mỗi chặng một span, tất cả cùng traceID, mỗi span trỏ về cha để dựng lại cây. Bài này tự cài một tracer tối giản trong Go (không cần backend) và đo thật một request đi qua handler → auth → db → cache: cây span cho thấy dbQuery chiếm 31/39ms = 80%, đúng chặng cần tối ưu. 22/09/2026 · 6 phút đọc 8 pprof: đừng đoán hàm nào chậm, đo — CPU profile và heap profile tìm đúng thủ phạm Tracing chỉ ra chặng nào chậm; nhưng trong một chặng, HÀM nào ngốn CPU hay giữ RAM? Đừng đoán — pprof của Go đo trực tiếp. Bài này chạy thật runtime/pprof: CPU profile chỉ ra main.hotHash chiếm 100% CPU (qua sha256), heap profile chỉ ra main.allocBig giữ đúng 197.76MB còn allocSmall biến mất vì đã bị GC. Kèm cách đọc hai cột flat vs cum mà nhiều người nhầm. 22/09/2026 · 7 phút đọc 9 runtime/metrics: quan sát goroutine, heap và GC từ bên trong app Go — bắt rò rỉ goroutine tại chỗ Metric ứng dụng (phần 3-6) cho biết dịch vụ thế nào, nhưng runtime Go bên dưới cũng cần quan sát: bao nhiêu goroutine đang sống, heap lớn bao nhiêu, GC chạy mấy lần và dừng thế giới bao lâu. Bài này đo thật bằng gói runtime/metrics: sinh 5000 goroutine + cấp phát 1.3GB, thấy goroutines 1→5001, 27 lần GC, 0.70ms STW; rồi thả ra thấy goroutines về 1 — chính là cách bắt rò rỉ goroutine, một trong những lỗi Go nguy hiểm nhất. 22/09/2026 · 6 phút đọc 10 Cardinality explosion: một label user_id làm nổ metric store 2000 lần — nguyên nhân số 1 sập Prometheus Label làm metric mạnh lên (cắt lát theo method, status), nhưng đặt sai một label là tự tay cho nổ. Bài này đo thật với client_golang: cùng một counter, label an toàn {method, status} sinh 10 chuỗi thời gian; thêm một label user_id thành 20.000 chuỗi — nổ đúng 2000 lần. Vì cardinality là TÍCH số giá trị mọi label, không phải tổng; và user_id/request_id/URL trong thực tế là vô hạn, chuỗi tăng không ngừng tới khi Prometheus ngốn hết RAM và sập. 22/09/2026 · 6 phút đọc 11 Sampling: head sampling 1% bỏ sót 99% lỗi, error-biased giữ 4% mà bắt 100% lỗi Trace và log mỗi request thì đúng nhất, nhưng hàng triệu request/ngày là khối dữ liệu không kham nổi. Sampling giảm khối lượng — nhưng sampling ngẫu nhiên (head) bỏ mù đúng thứ cần thấy: lỗi hiếm. Bài này đo thật trên 100.000 trace: head sampling 1% chỉ bắt 14/1029 lỗi (bỏ sót 99%); error-biased sampling giữ tổng cộng chỉ 4% mà bắt trọn 1029/1029 lỗi. Cùng bàn cái giá của tail sampling và vì sao metric không sample. 22/09/2026 · 6 phút đọc 12 Ghép log + metric + trace: chẩn đoán một sự cố từ p99 cao tới dòng log lỗi, và cây quyết định debug Mười một phần loạt Observability đi qua từng công cụ; bài cuối ghép chúng lại. Ba trụ cột — metric, trace, log — mỗi cái trả lời một câu hỏi khác nhau, và sức mạnh thật là dùng chúng nối tiếp. Bài này đo thật một sự cố đi qua cả ba: metric cho thấy p99=128ms và 2.8% lỗi, trace chỉ dbQuery chiếm 129ms là thủ phạm, log lọc theo trace_id cho biết chính xác lỗi gì — kèm một cây quyết định 'gặp triệu chứng X thì nhìn trụ cột nào trước'. 22/09/2026 · 6 phút đọc