Lập trình 22/09/2026 6 phút

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.

Lập trình 22/09/2026 6 phút

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.

Lập trình 22/09/2026 6 phút

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.

Lập trình 22/09/2026 6 phút

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'.

Lập trình 22/09/2026 6 phút

MVCC và VACUUM: vì sao UPDATE làm bảng phình 9 lần dù số dòng không đổi

PostgreSQL không sửa dòng tại chỗ — mỗi UPDATE tạo một phiên bản mới và để lại 'xác chết' (dead tuple). Bài này đo thật: một bảng 100.000 dòng sau 8 lần UPDATE toàn bộ phình từ 3.5MB lên 31MB với 799.810 dead tuple, dù vẫn đúng 100.000 dòng sống. VACUUM dọn dead tuple (dead về 0) nhưng không trả đĩa; chỉ VACUUM FULL trả bảng về 3.5MB — nhưng khoá cả đọc. Hiểu MVCC để không bị bloat bất ngờ.

Lập trình 22/09/2026 7 phút

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.