Distributed tracing là lớp quan sát giúp team backend trả lời một câu hỏi rất production: “request này đã đi qua những service nào, mất thời gian ở đâu, lỗi bắt đầu từ đâu?”. Khi hệ thống còn là monolith nhỏ, log theo request ID có thể đủ. Nhưng khi request đi qua API gateway, nhiều service, database, cache, message queue và worker bất đồng bộ, chỉ đọc log rời rạc thường không còn đủ nhanh để debug incident.
Bài này tập trung vào cách thiết kế và vận hành tracing cho microservices ở mức thực chiến: trace, span, context propagation, sampling, OpenTelemetry, liên kết logs-metrics-traces và checklist để tránh biến tracing thành một dashboard đẹp nhưng không dùng được lúc sự cố.

Distributed tracing là gì?
Distributed tracing ghi lại hành trình của một request qua nhiều boundary trong hệ thống. Một trace đại diện cho toàn bộ hành trình. Mỗi span đại diện cho một đơn vị công việc như gọi endpoint, query database, gọi service khác, publish message hoặc xử lý job. Các span có quan hệ cha-con để tạo thành timeline.
Ví dụ, request POST /orders có thể gồm root span ở API gateway, span xử lý ở Order Service, child span gọi Payment Service, span lock inventory, span ghi PostgreSQL và span publish event sang queue. Khi p95 latency tăng, trace giúp thấy hệ thống chậm do Payment Service, do database lock, hay do worker downstream.
Khi nào microservices thật sự cần tracing?
Tracing đặc biệt hữu ích khi hệ thống có nhiều service gọi nhau, request đi qua queue, có retry/circuit breaker, hoặc incident thường mất nhiều thời gian để khoanh vùng. Nếu team chỉ có một service nhỏ, hãy bắt đầu bằng structured logging và metrics trước. Nhưng khi có fan-out/fan-in, dependency chain dài, hoặc SLA nghiêm túc, tracing gần như là nền tảng bắt buộc.
Dấu hiệu nên triển khai tracing: log có request_id nhưng vẫn khó ghép timeline; người trực incident phải mở 5 dashboard khác nhau; lỗi 500 ở gateway nhưng root cause nằm ở service thứ ba; queue redelivery làm mất ngữ cảnh request ban đầu; hoặc p99 latency tăng nhưng metrics không nói rõ đoạn nào chậm.
Trace context propagation: phần dễ sai nhất
Tracing chỉ có ích khi context đi xuyên qua boundary. Với HTTP/gRPC, context thường được truyền qua header như chuẩn W3C traceparent. Với message queue, context cần nằm trong message metadata/header. Nếu service A publish event nhưng worker B tạo trace mới hoàn toàn, timeline sẽ bị đứt và incident vẫn khó debug.

Không nên nhét mọi thứ vào baggage. Baggage làm tăng payload và có thể vô tình truyền dữ liệu nhạy cảm. Hãy giữ context tối thiểu: trace id, span id, sampling flag và vài business correlation field thật sự cần như tenant id đã được kiểm soát.
Instrumentation với OpenTelemetry
OpenTelemetry là lựa chọn phổ biến vì tách phần instrument khỏi vendor backend. Thay vì khóa chặt vào một APM cụ thể, app export traces qua OpenTelemetry Collector rồi đẩy sang Jaeger, Tempo, Datadog, New Relic hoặc backend tương đương.
// Node.js pseudo setup với OpenTelemetry
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
}),
instrumentations: [getNodeAutoInstrumentations()],
serviceName: 'order-service',
});
sdk.start();
Auto-instrumentation là điểm bắt đầu tốt cho HTTP server, HTTP client, database driver và queue client. Nhưng production thường cần thêm manual spans ở các đoạn nghiệp vụ quan trọng: validate payment, reserve inventory, persist outbox event, call anti-fraud service, hoặc generate invoice.
Đặt span attributes thế nào để debug được?
Span attributes nên giúp lọc và hiểu vấn đề, không phải dump toàn bộ request. Các field hữu ích gồm service.name, http.route, db.system, messaging.system, retry.count, tenant_id đã sanitize, order_id hoặc business key không nhạy cảm. Tránh lưu email, token, số thẻ, payload cá nhân hoặc dữ liệu có thể vi phạm privacy.
Một quy tắc tốt: nếu attribute giúp trả lời “lỗi thuộc route nào, tenant nào, dependency nào, retry bao nhiêu lần?” thì nên cân nhắc. Nếu chỉ để tò mò hoặc có rủi ro PII, đừng đưa vào trace.
Sampling: không phải cứ thu 100% là tốt
Thu 100% traces ở môi trường traffic cao có thể tốn chi phí và gây nhiễu. Nhưng sampling quá mạnh lại làm mất trace đúng lúc cần. Cách thực tế là dùng head sampling cho traffic bình thường, tail sampling cho request lỗi/chậm, và luôn giữ traces liên quan đến incident class quan trọng.
Ví dụ policy: giữ 100% traces có error, 100% traces chậm hơn p99 threshold, 10% traces của endpoint critical như checkout, và 1% traffic còn lại. Quan trọng là sampling decision phải được propagate, nếu không mỗi service tự sample khác nhau sẽ làm trace bị mất đoạn.
Liên kết logs, metrics và traces
Tracing không thay thế logs và metrics. Metrics cho biết hệ thống đang có vấn đề, traces giúp khoanh vùng đường đi, logs cho chi tiết tại điểm lỗi. Một setup tốt cho phép từ alert p95 latency nhảy sang trace mẫu, rồi từ trace id mở logs tương ứng.
Vì vậy, structured logs nên luôn có trace_id và span_id. Alert nên gắn link tới dashboard trace query. Runbook incident nên bắt đầu bằng câu hỏi: endpoint nào tăng lỗi, service nào có span chậm, dependency nào có error tag, và trace mẫu nào đại diện cho nhóm lỗi?
Tracing qua queue và worker
Queue là nơi tracing hay bị đứt nhất. Khi publish message, hãy inject trace context vào metadata. Worker khi consume message cần extract context và tạo child span hoặc linked span tùy mô hình. Nếu một message được retry nhiều lần, span nên ghi retry count, delay, dead-letter status và queue name.
Với event-driven system, trace giúp phân biệt lỗi đồng bộ và lỗi bất đồng bộ. Request ban đầu có thể trả thành công, nhưng worker xử lý outbox event thất bại sau đó. Nếu trace hoặc correlation id không nối được hai phần này, team sẽ khó điều tra side effect bị thiếu.
Checklist triển khai distributed tracing trong production

- Đặt
service.name, environment và version/deployment tag thống nhất. - Instrument HTTP server, HTTP client, database, cache, queue producer/consumer.
- Propagate W3C trace context qua HTTP/gRPC và message metadata.
- Thêm manual spans ở các bước nghiệp vụ critical, không chỉ dựa vào auto-instrumentation.
- Gắn
trace_idvào structured logs để jump từ trace sang log. - Thiết kế sampling policy cho error, slow request và critical endpoint.
- Redact PII/token/secrets khỏi span names, attributes và events.
- Theo dõi cardinality của attributes để tránh chi phí và query chậm.
- Viết runbook incident: từ alert → trace mẫu → dependency chậm → log chi tiết.
- Test propagation qua queue và retry, không chỉ test request đồng bộ.
Sai lầm thường gặp
Sai lầm đầu tiên là đặt span name chứa ID động như GET /orders/123, làm cardinality tăng và dashboard khó gom nhóm. Hãy dùng route template như GET /orders/:id. Sai lầm thứ hai là thu traces nhưng không gắn với logs, khiến trace chỉ cho biết “đoạn này lỗi” nhưng không có chi tiết stack hoặc payload đã sanitize.
Sai lầm thứ ba là xem tracing như dự án tool, không phải practice vận hành. Nếu team không dùng traces trong review incident, không thêm span cho flow mới, và không có owner cho sampling/cost, dữ liệu tracing sẽ nhanh chóng cũ hoặc nhiễu.
Kết luận
Distributed tracing tốt không phải là có thật nhiều biểu đồ. Nó là khả năng đi từ một alert production đến root cause trong vài phút: request nào bị ảnh hưởng, service nào chậm, dependency nào lỗi, retry xảy ra ở đâu và business operation nào cần kiểm tra lại.
Nếu team đang xây backend production, hãy nối bài này với System Design cho Backend Developer, Outbox Pattern trong Backend, Idempotency trong API và Deploy Backend lên Production Checklist để hoàn thiện chuỗi thiết kế, vận hành và debug hệ thống production.