Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn đang là kỹ sư vận hành trực đêm cho một sàn thương mại điện tử Việt Nam. 2 giờ sáng, điện thoại reo: khách hàng phàn nàn "trang thanh toán quay vòng mãi không xong". Bạn mở dashboard lên, thấy CPU bình thường, RAM bình thường, request vẫn vào đều. Nhưng khách vẫn không thanh toán được. Bạn không biết chuyện gì đang xảy ra bên trong hệ thống. Đó chính là khoảnh khắc bạn nhận ra sự khác biệt giữa monitoring (giám sát) và observability (khả năng quan sát).
Monitoring trả lời câu hỏi bạn đã biết trước: "CPU có cao không?", "Service có sống không?". Còn observability giúp bạn trả lời những câu hỏi bạn chưa từng nghĩ tới khi sự cố xảy ra: "Tại sao đúng những đơn hàng dùng ví MoMo trên Android ở khu vực miền Trung mới bị treo?". Một hệ thống có observability tốt cho phép bạn suy luận về trạng thái nội bộ của nó chỉ bằng cách quan sát dữ liệu nó phát ra — mà không cần deploy thêm code hay đăng nhập vào từng server.
Cả một chuỗi bài trong khóa học này (Bài 20 về logging, Bài 21 về Golden Signals, Bài 22 về RED/USE, Bài 23 về distributed tracing) đều xoay quanh chủ đề observability. Bài 19 này là tấm bản đồ tổng quan: bạn cần hiểu rõ ba trụ cột — Logs, Metrics, Traces — mỗi trụ cột trả lời câu hỏi gì, khi nào dùng cái nào, và tại sao cả ba phải phối hợp với nhau. Nắm chắc bản đồ này, bạn sẽ debug nhanh hơn gấp nhiều lần và không bị "mù" giữa đêm khuya.
Khái niệm cốt lõi
Observability được xây dựng trên ba loại dữ liệu telemetry (dữ liệu đo lường), thường gọi là 3 Pillars (ba trụ cột): Logs, Metrics, Traces. Cách dễ nhớ nhất: mỗi trụ cột trả lời một câu hỏi khác nhau.
- Metrics trả lời: "Có gì bất thường không, và ở mức độ nào?"
- Traces trả lời: "Vấn đề nằm ở đâu trong hành trình của request?"
- Logs trả lời: "Chính xác chuyện gì đã xảy ra tại điểm đó?"
Logs — sự kiện rời rạc kèm ngữ cảnh
Log là bản ghi của một sự kiện rời rạc (discrete event) xảy ra tại một thời điểm cụ thể. Một dòng log tốt luôn có bốn thành phần: timestamp (thời điểm), level (mức độ: DEBUG/INFO/WARN/ERROR), message (thông điệp), và các trường ngữ cảnh (fields) như user_id, order_id, request_id.
Log mạnh nhất khi bạn cần debug một request cụ thể hay hiểu chi tiết một tình huống. Khi khách hàng "Nguyễn Văn A, đơn hàng #48213" báo lỗi, log là nơi bạn tìm được đúng dòng: ERROR payment failed: gateway timeout, order_id=48213, gateway=momo, latency_ms=30021.
Điểm yếu của log: tốn kém và khó tổng hợp. Một hệ thống lớn sinh hàng terabyte log mỗi ngày. Bạn không thể trả lời câu hỏi "tỷ lệ lỗi thanh toán trong 5 phút qua là bao nhiêu?" một cách nhanh và rẻ chỉ bằng cách đọc log. Công cụ phổ biến: ELK (Elasticsearch, Logstash, Kibana), Grafana Loki, Datadog Logs. Chi tiết về cấu trúc log chuẩn sẽ được đào sâu ở Bài 20.
Metrics — số liệu tổng hợp theo thời gian
Metric là một con số đo được tại các mốc thời gian đều đặn — ví dụ số request mỗi giây, độ trễ p95, tỷ lệ lỗi, phần trăm CPU. Bản chất metric là time-series (chuỗi thời gian): một tên metric + các nhãn (labels) + một dãy giá trị theo thời gian.
Sức mạnh của metric là rẻ, gọn, tổng hợp nhanh. Dù bạn có 10 triệu request, metric http_requests_total chỉ là một con số đếm tăng dần, tốn rất ít tài nguyên lưu trữ. Nhờ vậy metric là nền tảng lý tưởng cho dashboard và alerting (cảnh báo). Đây chính là lớp "báo động" đầu tiên: khi tỷ lệ lỗi vượt ngưỡng, metric kích hoạt alert.
Điểm yếu: metric mất ngữ cảnh chi tiết. Nó cho bạn biết "5% request đang lỗi" nhưng không cho biết request nào của user nào. Đó là lý do metric có "cardinality problem" (vấn đề số chiều): nếu bạn gắn user_id làm label, số chuỗi thời gian bùng nổ và hệ thống sập. Công cụ phổ biến: Prometheus, Grafana, VictoriaMetrics, Datadog. Các bộ metric chuẩn (4 Golden Signals, RED, USE) sẽ được học ở Bài 21 và 22.
Traces — hành trình của một request qua nhiều service
Trong kiến trúc microservices, một request từ khách hàng có thể đi qua 10–20 service khác nhau: API Gateway → Auth → Order → Inventory → Payment → Notification... Khi request chậm, câu hỏi khó nhất là: chậm ở service nào?
Trace trả lời chính xác câu hỏi đó. Một trace là bức tranh toàn cảnh hành trình của một request, được ghép từ nhiều span — mỗi span là một đoạn công việc tại một service, có thời điểm bắt đầu, thời gian kéo dài, và quan hệ cha-con với span khác. Nhờ một trace_id xuyên suốt, bạn thấy được: request tiêu 30ms ở Auth, 15ms ở Order, nhưng tới 3.000ms ở Payment — thủ phạm lộ diện ngay.
Trace là mảnh ghép quan trọng nhất cho hệ phân tán, và thường được sampling (lấy mẫu) — bạn không lưu 100% trace mà chỉ giữ một tỷ lệ (ví dụ 1–10%) để tiết kiệm chi phí. Tiêu chuẩn ngành hiện nay là OpenTelemetry để thu thập, và Jaeger hoặc Tempo để lưu trữ, hiển thị. Distributed tracing được đào sâu ở Bài 23.
Sợi dây liên kết ba trụ cột
Điều khiến observability hiện đại thực sự mạnh không phải là ba trụ cột đứng riêng, mà là khả năng nhảy qua lại giữa chúng. Nếu mỗi log, mỗi metric exemplar, mỗi span đều mang chung một trace_id, bạn có thể: nhìn thấy đỉnh latency trên biểu đồ metric → nhấp vào đó nhảy sang trace tương ứng → từ trace nhảy tiếp sang đúng dòng log của service bị chậm. Ba trụ cột liên kết bằng correlation ID chính là "chén thánh" của observability.
Tình huống thực tế
Ví dụ 1 — Sàn TMĐT Việt Nam trong ngày sale 12.12
Một sàn thương mại điện tử tại TP.HCM (giả định tên "ShopFast") gặp sự cố trong đợt sale 12.12. Lúc 20:15, đội trực nhận alert: metric checkout_error_rate nhảy từ 0,3% lên 7% trong vòng 4 phút. Đây là Metrics làm nhiệm vụ báo động.
Đội mở dashboard, thấy latency p99 của luồng checkout tăng vọt. Họ nhấp vào một exemplar trên biểu đồ để mở trace. Trace cho thấy: request tốn 42ms qua Order Service, nhưng span gọi Payment Service kéo dài 8.900ms rồi timeout. Vấn đề được khoanh vùng vào Payment Service — không phải database, không phải cache, không phải mạng chung.
Cuối cùng, họ lọc log của Payment Service theo trace_id và thấy dòng: ERROR connection pool exhausted, active=200, max=200, waiting=1500. Nguyên nhân gốc: connection pool tới cổng thanh toán quá nhỏ so với lưu lượng ngày sale. Họ tăng pool size, sự cố hết sau 12 phút.
Bài học: Cả ba trụ cột phối hợp nhịp nhàng. Metrics phát hiện có vấn đề, Traces chỉ ra ở đâu, Logs giải thích tại sao. Nếu chỉ có log, đội đã mất hàng chục phút grep giữa hàng triệu dòng của 15 service mà chưa chắc tìm ra.
Ví dụ 2 — Startup fintech đốt tiền vì log
Một startup fintech ở Hà Nội (giả định "PayNow") ban đầu chỉ dùng logs cho mọi thứ — kể cả để đếm số giao dịch, tính tỷ lệ thành công, vẽ biểu đồ doanh thu bằng cách query log. Sau 8 tháng, hóa đơn Datadog Logs của họ vọt lên hơn 6.000 USD/tháng, chiếm gần 40% chi phí hạ tầng. Tệ hơn, mỗi lần cần biết "tỷ lệ giao dịch thành công 5 phút qua", dashboard mất 20–30 giây để chạy xong vì phải quét log.
Đội tái cấu trúc theo đúng nguyên tắc 3 trụ cột: chuyển toàn bộ con số tổng hợp (số giao dịch, tỷ lệ lỗi, latency) sang metrics với Prometheus; chỉ giữ log cho debug từng giao dịch cụ thể; đồng thời hạ log level mặc định từ DEBUG xuống INFO ở production. Kết quả: hóa đơn log giảm còn 1.400 USD/tháng, dashboard phản hồi dưới 1 giây.
Bài học: Dùng sai trụ cột cho sai mục đích trả giá rất đắt. Log không phải công cụ để tính toán số liệu tổng hợp — đó là việc của metrics. Chọn đúng trụ cột cho đúng câu hỏi tiết kiệm cả tiền lẫn thời gian.
Ví dụ 3 — Grab và nỗi đau microservices không có trace
Ở quy mô như Grab (hoạt động khắp Đông Nam Á, hàng trăm microservices), một request đặt xe đi qua rất nhiều tầng: định vị, tính giá, ghép tài xế, thanh toán, thông báo. Giả sử người dùng ở Đà Nẵng báo "app tính giá chậm". Nếu chỉ có metrics và logs, kỹ sư sẽ thấy hệ thống tổng thể vẫn khỏe, và phải mở log của từng service để đoán mò.
Với distributed tracing dùng OpenTelemetry, kỹ sư mở đúng trace của request đó và thấy ngay: 90% thời gian nằm ở một lời gọi tới service "surge pricing" đang chờ một cache miss rồi query database chậm. Không cần đoán, không cần đọc log của 12 service khác.
Bài học: Với hệ thống một khối (monolith) nhỏ, metrics + logs có thể đủ. Nhưng khi bạn đã có microservices, traces là trụ cột không thể thiếu — thiếu nó, debug hệ phân tán chẳng khác gì tìm kim trong đống rơm giữa đêm tối.
Hướng dẫn từng bước
Nếu bạn đang xây dựng nền tảng observability cho một hệ thống, đây là lộ trình thực dụng:
- Bắt đầu bằng Metrics. Đây là lớp rẻ nhất và cho giá trị nhanh nhất. Cài Prometheus (hoặc dùng dịch vụ như Datadog), đo bốn chỉ số cốt lõi cho mỗi service: request rate, error rate, latency (p50/p95/p99), và mức sử dụng tài nguyên. Dựng dashboard và alert dựa trên chúng.
- Chuẩn hóa Logs có cấu trúc. Chuyển từ log dạng văn bản tự do sang structured logging (log JSON có trường rõ ràng). Bắt buộc mỗi dòng log mang: timestamp, level, service name, và một
request_id/trace_id. Đây là nền tảng để liên kết về sau.
- Thêm Traces khi hệ thống phân tán. Tích hợp OpenTelemetry SDK vào các service. Bắt đầu với sampling thấp (1–5%) để kiểm soát chi phí, và luôn giữ trace của các request bị lỗi (tail-based sampling ưu tiên lỗi).
- Liên kết ba trụ cột bằng một ID chung. Đảm bảo
trace_idđược truyền qua HTTP header giữa các service và được ghi vào cả log lẫn metric exemplar. Đây là bước biến ba trụ cột rời rạc thành một hệ thống điều tra liền mạch.
- Thiết lập quy trình điều tra. Huấn luyện đội theo thứ tự: nhận alert từ Metrics → mở Trace để khoanh vùng → lọc Log theo
trace_idđể tìm nguyên nhân gốc. Viết quy trình này vào runbook (xem Bài 10).
- Rà soát chi phí định kỳ. Mỗi tháng kiểm tra: trụ cột nào đang tốn nhất? Có metric cardinality cao bất thường không? Log level có bị để DEBUG ở production không? Sampling rate của trace có hợp lý không?
Lỗi thường gặp & mẹo
Lỗi 1 — Dùng log để làm mọi thứ. Đây là sai lầm phổ biến nhất, như ví dụ PayNow. Log đắt và chậm khi tổng hợp. Mẹo: mỗi khi định query log để đếm hay tính trung bình, hãy tự hỏi "cái này có nên là một metric không?".
Lỗi 2 — Metric cardinality bùng nổ. Gắn user_id, email, hoặc order_id làm label của metric sẽ tạo hàng triệu chuỗi thời gian và làm sập Prometheus. Mẹo: label chỉ dùng cho giá trị có tập hữu hạn nhỏ (như status_code, region, service). Danh tính chi tiết thuộc về log và trace.
Lỗi 3 — Ba trụ cột không liên kết. Có đủ logs, metrics, traces nhưng không dùng chung trace_id — bạn vẫn phải nhảy giữa các công cụ và ghép thủ công bằng timestamp, rất chậm. Mẹo: chuẩn hóa việc truyền và ghi trace_id ngay từ đầu, đây là khoản đầu tư đáng giá nhất.
Lỗi 4 — Log level để DEBUG ở production. Sinh ra khối lượng log khổng lồ, đốt tiền và làm nhiễu tín hiệu. Mẹo: mặc định INFO ở production, chỉ bật DEBUG tạm thời khi cần điều tra và có thể bật theo từng service.
Lỗi 5 — Sampling trace quá thấp cho request lỗi. Nếu bạn sampling ngẫu nhiên 1%, đa số trace của các lỗi hiếm sẽ bị bỏ qua — đúng lúc cần thì không có. Mẹo: ưu tiên giữ 100% trace của request lỗi hoặc chậm bất thường.
Mẹo tổng quát: Đừng cố hoàn hảo cả ba trụ cột ngay từ ngày đầu. Metrics trước, structured logs sau, traces khi thực sự phân tán. Observability là hành trình tăng dần, không phải công tắc bật/tắt.
Bài tập thực hành
- Phân loại câu hỏi. Với mỗi câu hỏi vận hành sau, hãy xác định trụ cột nào (Logs / Metrics / Traces) phù hợp nhất để trả lời:
- Thiết kế một dòng log tốt. Viết ra một dòng structured log (dạng JSON) cho sự kiện "thanh toán thất bại do timeout cổng MoMo", đảm bảo có đủ: timestamp, level, message, và ít nhất bốn trường ngữ cảnh trong đó có
trace_id.
- Phát hiện lỗi cardinality. Cho danh sách label sau của một metric
http_requests_total:method,status_code,endpoint,user_email,session_id. Chỉ ra label nào sẽ gây bùng nổ cardinality và giải thích nên chuyển thông tin đó sang đâu.
- Vẽ quy trình điều tra. Dựa trên Ví dụ 1 (ShopFast ngày sale), vẽ một sơ đồ ngắn thể hiện dòng chảy điều tra qua ba trụ cột, ghi rõ mỗi bước dùng trụ cột nào và trả lời câu hỏi gì.
- Đánh giá hệ thống hiện tại. Nếu bạn đang làm ở một dự án, hãy liệt kê: dự án đang có trụ cột nào, thiếu trụ cột nào, và ba trụ cột có liên kết bằng ID chung chưa. Đề xuất một cải tiến ưu tiên cao nhất.
Tóm tắt
Observability được xây trên ba trụ cột bổ trợ nhau, mỗi trụ cột trả lời một câu hỏi:
- Metrics — con số tổng hợp theo thời gian, rẻ và nhanh, lý tưởng cho dashboard và alert. Trả lời: "Có bất thường không?". Cẩn thận với cardinality.
- Traces — hành trình của một request qua nhiều service, ghép từ các span. Trả lời: "Vấn đề ở đâu?". Không thể thiếu trong microservices; dùng OpenTelemetry + Jaeger/Tempo.
- Logs — sự kiện rời rạc kèm ngữ cảnh chi tiết, tốt để debug một request cụ thể. Trả lời: "Chính xác chuyện gì xảy ra?". Đắt khi tổng hợp; dùng ELK/Loki/Datadog.
trace_id chung để điều tra liền mạch: Metrics báo động → Traces khoanh vùng → Logs xác định nguyên nhân. Chọn đúng trụ cột cho đúng câu hỏi vừa giúp bạn debug nhanh hơn, vừa tránh đốt tiền vô ích. Các bài tiếp theo (20–23) sẽ đào sâu từng trụ cột — bài này là tấm bản đồ giúp bạn không bao giờ bị lạc.