Chapter 05 — Trace causality và error propagation với Tempo¶
Trace không tự tìm ra root cause. Nó chỉ trở thành causal evidence khi parent/link đúng, queue/retry được mô hình hóa, sampling coverage được công bố và thời gian được đọc cùng uncertainty. Chương này dựng lại failure path của payment incident bằng span cụ thể; Tempo là storage/query implementation ở phần reference.

Poster: dựng critical path, theo error propagation và giữ sampling coverage như một phần của độ tin cậy.
Prerequisites¶
- 01 — Observability — các khái niệm về trace, sampling
- 02 — OpenTelemetry — thu thập và lấy mẫu (sampling) trace
- 04 — Loki — các mẫu thiết kế kiến trúc phân tán song song
Related Documents¶
- 02 — OpenTelemetry — tail sampling trước khi đẩy dữ liệu vào Tempo
- 03 — Prometheus — exemplars liên kết metrics với Tempo traces
- 11 — Root Cause Analysis — traces làm đầu vào cho RCA
Next Reading¶
Sau chương này, hãy chuyển sang 06 — Telemetry Data Plane (normalize / enrich / store / feature), rồi 07 — Kafka.
Cách đọc chương này¶
Phần I đọc một failed trace, sau đó cố tình làm mất span, đảo clock, thêm retry, async queue và fan-out để xem RCA còn đứng vững không. Phần II giữ Tempo/TraceQL/storage/sampling reference. Đích đến là một trace evidence contract cho Chapter 10–11, không phải một UI tìm trace đẹp.
Phần I — Từ request path đến causal evidence¶
Failed trace của một checkout¶
Đơn vị thời gian dưới đây tính từ lúc gateway nhận request. Các span cùng trace, trừ Kafka consumer dùng span link vì xử lý async:
| Span | Start | Duration | Status | Evidence |
|---|---|---|---|---|
gateway POST /checkout |
0 ms | 6.340 ms | error | Customer request boundary |
checkout create_order |
8 ms | 6.280 ms | error | Parent business operation |
order-api POST /orders |
25 ms | 6.190 ms | error | Chờ payment result |
kafka produce payment.requested |
41 ms | 12 ms | ok | Async handoff |
| queue wait | 53 ms | 1.820 ms | unset | Broker/consumer lag, không phải app CPU |
| payment-worker attempt 1 | 1.873 ms | 2.030 ms | error | Retry attempt 1 |
payment-db pool.acquire #1 |
1.891 ms | 2.000 ms | error | db.pool.acquire_timeout |
| payment-worker backoff | 3.903 ms | 210 ms | unset | Retry delay |
| payment-worker attempt 2 | 4.113 ms | 2.040 ms | error | Retry attempt 2 |
payment-db pool.acquire #2 |
4.130 ms | 2.000 ms | error | Cùng failure family |
| order-api compensation | 6.170 ms | 35 ms | ok | Chuyển order sang pending |
Nếu chỉ nhìn span dài nhất, gateway 6.340 ms đứng đầu. Nếu nhìn service có nhiều error spans, payment-worker có thể thắng. Nếu nhìn operation đỏ sớm nhất theo trace, payment-db pool.acquire xuất hiện sau queue wait nhưng là nơi failure bắt đầu trong execution. Root-cause reasoning phải giải thích vì sao DB pool candidate giải thích queue/retry/downstream errors tốt hơn, không xếp theo duration/count đơn thuần.
Phân biệt critical path, causal path và error path¶
Ba khái niệm thường bị trộn:
- Critical path: chuỗi spans quyết định latency end-to-end. Trong trace này gồm queue wait + hai pool timeouts + backoff.
- Error propagation path: nơi status/outcome lỗi lan ngược từ DB → worker → order → checkout → gateway.
- Candidate causal path: chuỗi điều kiện có thể tạo failure. Pool saturation là evidence ngoài trace; trace chỉ thấy acquire timeout.
Một child span lỗi không phải lúc nào gây parent lỗi. Analytics call có thể fail nhưng checkout fallback thành công. Ngược lại, parent có business error dù child status OK vì semantic outcome sai. Propagation engine cần policy operation/failure family, không chỉ OR mọi status=error.
Downstream weighting: root candidate giải thích bao nhiêu triệu chứng?¶
Topology quan sát được:
payment-db → payment-worker → order → checkout → gateway, và order event ảnh hưởng notification.
Nếu payment-db onset phù hợp và failed spans lan qua bốn downstream services, candidate nhận downstream explanatory weight cao. Gateway 5xx có nhiều traffic hơn nhưng không có upstream reach; nó là symptom boundary.
Weight không nên chỉ là số node. Một notification service không critical bằng payment path; edge cần criticality, traffic fraction, success/error propagation rate và freshness. Shared dependency như DNS có downstream lớn nhưng nếu DNS probes/control group khỏe, negative evidence hạ điểm.
Temporal order: “đỏ trước” cần hiệu chỉnh detector delay¶
Giả sử detector timestamps:
| Signal | Alert time | Detector/window delay | Estimated onset interval |
|---|---|---|---|
| Gateway 5xx | 10:11:30 | 30–60 s | 10:10:30–10:11:00 |
| Payment timeout | 10:11:20 | 30–60 s | 10:10:20–10:10:50 |
| DB pool saturation | 10:12:00 | 2-min averaging | 10:10:00–10:10:30 |
| DB acquire error spans | 10:10:44 | 2–5 s ingest | 10:10:39–10:10:42 |
DB metric alert đến sau gateway nhưng estimated onset sớm hơn. Trace event time giúp, song clock uncertainty vẫn tồn tại. Nếu DB node chậm 90 giây, raw span timestamps có thể đặt DB “trước” giả. RCA Chapter 11 dùng onset intervals, ingest delay và clock health; các interval overlap được coi tie thay vì tạo thứ tự chắc chắn giả.
Retry làm duration/count nói dối¶
Một operation có hai attempts. Nếu mỗi attempt tạo trace root mới, engine thấy hai incidents/request. Nếu attempts là child spans nhưng không ghi attempt/backoff, engine có thể kết luận DB lỗi hai lần độc lập.
Cần giữ:
- stable operation/business correlation ID có privacy policy;
- attempt number và retry reason;
- original deadline và remaining budget;
- backoff duration;
- outcome cuối;
- idempotency/side-effect status.
Retry amplification có thể trở thành root của giai đoạn sau dù DB pool là trigger ban đầu. Ví dụ tắt retry làm DB query 2.300 → 900/s và success tăng 78% → 95%, nhưng pool leak vẫn còn. RCA nên biểu diễn “trigger: pool leak; amplifier: retry; downstream symptoms”, không ép mọi incident chỉ có một nhãn root.
Async queue: trace ngắn không có nghĩa request nhanh¶
Producer mất 12 ms, consumer processing chỉ 180 ms nhưng message chờ 140 giây. Nếu trace model chỉ nối spans khi consumer bắt đầu, service map không hiển thị queue wait. Event produced time/broker time/processing time cần trở thành explicit wait evidence hoặc derived span/feature.
Với batch 100 messages, một consumer span không nên là child của một producer ngẫu nhiên. Dùng links cho từng message hoặc batch summary có coverage; nếu chỉ giữ 10 links vì limit, quality flag phải nói partial. Correlation theo incident scope, không theo một parent giả.
Fan-out: child lỗi có thể không ảnh hưởng customer¶
Checkout song song gọi inventory, recommendations và fraud. Recommendations timeout nhưng fallback cache; payment DB timeout làm request fail. Cả hai child đỏ.
Phân biệt bằng:
- dependency criticality/optional edge;
- parent outcome;
- cancellation/fallback events;
- proportion failed traces có child failure;
- control group successful traces.
Nếu recommendations timeout xuất hiện ở 30% success traces lẫn 32% failed traces, nó là tương quan nền. Payment acquire timeout xuất hiện 84% failed và 0,2% success traces, evidence mạnh hơn. So với control group quan trọng hơn việc một span “đỏ”.
Span error propagation cần semantic outcome¶
Các trường hợp status đánh lừa:
- HTTP 200 nhưng body
processor_unavailable. - gRPC cancelled vì parent deadline, child không phải origin.
- DB query trả zero rows hợp lệ nhưng application log ERROR.
- Client timeout trong khi server hoàn tất và tạo side effect.
- Circuit breaker trả fallback nhanh với status OK nhưng degraded outcome.
Propagation rule nên map protocol status, application outcome, cancellation origin và failure family. “Error=true” là feature thô, không phải verdict.
Partial trace: RCA phải suy luận có điều kiện¶
Trong 100 failed requests:
- 84 trace đầy đủ có
payment-db pool.acquirelỗi; - 11 trace mất DB child nhưng payment span ghi cùng failure family;
- 3 trace chỉ có gateway/checkout vì async context gãy;
- 2 trace là card-network declines không liên quan.
RCA có thể nói: “DB pool giải thích 84/100 observed failed traces và 95/97 traces có đủ payment context; overall trace coverage 87%.” Không được nói “DB gây 84% mọi lỗi” nếu sampling/inclusion chưa cho phép ước lượng.
Khi root span thiếu nhưng children còn, trace store có thể xem trace partial. Orphan rate và missing expected edges là quality evidence. Không tự nối spans chỉ vì service/time gần nhau; đó là correlation probabilistic và phải đánh dấu.
Sampling bias và trace-derived metrics¶
Giữ 100% error traces, 1% success traces làm dataset có error ratio quan sát cực cao. Span metrics dùng để tính service error rate chỉ đúng nếu tạo trước biased sampling hoặc có weighting/inclusion probability phù hợp.
Tail sampling cũng ảnh hưởng novelty: rare success path có thể không bao giờ thấy; detector tưởng operation mới khi policy đổi. Mọi feature từ trace cần sampling policy/version và coverage theo service/route/outcome.
Exemplar là đường vào tốt từ metric spike tới trace cụ thể, nhưng một exemplar không đại diện distribution. Dùng exemplar để điều tra, rồi query/aggregate một tập trace có thiết kế mẫu rõ.
Từ traces sang service graph: observed graph không phải intended graph¶
Trace-derived graph cho biết edge đã quan sát trong window. Nó bỏ edge không có traffic/sampled, có thể thêm edge tạm do debug/canary và không biết ownership/criticality. Chapter 08 hợp nhất observed graph với service catalog, mesh/cloud/config sources.
Mỗi edge nên có:
- direction và protocol;
- first/last observed, request volume;
- success/error/latency distribution;
- sampling coverage;
- environment/region/tenant scope;
- critical/optional semantics từ source đáng tin;
- freshness/confidence.
Một graph snapshot theo incident time quan trọng hơn graph hiện tại. Sau rollback, topology mới không được dùng để giải thích incident cũ.
Trace search: bắt đầu từ incident, không quét tùy ý toàn kho¶
Điểm vào hiệu quả:
- Metric anomaly có scope/window và exemplar.
- Log failure signature có trace ID.
- Incident có service/operation/failure family để tìm cohort traces.
Query “mọi trace chậm trong 24 giờ” vừa đắt vừa trộn regimes. Query cohort nên có window, service/operation, deployment/region và control group. Tempo/TraceQL hay backend khác chỉ là cách thực hiện.
Business ID có thể giúp support điều tra một giao dịch nhưng có cardinality/privacy cao. Tokenize, access control và audit; không biến customer ID thành indexed attribute mặc định.
Storage/cost phải bảo vệ rare evidence¶
Giả sử 100.000 req/s × 15 spans × 1 KB = khoảng 1,5 GB/s trước compression, tương đương quy mô rất lớn mỗi ngày. Không thể quyết định “giữ tất cả” mà không nói retention, sampling và query pattern.
Policy theo decision value:
- giữ errors/slow tails/rare failure families lâu hơn;
- giữ success sample đại diện cho control group;
- giữ critical transaction paths ưu tiên;
- giảm verbose/internal spans ít giá trị;
- tách hot investigation window và cold replay retention;
- đo cost per retained useful trace, không chỉ $/GB.
Nếu chỉ giữ lỗi, mất control group và dễ gán nhầm correlation. Nếu chỉ random sample thấp, mất rare faults. Cần stratified policy và budget guardrail per tenant/service.
Failure modes của trace platform¶
Trace hoàn tất muộn. Root tới trước, child tới sau decision wait. Search sớm thấy partial; revision policy cập nhật evidence.
Collector sharding sai. Spans cùng trace vào sampler khác nhau và bị quyết định thiếu. Coverage/orphan metrics phát hiện.
Tempo ingest thành công, query chưa thấy. Compaction/index/query lag làm agent nhận empty. Response phải phân biệt not-found, partial và backend error.
Trace ID collision/reuse bug. Hai request dính một trace tạo impossible concurrency. Validation giới hạn duration/service graph và quarantine.
Clock skew tạo negative duration. Không sửa im lặng; giữ raw time, corrected time/uncertainty và quality flag.
Large trace. Fan-out hàng chục nghìn spans bị truncate; root-cause evidence có thể nằm phần mất. Quota kèm truncation marker, aggregate branch evidence.
PII/secrets trong attributes. Tail sampler giữ mọi error vô tình giữ payload nhạy cảm nhiều hơn. Redact trước storage/sampling export, allowlist attributes.
Multi-tenant leakage. Trace search theo ID phải enforce tenant, kể cả ID bị log ở ticket tenant khác.
Golden replay chứng minh Chapter 05¶
Replay gồm:
- Trace đầy đủ với queue wait, hai attempts và DB acquire timeout.
- Mất DB child spans ở 30% traces.
- Clock DB lệch -90 giây.
- Recommendations lỗi trên cả success và failed control group.
- Async batch có 100 producers và partial links.
- Tail sampling policy đổi giữa incident.
- Trace backend trả partial/not-found trong ba phút.
- Fault auth nổ chồng với path riêng tại 10:37.
Điều kiện đạt:
- Root ranking không chọn gateway chỉ vì duration/count.
- Pool timeout vẫn là top candidate khi một phần child span mất, nhưng confidence giảm.
- Clock skew không làm DB được tuyên bố “đỏ trước” chắc chắn ngoài evidence.
- Optional recommendations bị loại bằng successful control group.
- Queue wait được tính vào customer path.
- Sampling change không bị hiểu là recovery/novelty.
- Backend partial không trả zero evidence giả.
- Auth traces tạo failure episode riêng dù cùng gateway parent symptoms.
Output contract sang Chapter 10–11¶
Trace evidence gồm: trace/operation identity, parent/link graph, event intervals và clock uncertainty, retry/batch semantics, semantic outcome/failure family, critical/optional edge, sampled cohort/coverage, partial/truncation flags, deployment/scope và control-group statistics. RCA dùng nó cùng metric/log/topology/change evidence; trace không có quyền tự mình tuyên bố root cause.
Phần II — Tempo và distributed tracing reference¶
Phần dưới giải thích Tempo, storage/query path, TraceQL, span metrics, sampling, deployment, security và cost. Dùng nó để giữ/query trace evidence ở Phần I với coverage và failure semantics rõ ràng.
1. Why Tempo?¶
Ý TƯỞNG
Tempo không cố gắng trở thành "Elasticsearch cho traces". Nó chọn triết lý store-first, find-by-id later: chấp nhận không index mọi attribute để đổi lấy khả năng lưu mọi thứ rẻ trên object storage. Trong AIOps, điều này khớp với thực tế — hầu hết đường vào trace đều là trace_id từ metric exemplar hoặc log, không phải full-text search trên span attribute tùy ý.
Tip
Khi nào chọn Tempo ngay từ đầu? Khi stack đã có Grafana + Prometheus + Loki, và bạn cần correlating ba tín hiệu bằng một trace_id. Đừng chọn Tempo nếu bạn cần search tag ad-hoc kiểu Jaeger UI cho mọi attribute mà không muốn đầu tư pipeline index / TraceQL.
The Distributed Tracing Problem at Scale¶
Các hệ thống tiền nhiệm như Jaeger và Zipkin lưu trữ traces trong các cơ sở dữ liệu: - Jaeger → Cassandra hoặc Elasticsearch - Zipkin → MySQL, Cassandra, Elasticsearch
Vấn đề: Tại mức 100K requests/giây với trung bình mỗi trace gồm 15 spans:
100,000 req/giây × 15 spans × 2KB/span = 3GB/giây dung lượng dữ liệu trace
3GB/giây × 86400 giây = ~260TB/ngày
Lưu trữ 260TB/ngày trong Cassandra hoặc Elasticsearch phát sinh chi phí đắt đỏ vượt ngoài khả năng chi trả.
Tempo's Solution¶
Lưu trữ traces trên bộ lưu trữ đối tượng (S3) dưới dạng các files Parquet. Không đánh chỉ mục (index). Không cơ sở dữ liệu. Hoàn toàn dựa trên object storage.
Sự đánh đổi:
- ✅ Khả năng mở rộng không giới hạn với chi phí S3 cực thấp ($0.023/GB/tháng)
- ✅ Không cần vận hành và quản lý các clusters Cassandra/Elasticsearch phức tạp
- ✅ Tích hợp tự nhiên với Grafana
- ❌ Không thể tìm kiếm theo các thuộc tính span tùy ý (chỉ có thể tìm theo TraceID)
- ❌ Việc tìm kiếm dựa trên thẻ tags yêu cầu phải có một chỉ mục riêng biệt (các pipelines TraceQL / bộ chỉ mục tag của Tempo)
Khi nào việc tìm kiếm theo TraceID là đủ: Đối với hầu hết các trường hợp sử dụng của AIOps, bạn tìm đến một trace thông qua: - Một Prometheus exemplar (từ đỉnh spike của metric → TraceID) - Một Loki log entry (từ log error → TraceID trong log body) - Một thông tin ghi chú cảnh báo (từ alert → TraceID của yêu cầu bị lỗi)
Bạn hiếm khi cần phải tìm kiếm theo một thuộc tính span tùy ý trong môi trường production AIOps.
Khi nào Trace "đủ" — và khi nào chưa?¶
| Nhu cầu | TraceID lookup (Tempo mặc định) | Cần TraceQL / index |
|---|---|---|
| RCA từ alert có exemplar | ✅ Đủ | Không |
Log error → trace_id trong body |
✅ Đủ | Không |
| "Tìm mọi request timeout của payment trong 1h" | ❌ | ✅ Cần |
| "Tìm order_id = X xuyên services" | ❌ | ✅ (attribute phải được index/searchable) |
| Service graph / RED metrics | ✅ Qua SpanMetrics | Không bắt buộc |
Warning
Nếu team SRE thường xuyên phải "tìm theo customer_id / order_id" trong Tempo mà không có đường vào từ log/metric, bạn đang thiết kế sai điểm vào observability. Hãy inject trace_id vào log structured và đưa business ID vào log body + span attribute có kiểm soát — đừng biến Tempo thành search engine full-text.
2. Tempo vs Jaeger vs AWS X-Ray¶
| Chiều so sánh | Tempo | Jaeger | AWS X-Ray |
|---|---|---|---|
| Bộ lưu trữ | S3 (object store) | Cassandra / Elasticsearch | AWS managed |
| Tính năng tìm kiếm | TraceID + TraceQL (tìm tag qua index) | Tìm kiếm tag đầy đủ | TraceID + bộ lọc cơ bản |
| Khả năng mở rộng | Không giới hạn (S3) | Bị giới hạn bởi database cluster | Không giới hạn (managed) |
| Chi phí (cho 10M spans/ngày) | Khoảng ~$5/tháng S3 | Khoảng ~$500/tháng Cassandra | Khoảng ~$50/tháng |
| Thiết lập | Trung bình | Trung bình-Cao | Thấp (chỉ cần SDK) |
| Tích hợp AWS | Thủ công (OTel) | Thủ công (OTel) | Tự nhiên (AWS SDK) |
| Tích hợp Grafana | ✅ Tự nhiên | ✅ Qua plugin | ✅ Qua plugin |
| Ngôn ngữ truy vấn | TraceQL | JaegerQL | Các bộ lọc cơ bản |
| Đa người thuê (Multi-tenancy) | ✅ Có hỗ trợ | ❌ Hạn chế | ❌ Theo tài khoản |
| Tương quan Exemplar | ✅ Tự nhiên với Prometheus | ❌ Thủ công | ❌ |
| Giấy phép bản quyền | AGPLv3 | Apache 2.0 | Sở hữu riêng của AWS |
Khuyến nghị:
- Dự án mới + sử dụng hệ thống Grafana: Tempo (tích hợp tự nhiên, chi phí S3 tối ưu)
- Hệ thống đã chạy Jaeger và yêu cầu tìm kiếm tag: Jaeger
- Hệ thống chạy hoàn toàn trên AWS quy mô nhỏ: X-Ray (nhưng bị lock-in nhà cung cấp)
- Doanh nghiệp quy mô lớn: Tempo (khả năng mở rộng tốt nhất với chi phí thấp nhất)
3. Internal Architecture¶
Ý TƯỞNG
Tempo tách write path (distributor → ingester → S3) và read path (query-frontend → querier → S3/cache) hoàn toàn độc lập. Đây là pattern "ingester owns recent data, object storage owns history" giống Loki — cho phép scale ghi và đọc theo tín hiệu nghẽn khác nhau.
graph TD
subgraph Write["Write Path"]
DIST[Distributor\nvalidate · hash]
ING1[Ingester 1\nin-memory blocks]
ING2[Ingester 2]
ING3[Ingester 3]
WAL[WAL\n/var/tempo/wal]
end
subgraph Compact["Background"]
COMP[Compactor\nmerge · deduplicate\nretention]
end
subgraph Read["Read Path"]
QF[Query Frontend\ncache · fan-out]
QUER[Querier\nfetch + merge]
CACHE[Block Cache\nMemcached]
end
subgraph Storage["Object Storage"]
S3[AWS S3\n.parquet blocks]
INDEX[Tag Index\nglobal search]
end
SOURCE[OTel Collector\ngateway] -->|OTLP gRPC :4317| DIST
DIST -->|hash on TraceID mod 3| ING1
DIST -->|hash on TraceID mod 3| ING2
DIST -->|hash on TraceID mod 3| ING3
ING1 --- WAL
ING1 -->|flush every 30min| S3
ING2 -->|flush| S3
ING3 -->|flush| S3
ING1 -->|write tag index| INDEX
COMP -->|merge small blocks| S3
COMP -->|rebuild index| INDEX
GRAFANA[Grafana] -->|Get trace by ID or TraceQL| QF
QF -->|in-memory trace| ING1
QF -->|S3 lookup| QUER
QUER -->|block cache hit| CACHE
QUER -->|block cache miss| S3
QUER -->|merge| QF
QF -->|return trace| GRAFANA
style Write fill:#dbeafe,color:#1e293b
style Read fill:#dcfce7,color:#1e293b
style Storage fill:#f3e8ff,color:#1e293b
style Compact fill:#ffedd5,color:#1e293b
Key Endpoints¶
| Endpoint | Giao thức | Cổng | Mô tả |
|---|---|---|---|
/api/traces/{traceID} |
HTTP | 3200 | Lấy thông tin trace theo ID |
/api/search |
HTTP | 3200 | Tìm kiếm thẻ tag bằng TraceQL |
/api/search/tags |
HTTP | 3200 | Liệt kê tên các tags có thể tìm kiếm |
/api/search/tag/{tag}/values |
HTTP | 3200 | Liệt kê các giá trị của một tag cụ thể |
/api/v2/search |
HTTP | 3200 | Tìm kiếm TraceQL (v2) |
| OTLP gRPC receive | gRPC | 4317 | Nhận traces từ OTel Collector |
| Tempo internal | gRPC | 9095 | Truyền tin nội bộ giữa các thành phần |
| Memberlist | UDP | 7946 | Gossip ring |
/metrics |
HTTP | 3200 | Metrics Prometheus |
/ready |
HTTP | 3200 | Kiểm tra trạng thái sẵn sàng |
4. Data Flow — Write Path¶
sequenceDiagram
participant OTel as OTel Collector Gateway
participant DIST as Distributor
participant ING as Ingester (×3)
participant WAL as WAL (/var/tempo/wal)
participant S3 as AWS S3
OTel->>DIST: OTLP gRPC ExportRequest (lô spans)
DIST->>DIST: Xác thực spans (kích thước tối đa, số lượng thuộc tính)
DIST->>DIST: Băm TraceID → xác định shard ingester tương ứng
DIST->>ING: Chuyển tiếp spans (replication_factor=3: tới cả 3 ingesters)
ING->>WAL: Ghi nối đuôi spans vào WAL (đảm bảo độ bền dữ liệu)
ING->>ING: Đệm dữ liệu trên memory block (được đánh chỉ mục theo TraceID)
ING-->>DIST: Quorum ack (2/3)
DIST-->>OTel: Xác nhận hoàn tất
Note over ING: Mỗi 30 phút (ingester.max_block_duration)...
ING->>ING: Đóng block hiện tại, dừng nhận ghi mới vào block đó
ING->>S3: Upload block lên dưới dạng các file .parquet
ING->>S3: Ghi thông tin chỉ mục index toàn cục (TraceID → vị trí block)
ING->>WAL: Xóa bỏ dữ liệu WAL tương ứng của block đã upload
Ingester Block Format¶
Tempo lưu trữ traces theo các blocks. Mỗi block bao phủ một khung thời gian:
/var/tempo/
├── wal/
│ ├── 00000001 ← Các phân đoạn WAL
│ └── 00000002
└── blocks/
├── 0193abc1.../ ← Block (định danh ULID)
│ ├── meta.json ← Metadata của Block (khoảng thời gian, số lượng trace, dung lượng)
│ ├── data.parquet ← Dữ liệu Trace (định dạng cột Parquet)
│ └── bloom-filter ← Bloom filter để kiểm tra nhanh sự tồn tại của TraceID
└── 0193def2.../
Bloom filter: Trước khi tải một block Parquet từ S3 về, querier sẽ kiểm tra nhanh qua bloom filter (một cấu trúc dữ liệu xác suất) để xác định xem TraceID cần tìm có khả năng nằm trong block đó hay không. Cơ chế này giúp tránh tải dữ liệu không cần thiết từ S3.
Tip
Bloom filter không bao giờ false-negative (nếu nói "không có" thì chắc chắn không có), nhưng có false-positive (nói "có thể có" nhưng thực ra không). Tỷ lệ FP càng thấp → bloom filter càng lớn → tốn memory/storage hơn nhưng ít GET S3 lãng phí hơn. Production thường chọn 0.01 hoặc 0.005.
5. Data Flow — Read Path¶
sequenceDiagram
participant G as Grafana
participant QF as Query Frontend
participant Q as Querier
participant ING as Ingesters
participant S3 as AWS S3
participant MC as Memcached
G->>QF: GET /api/traces/4bf92f3577b34da6
QF->>Q: Chuyển tiếp yêu cầu
par Tìm các traces mới ghi nhận (trên ingesters)
Q->>ING: Tìm trong các blocks đang đệm trên memory theo TraceID
ING-->>Q: Trả về spans từ block hiện tại (nếu trace mới được ghi nhận gần đây)
and Tìm các traces cũ (trên S3)
Q->>MC: Kiểm tra cache Bloom filter?
MC-->>Q: Bloom filter xác nhận (có thể tồn tại trong block 0193abc1)
Q->>S3: Tải metadata của block về
Q->>S3: Lấy các spans cụ thể theo TraceID từ file Parquet
S3-->>Q: Trả về trace spans
end
Q->>Q: Gộp các spans từ ingesters + S3
Q->>Q: Sắp xếp theo mốc thời gian timestamp
Q-->>QF: Trả về Trace hoàn chỉnh
QF-->>G: Trả về Trace dưới định dạng OTLP JSON
6. Trace Storage Format¶
Parquet Column Layout¶
Tempo sử dụng định dạng Apache Parquet (lưu trữ dạng cột) cho dữ liệu traces:
Các cột của file data.parquet:
├── TraceID (byte_array) ← Được sắp xếp để phục vụ tìm kiếm nhị phân (binary search)
├── RootSpanName (string)
├── RootServiceName (string)
├── StartTimeUnixNano (int64)
├── DurationNano (int64) ← Dành cho các truy vấn dựa trên latency
├── Spans[]
│ ├── SpanID (byte_array)
│ ├── ParentSpanID (byte_array)
│ ├── Name (string)
│ ├── Kind (int) ← Server, Client, Internal, v.v.
│ ├── StartTimeUnixNano (int64)
│ ├── DurationNano (int64)
│ ├── StatusCode (int)
│ ├── StatusMessage (string)
│ └── Attributes (map<string, AnyValue>)
└── Resource Attributes (map<string, AnyValue>)
Tại sao nên dùng Parquet:
- Lưu trữ dạng cột (Columnar): Cho phép lọc nhanh theo
StatusCode == ERRORmà không cần đọc dữ liệu các cột khác - Khả năng nén: Định dạng Parquet kết hợp thuật toán nén Snappy giúp nén dữ liệu trace đạt tỷ lệ từ 5–10:1
- Tính năng S3 Select: Có thể thực hiện lọc các cột ngay tại phía server S3, giúp giảm thiểu băng thông truyền dữ liệu
Vparquet3 (Định dạng riêng của Tempo)¶
Tempo sử dụng một schema Parquet tùy biến riêng gọi là vparquet3:
# Bật cấu hình trong file tempo config
storage:
trace:
backend: s3
block:
version: vparquet3 # Bắt buộc phải bật để tính năng TraceQL hoạt động
bloom_filter_false_positive: 0.01 # Chấp nhận tỷ lệ dương tính giả 1% đối với bloom filters
bloom_filter_shard_size_bytes: 100kb
7. TraceQL Query Language¶
TraceQL là ngôn ngữ truy vấn của Tempo dùng để tìm kiếm traces theo thuộc tính của spans.
Lưu ý: Việc tìm kiếm bằng TraceQL yêu cầu dữ liệu phải ở định dạng
vparquet3VÀ phải cấu hình pipelinelocal-blockshoặc bộ chỉ mục tag index. Nếu chỉ truy vấn trực tiếp bằng TraceID thì không cần các cấu hình này.
TraceQL Syntax¶
# Cơ bản: tìm toàn bộ các traces chứa span có status lỗi
{ status = error }
# Tìm các traces từ một dịch vụ cụ thể có latency cao
{ resource.service.name = "payment-service" && duration > 2s }
# Tìm các traces có mã lỗi HTTP status code cụ thể
{ span.http.status_code = 500 }
# Tìm các traces có span lỗi VÀ thuộc dịch vụ payment
{ status = error && resource.service.name = "payment-service" }
# Tìm các traces có giá trị thuộc tính cụ thể
{ span.order.id = "ord-12345" }
# Tổng hợp: đếm số lượng traces theo từng service
{ resource.service.name =~ ".*" } | by(resource.service.name) | count() > 0
# Tìm kiếm theo latency (slow traces)
{ duration > 5s }
# Phối hợp: tìm lỗi trong dịch vụ payment xử lý chậm hơn 3s
{
resource.service.name = "payment-service"
&& status = error
&& duration > 3s
}
Enabling Tag-Based Search (Pipeline)¶
# tempo-config.yaml
pipeline:
# Bật tính năng tìm kiếm cấu trúc bằng TraceQL
search:
enabled: true
storage:
trace:
backend: s3
local_blocks:
path: /var/tempo/blocks
max_stale_cut: 15m
flush_to_storage: true
8. Metrics from Traces (SpanMetrics)¶
SpanMetrics là một processor của OTel Collector giúp tự động tạo ra các RED metrics từ dữ liệu traces. Đây là tính năng rất giá trị cho AIOps pipeline.
Why SpanMetrics?¶
Thay vì phải thiết lập mã nguồn trong từng dịch vụ để phát đi metrics latency/error, SpanMetrics tự động tạo ra các metrics này từ thông tin spans của trace:
Traces → SpanMetrics Processor → Prometheus metrics
Hệ thống tự động sinh ra các metrics:
- traces_spanmetrics_calls_total (counter, phân loại theo service/operation/status)
- traces_spanmetrics_duration_milliseconds (histogram, phân loại theo service/operation)
OTel Collector SpanMetrics Configuration¶
connectors:
spanmetrics:
histogram:
explicit:
buckets: [5ms, 10ms, 25ms, 50ms, 75ms, 100ms, 250ms, 500ms, 750ms, 1s, 2.5s, 5s, 10s]
dimensions:
- name: http.method
- name: http.status_code
- name: service.name
- name: db.system
- name: messaging.system
dimensions_cache_size: 10000
aggregation_temporality: AGGREGATION_TEMPORALITY_CUMULATIVE
metrics_flush_interval: 15s
namespace: "traces" # Tiền tố gán cho các metrics được sinh ra
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, tail_sampling, batch]
exporters: [otlp/tempo, spanmetrics] # Đồng thời gửi tới Tempo VÀ sinh ra metrics
metrics/spanmetrics:
receivers: [spanmetrics] # Nhận dữ liệu đầu vào từ traces pipeline
processors: [batch]
exporters: [prometheusremotewrite] # Đẩy metrics về Prometheus
Ví dụ metrics được sinh ra:
# Số lượng yêu cầu gọi theo service và operation
traces_spanmetrics_calls_total{service_name="order-service", span_name="POST /api/orders", status_code="STATUS_CODE_OK"} 1234
traces_spanmetrics_calls_total{service_name="order-service", span_name="POST /api/orders", status_code="STATUS_CODE_ERROR"} 42
# Biểu đồ phân phối latency (Histogram)
traces_spanmetrics_duration_milliseconds_bucket{service_name="order-service", span_name="POST /api/orders", le="100"} 1000
traces_spanmetrics_duration_milliseconds_bucket{service_name="order-service", span_name="POST /api/orders", le="500"} 1230
Giá trị đối với AIOps: SpanMetrics cung cấp đầy đủ các RED metrics cho mỗi cặp service-operation mà không cần thay đổi bất kỳ dòng code ứng dụng nào. Đây là con đường nhanh nhất để đạt độ phủ sóng khả năng quan sát (observability coverage) toàn diện.
Ý TƯỞNG
SpanMetrics biến trace thành "nguồn sinh metric". Bạn trả chi phí instrumentation một lần (auto-instrument HTTP/gRPC/DB) và nhận cả latency histogram + error counter + service graph. Correlation engine AIOps sau này có thể nhảy từ metric spike → exemplar → full trace trong Tempo.
Warning
Dimensions trong SpanMetrics chính là nhãn Prometheus. Thêm user_id, order_id, session_id vào dimensions = cardinality bomb phá Prometheus. Chỉ dùng low-cardinality labels: service.name, http.method, http.status_code, db.system.
9. Deployment Modes¶
Single Binary (Cho môi trường phát triển/thử nghiệm)¶
Scalable (Môi trường Production)¶
# Triển khai dưới dạng các microservices
targets:
distributor: 2 replicas
ingester: 3 replicas (chạy StatefulSet, cấu hình WAL bền vững)
querier: 2 replicas
query-frontend: 2 replicas
compactor: 1 replica (chạy duy nhất dạng singleton)
Helm Installation¶
helm repo add grafana https://grafana.github.io/helm-charts
helm install tempo grafana/tempo-distributed \
--namespace observability \
--values tempo-values.yaml
10. Production Configuration¶
Complete tempo-config.yaml¶
target: all # hoặc chỉ định thành phần cụ thể
server:
http_listen_port: 3200
grpc_listen_port: 9095
log_level: info
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
jaeger:
protocols:
thrift_http:
endpoint: 0.0.0.0:14268
grpc:
endpoint: 0.0.0.0:14250
ingester:
max_block_duration: 30m # Thực hiện đẩy block (flush) mỗi 30 phút
max_block_bytes: 1_073_741_824 # Cực đại kích thước block 1GB trước khi ép buộc flush
trace_idle_period: 20s # Thời gian chờ span cuối cùng trước khi đóng trace
flush_check_period: 30s
lifecycler:
ring:
replication_factor: 3 # Đảm bảo duy trì 3 bản sao cho mỗi trace
compactor:
compaction:
block_retention: 336h # Thời gian lưu giữ 14 ngày
compacted_block_retention: 1h # Thời gian giữ lại các compacted blocks trên disk
compaction_window: 4h # Cửa sổ thời gian thực hiện compaction
querier:
frontend_worker:
frontend_address: tempo-query-frontend.observability.svc.cluster.local:9095
query_frontend:
search:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09 # Tốc độ quét đích 1GB/s
storage:
trace:
backend: s3
wal:
path: /var/tempo/wal
s3:
bucket: tempo-traces-prod
region: us-east-1
# Xác thực bằng IRSA
block:
version: vparquet3
bloom_filter_false_positive: 0.01
bloom_filter_shard_size_bytes: 102400
# Member list phục vụ điều phối phân tán
memberlist:
abort_if_cluster_join_fails: false
join_members:
- tempo-gossip-ring.observability.svc.cluster.local:7946
# Limits
limits_config:
max_traces_per_user: 0 # 0 = không giới hạn
max_search_duration: 336h # Cửa sổ thời gian tìm kiếm tối đa 14 ngày
ingestion_rate_limit_bytes: 20000000 # Giới hạn nạp dữ liệu 20MB/s cho mỗi tenant
ingestion_burst_size_bytes: 50000000 # Khung burst 50MB
max_bytes_per_trace: 5000000 # Kích thước trace tối đa 5MB
max_search_bytes_per_trace: 5000000
# Trình sinh metrics (SpanMetrics)
metrics_generator:
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://prometheus.observability.svc.cluster.local:9090/api/v1/write
processors: [service-graphs, span-metrics]
processor:
service_graphs:
dimensions: [service.name, http.method]
max_items: 10000
span_metrics:
dimensions:
- http.method
- http.status_code
- service.name
histogram_buckets: [5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000]
Kubernetes StatefulSet for Ingesters¶
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: tempo-ingester
namespace: observability
spec:
replicas: 3
serviceName: tempo-ingester
podManagementPolicy: Parallel
# Phân bố chạy trên các Availability Zones khác nhau
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: tempo-ingester
containers:
- name: tempo
image: grafana/tempo:2.4.0
args:
- -config.file=/conf/tempo.yaml
- -target=ingester
resources:
requests:
cpu: "1"
memory: "4Gi"
limits:
cpu: "2"
memory: "8Gi"
volumeMounts:
- name: tempo-wal
mountPath: /var/tempo/wal
volumeClaimTemplates:
- metadata:
name: tempo-wal
spec:
accessModes: [ReadWriteOnce]
storageClassName: gp3
resources:
requests:
storage: 50Gi # Đủ dung lượng lưu trữ WAL cho 30 phút chứa traces
11. Grafana Integration¶
Datasource Configuration¶
# grafana-datasources.yaml
apiVersion: 1
datasources:
- name: Tempo
type: tempo
url: http://tempo-query-frontend.observability.svc.cluster.local:3200
uid: tempo
jsonData:
# Liên kết traces sang Loki logs bằng TraceID
tracesToLogsV2:
datasourceUid: loki
spanStartTimeShift: '-1h'
spanEndTimeShift: '1h'
tags: [{key: 'service.name', value: 'service'}]
filterByTraceID: true
filterBySpanID: false
customQuery: false
# Liên kết traces sang Prometheus metrics
tracesToMetrics:
datasourceUid: prometheus
spanStartTimeShift: '-30m'
spanEndTimeShift: '30m'
tags: [{key: 'service.name', value: 'service'}]
queries:
- name: "Request Rate"
query: "sum(rate(traces_spanmetrics_calls_total{$$__tags}[5m]))"
- name: "P99 Latency"
query: "histogram_quantile(0.99, sum(rate(traces_spanmetrics_duration_milliseconds_bucket{$$__tags}[5m])) by (le))"
# Khung hiển thị Service graph
serviceMap:
datasourceUid: prometheus
# Khai báo các tags hiển thị tìm kiếm trong giao diện Explore của Tempo
search:
hide: false
Grafana Explore — Trace Investigation Workflow¶
1. Tại màn hình Grafana Explore → chọn datasource Prometheus:
Chạy câu truy vấn: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
→ Phát hiện latency P99 tăng đột biến (spike) vào lúc 14:23
2. Click vào exemplar xuất hiện trên spike → giao diện trace của Tempo tự động mở ra
(exemplar này chứa thông tin TraceID của chính yêu cầu bị chậm đó)
3. Tại giao diện xem trace của Tempo:
→ Sơ đồ spans hiển thị chi tiết: phân đoạn payment-service.chargeCard mất đến 1.8s
→ Click vào nút "Logs for this trace"
4. Giao diện Loki tự động hiển thị kết quả truy vấn:
{namespace="production"} |= "4bf92f35..."
→ logs hiển thị thông tin: "DB connection pool exhausted, queuing for 1.7s"
5. Kết luận nguyên nhân gốc rễ: DB connection pool bị cấu hình quá nhỏ
Tip
Workflow exemplar → Tempo → Loki là vòng lặp RCA chuẩn của stack Grafana. Hãy kiểm tra cả ba datasource đã cấu hình tracesToLogsV2 / exemplars / trace_id trong log body trước khi train bất kỳ model AIOps nào — nếu correlation tay đã gãy, model cũng gãy.
12. Trace vs Log vs Metric cho RCA¶
Ý TƯỞNG
Metric nói cái gì đang sai và khi nào. Log nói chi tiết cục bộ tại một process. Trace nói chuỗi quan hệ nhân-quả xuyên service của một request. RCA tự động chỉ mạnh khi ba tín hiệu gắn bằng cùng trace_id / topology — chứ không phải khi bạn có "nhiều data" rời rạc.
Ma trận quyết định: tín hiệu nào thắng cho từng loại sự cố?¶
| Loại sự cố | Metric | Log | Trace | Tín hiệu chính cho RCA |
|---|---|---|---|---|
| Latency P99 tăng, error rate ổn | ✅ Phát hiện | ⚠️ Có thể không có error log | ✅ Thấy span chậm | Trace (critical path) |
| Error rate tăng cục bộ 1 service | ✅ | ✅ Stack/message | ✅ Span status ERROR | Log + Trace |
| Timeout cascade (A→B→C) | ⚠️ Nhiều alert đồng thời | ⚠️ Nhiều log "timeout" rời | ✅ Parent-child + duration | Trace |
| OOM / process crash | ✅ | ✅ Last logs | ❌ Trace đứt giữa chừng | Log + Metric |
| Deployment regression | ✅ | ⚠️ | ✅ So sánh span latency trước/sau | Metric + Trace + change |
| Data corruption / wrong value | ❌ | ✅ | ⚠️ Attribute business | Log |
| Resource saturation (CPU/mem) | ✅ | ⚠️ | ⚠️ Slow spans gián tiếp | Metric |
| Cross-service retry storm | ⚠️ Rate tăng | ⚠️ "retrying" | ✅ Nhiều client spans lặp | Trace |
Khi nào Trace quan trọng hơn Log/Metric?¶
1. Bạn cần critical path, không phải "service chậm" chung chung
Metric http_request_duration_seconds{service="api-gateway"} chỉ nói gateway chậm. Trace cho biết 1.8s nằm ở payment.chargeCard, không phải routing hay auth. RCA path-finding đi ngược từ root span lỗi dọc theo child có duration lớn nhất hoặc status ERROR.
2. Incident chỉ hiện khi request đi đúng tổ hợp dependency
Ví dụ: chỉ request có feature-flag X + gọi inventory + payment song song mới timeout. Metric trung bình bị hòa tan; log từng service không cho thấy fan-out. Trace giữ topology request-scoped.
3. Correlation alerts "nổ chùm" cần gỡ bằng request tree
Alert correlation engine nhận 12 alerts trong 3 phút. Không có trace, bạn chỉ có đồng thời thời gian. Có trace_id chung, bạn chứng minh chúng là cùng một request tree chứ không phải 12 root cause độc lập.
4. Bounded context async (queue, outbox)
Log "message processed" ở consumer không liên kết với producer nếu thiếu context propagation. Trace (hoặc ít nhất traceparent / link spans) là cầu nối causality qua Kafka.
Khi nào Trace không thay Log/Metric?¶
- Detection / SLI realtime: Metric rẻ, sampling-stable, alert được. Trace sampling làm SLO lệch nếu chỉ sample 1%.
- Forensics chi tiết exception: Log stacktrace + context fields sâu hơn span status message.
- Capacity planning: Metric time-series dài hạn; trace retention thường 7–14 ngày.
Quy tắc thực dụng AIOps:
DETECT → Metric (anomaly / threshold)
LOCALIZE → Trace (which span / which hop)
EXPLAIN → Log (why that hop failed)
PROVE → trace_id liên kết cả ba
Warning
Đừng "RCA bằng trace only". Trace thiếu resource pressure (CPU steal, disk full). Metric node/pod + log kernel/OOMKilled vẫn bắt buộc. Trace là bản đồ request, không phải bản đồ cluster.
13. Sampling Paradox — Head vs Tail Based¶
Nghịch lý cốt lõi¶
Bạn sample thấp → rẻ, nhưng đúng lúc incident xảy ra thì trace "đắt giá" nhất có thể bị drop.
Bạn sample cao → đắt, và 99% traces "happy path" hầu như không bao giờ được mở trong incident.
Đây là sampling paradox: giá trị thông tin của trace phân phối cực kỳ lệch — phần lớn value nằm ở tail (error, slow, rare path), trong khi head-based sampling (quyết định lúc bắt đầu request) không biết request đó sẽ lỗi hay chậm.
Head-based sampling¶
Quyết định khi request bắt đầu (SDK hoặc agent):
| Ưu | Nhược |
|---|---|
| Đơn giản, chi phí CPU thấp | Không biết outcome (error/slow) |
| Giảm load ngay từ source | Incident hiếm có thể bị drop hoàn toàn |
| Dễ giải thích % cố định | Bias: "tỷ lệ error trong Tempo" ≠ error rate thật |
# Ví dụ head sampling 5% — NGUY HIỂM cho AIOps nếu dùng một mình
processors:
probabilistic_sampler:
sampling_percentage: 5
Edge case chết người: Error rate 0.1%, head sample 1%. Xác suất một request lỗi được giữ ≈ 1%. Trong 10 lỗi, kỳ vọng chỉ ~0.1 trace lỗi được lưu — RCA "không có evidence".
Tail-based sampling¶
Quyết định sau khi trace hoàn tất (thường ở OTel Collector gateway), dựa trên toàn bộ spans:
processors:
tail_sampling:
decision_wait: 10s
num_traces: 100000
expected_new_traces_per_sec: 5000
policies:
- name: errors-keep-all
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow-keep
type: latency
latency: {threshold_ms: 2000}
- name: baseline-1pct
type: probabilistic
probabilistic: {sampling_percentage: 1}
| Ưu | Nhược |
|---|---|
| Giữ 100% error / slow | Tốn memory đệm traces đang mở |
| Baseline 1% cho so sánh | decision_wait quá ngắn → thiếu late spans → quyết định sai |
| Phù hợp AIOps RCA | Gateway trở thành SPOF/scale point |
Edge case: mất đúng trace của incident¶
| Kịch bản | Cơ chế mất | Hậu quả | Phòng ngừa |
|---|---|---|---|
| Head sample drop request lỗi | Quyết định sớm | Không có trace_id trong Tempo dù log có | Ưu tiên tail sampling errors |
decision_wait < lag async span |
Trace incomplete khi quyết định | Drop hoặc partial; RCA đứt hop cuối | Tăng wait; dùng span links cho async |
| Collector OOM / queue full | Drop batch | Mất cụm traces lúc peak = lúc hay incident | memory_limiter + persistent queue + backpressure metric |
max_bytes_per_trace cắt trace lớn |
Trace bị reject | Fan-out lớn (graph QL N+1) biến mất | Giới hạn span attributes; fix fan-out |
| Multi-collector không sticky | Spans cùng TraceID tới collector khác nhau | Tail sampler không thấy full tree | Load-balance theo TraceID hoặc sample ở gateway tập trung |
| Late spans sau flush | Ingester trace_idle_period đóng sớm |
Partial trace | Căn chỉnh idle period với async SLA |
Warning
Paradox vận hành: lúc traffic spike (incident), collector queue đầy → sampling/export drop đúng lúc bạn cần evidence nhất. Hãy alert trên otelcol_processor_dropped_spans / exporter queue size cùng severity với business SLO — đây là "observability of observability".
Khuyến nghị production AIOps¶
100% ERROR spans → keep
100% latency > SLO×2 → keep
100% traces có attribute incident_id / canary → keep (policy string_attribute)
1–5% probabilistic → baseline + SpanMetrics vẫn cần stream trước sample nếu metric từ trace
Never: head 0.1% only → "tiết kiệm" biến Tempo thành bảo tàng rỗng khi fire
Tip
Nếu SpanMetrics chạy trước tail_sampling trong pipeline traces, metric RED vẫn full-fidelity trong khi Tempo chỉ lưu subset. Đây là pattern chi phí tốt: metric cho detect, sampled traces cho deep dive.
14. Cost vs Coverage Decision Tree¶
┌─────────────────────────┐
│ Bắt đầu: bao nhiêu │
│ spans/s raw production? │
└───────────┬─────────────┘
│
┌─────────────────┴─────────────────┐
│ < 5K spans/s │ > 50K spans/s
│ → có thể keep 100% ngắn hạn │ → bắt buộc sampling
└─────────────────┬─────────────────┘
│
┌───────────▼───────────┐
│ Mục tiêu chính? │
└───────────┬───────────┘
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ RCA/AIOps │ │ Compliance │ │ Dev debug │
│ deep dive │ │ audit 100% │ │ staging only │
└─────┬──────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
Tail: 100% err/slow Separate tenant Head 10–50%
+ 1–5% baseline + longer retention short retention
retention 7–14d legal hold bucket 1–3d
Bảng quyết định chi phí (order-of-magnitude)¶
| Policy | Coverage RCA | S3 (relative) | Rủi ro |
|---|---|---|---|
| Head 100% | Cao | 100% | Đắt; OOM ingester |
| Head 1% | Thấp với rare errors | ~1% | Mất incident traces |
| Tail 100% err + 1% ok | Cao cho lỗi | ~2–10% | Phụ thuộc policy đúng |
| Tail 100% err/slow + 5% ok | Rất cao | ~5–15% | Cân bằng tốt nhất đa số team |
| Chỉ errors, 0% ok | Tốt cho lỗi | Rất thấp | Không baseline; không so sánh "normal" |
Câu hỏi bắt buộc trước khi chốt sampling¶
- Error budget / SLO có phụ thuộc metric từ traces không? → SpanMetrics trước sample.
- Legal có yêu cầu giữ 100% transaction không? → Tenant riêng, không trộn debug sampling.
- MTTR hiện tại có bị chặn vì "không có trace" không? → Ưu tiên coverage errors hơn retention dài.
- P99 business hay mean? → Policy latency threshold theo P99 target, không theo mean.
Ý TƯỞNG
Cost tối ưu không phải "sample thấp nhất có thể", mà là maximize P(giữ được trace khi page fire) dưới budget $X. Công thức tư duy: Value ≈ P(incident covered) × MTTR_reduction − storage_cost − eng_time.
Decision checklist nhanh¶
[ ] Tail sampling ở gateway (không head-only trên SDK prod)
[ ] Policy: ERROR + latency > 2×SLO + probabilistic baseline
[ ] SpanMetrics / service graph trước sampling
[ ] Retention: hot 7–14d; archive cold nếu compliance
[ ] Alert drop rate collector & Tempo ingestion
[ ] max_bytes_per_trace + attribute allowlist
[ ] Ước S3: spans/s × size × retention × $0.023
15. Edge Cases: Cardinality, PII, Multi-tenant¶
High-cardinality attributes trên spans¶
Span attributes không tự động trở thành Prometheus series — trừ khi bạn đưa chúng vào SpanMetrics dimensions hoặc index search. Nhưng high-cardinality vẫn gây hại:
| Vấn đề | Cơ chế | Hậu quả |
|---|---|---|
| Attribute nổ (user_id, URL raw) | Mỗi span phình to | Ingester memory, S3, max_bytes_per_trace |
| TraceQL search trên high-card field | Index / scan tốn kém | Query chậm, cost read |
| SpanMetrics dimension high-card | Label Prometheus | TSDB OOM, query vỡ |
| Unbounded span events / logs-in-span | Gắn exception full body | Trace multi-MB |
Allowlist khuyến nghị:
# OTel Collector — chặn attribute nguy hiểm
processors:
attributes/deny_high_card:
actions:
- key: http.url
action: delete # dùng http.route đã templatize
- key: user.email
action: delete
- key: db.statement
action: hash # hoặc delete nếu có PII trong SQL
- key: enduser.id
action: delete # để ở log body đã redact, không nhét metric
Warning
http.url với query string (?user=...&token=...) vừa cardinality vừa PII/secret. Luôn prefer http.route (/users/{id}).
PII trong spans¶
Traces thường bị bỏ quên trong privacy review vì "không phải log". Thực tế span name, attribute, event chứa:
- Email, phone, CMND trong attribute custom
- Authorization header nếu middleware instrument sai
- SQL full statement với giá trị literal
- Kafka message payload gắn vào span event
Biện pháp tầng tầng:
- SDK / instrumentation: không capture raw body; scrub headers.
- Collector: redaction processor (regex token, email).
- Tempo: multi-tenant isolation + encryption S3 KMS; hạn chế ai được Explore.
- Process: DLP scan định kỳ trên sample blocks; retention ngắn hơn log nếu policy yêu cầu.
processors:
transform/redact:
trace_statements:
- context: span
statements:
- replace_pattern(attributes["http.user_agent"], ".*", "[REDACTED_UA]")
# ví dụ minh họa — prefer allowlist thay vì cố redact hết
Tip
Prefer allowlist attributes hơn denylist. Denylist luôn thua một sprint feature mới (customer.tax_id xuất hiện tuần sau).
Multi-tenant trace pollution¶
| Hiện tượng | Nguyên nhân | Hậu quả AIOps |
|---|---|---|
| Trace đứt khi gọi cross-tenant | Mỗi service gắn X-Scope-OrgID khác |
Không ghép full path |
| Tenant A query thấy meta tenant B | Sai header / Grafana DS | Data leak |
| "Production" và "canary" chung tenant không tag | Không có deployment.environment |
RCA nhầm canary với prod traffic |
| Load test pollute baseline | Cùng tenant, không attribute load_test=true |
Sampling/metrics lệch; model học nhiễu |
Khuyến nghị:
- Một tenant Tempo cho toàn bộ prod services cùng trust boundary (để cross-service trace liền), tách tenant cho staging / external customers.
- Resource attributes bắt buộc:
service.name,deployment.environment,k8s.namespace.name. - Load test / synthetic: attribute + tail policy riêng (drop hoặc sample thấp) để không pollute baseline 1%.
Ý TƯỞNG
"Multi-tenant" trong Tempo là ranh giới an ninh và quota, không phải ranh giới topology request. Request business đi qua 15 microservices phải cùng tenant tracing; tách team billing theo Grafana folder/RBAC, đừng cắt trace giữa chừng.
16. Trace-based SLI/SLO Patterns¶
Pattern 1: Availability SLI từ span status¶
# Tỷ lệ success theo service-operation (từ SpanMetrics)
sum(rate(traces_spanmetrics_calls_total{
service_name="checkout",
span_name="POST /api/checkout",
status_code="STATUS_CODE_OK"
}[5m]))
/
sum(rate(traces_spanmetrics_calls_total{
service_name="checkout",
span_name="POST /api/checkout"
}[5m]))
SLO ví dụ: 99.9% spans root checkout không ERROR trong 30 ngày.
Warning
Nếu Tempo/Collector sample trước khi sinh SpanMetrics, SLI sẽ lệch (error được keep 100% trong khi success sample 1% → error rate "ảo" tăng). SpanMetrics phải đứng trước tail_sampling, hoặc dùng metric app/RED độc lập cho SLO cứng.
Pattern 2: Latency SLI từ trace duration¶
histogram_quantile(0.99,
sum by (le) (
rate(traces_spanmetrics_duration_milliseconds_bucket{
service_name="checkout",
span_name="POST /api/checkout"
}[5m])
)
)
SLO: P99 root span < 800ms. Trace-based latency phản ánh end-to-end tốt hơn metric từng hop nếu root span bao trùm toàn request.
Pattern 3: "Correctness path" SLI (multi-span)¶
Một số SLO không phải HTTP 200 mà là đúng chuỗi hop:
SLI "payment path complete" =
tồn tại child span payment.authorize SUCCESS
AND child span inventory.reserve SUCCESS
trong cùng trace root checkout
TraceQL / offline job quét traces sampled để ước lượng; không thay SLO metric realtime nhưng dùng cho weekly quality review và training RCA labels.
Pattern 4: SLO burn + exemplar → Tempo¶
1. Multi-window burn rate alert trên SLI SpanMetrics
2. Annotation / exemplar đính TraceID chậm hoặc lỗi
3. On-call click → Tempo → critical path
4. AIOps correlation gắn alert_group_id ↔ related_trace_ids
Anti-patterns SLI từ trace¶
| Anti-pattern | Vì sao sai |
|---|---|
| SLO chỉ trên sampled Tempo store | Mẫu lệch → error budget giả |
| Đếm span ERROR mọi internal span | Internal retry ERROR làm burn ảo |
Dùng span name high-card (/user/123) |
Không stable theo thời gian |
| Gộp mọi service một SLI | Che mất dependency yếu |
Tip
Root span / entry span (gateway hoặc service edge) là ứng viên SLI tốt nhất. Internal spans dùng cho RCA, không phải lúc nào cũng cho error budget.
17. Tempo trong AIOps Pipeline¶
Vai trò của trace_id trong correlation engine¶
flowchart LR
A[Alert / Anomaly] --> B[Signal Collector]
B --> C{Có trace_id?}
C -->|Có| D[Fetch Tempo API]
C -->|Không| E[Heuristic time+service]
D --> F[Span tree parse]
F --> G[Critical path / ERROR spans]
G --> H[RCA ranker]
E --> H
H --> I[Evidence bundle + remediation hint]
B --> J[Loki by trace_id]
J --> H
Schema evidence tối thiểu:
{
"alert_id": "al-9f3c",
"related_trace_ids": ["4bf92f3577b34da6a3ce929d0e0e4736"],
"critical_path": [
{"service": "api-gateway", "span": "HTTP POST", "duration_ms": 2100},
{"service": "order-service", "span": "createOrder", "duration_ms": 2050},
{"service": "payment-service", "span": "chargeCard", "duration_ms": 1980, "status": "ERROR"}
],
"error_span": {
"service": "payment-service",
"message": "upstream timeout",
"attributes": {"peer.service": "psp-gateway", "http.status_code": 504}
}
}
RCA path finding trên span tree¶
Thuật toán thực dụng (không cần GNN lúc đầu):
- Lấy root span (hoặc entry service bị alert).
- DFS/BFS children; ưu tiên nhánh có
status=ERROR, sau đó nhánhdurationlớn nhất. - Dừng khi gặp leaf ERROR hoặc external dependency timeout.
- Gắn topology service graph (SpanMetrics service-graphs) để loại cạnh không tồn tại trong dependency map.
- Score:
error_flag * w1 + normalized_duration * w2 + change_recent * w3.
# Pseudo-code path finding
def critical_path(spans):
by_parent = index_children(spans)
root = find_root(spans)
path = []
node = root
while node:
path.append(node)
children = by_parent.get(node.span_id, [])
if not children:
break
# Ưu tiên ERROR, rồi duration
children.sort(key=lambda s: (s.status != ERROR, -s.duration))
node = children[0]
return path
API tích hợp Tempo¶
| Mục đích | API |
|---|---|
| Lấy full trace | GET /api/traces/{traceID} |
| Search theo service/lỗi | TraceQL /api/search |
| Batch cho training | Export blocks S3 Parquet offline |
Ý TƯỞNG
Correlation engine không thay Tempo làm storage. Nó cache metadata (trace_id, critical path summary, top error attributes) trong event bus (Kafka) để LLM agent / RCA service không kéo full parquet mỗi lần. Full trace chỉ fetch khi human hoặc agent cần drill-down.
Liên kết chương khác¶
- 10 — Alert Correlation — nhóm alert bằng
related_trace_ids - 11 — Root Cause Analysis — span analysis, evidence scoring
- 12 — LLM Agent — agent đọc critical path + log cùng
trace_id
18. Case Study: Timeout Cascade¶
Bối cảnh¶
Hệ thống checkout: api-gateway → order-service → payment-service → psp-gateway (HTTP). Song song order-service → inventory-service.
Triệu chứng on-call nhận được:
- Alert 1:
api-gatewayP99 > 3s - Alert 2:
order-serviceerror rate tăng - Alert 3:
payment-serviceupstream 504 - Alert 4: HPA scale gateway (CPU tăng do connection giữ lâu)
Chỉ nhìn Metric + Log — bức tranh mơ hồ¶
Metric: cả gateway, order, payment đều "xấu" gần như cùng phút
→ Biased kết luận: "payment chết" hoặc "network blip" — thiếu thứ tự nhân quả
Log order-service: "context deadline exceeded"
Log payment-service: "PSP timeout after 2000ms"
Log gateway: "upstream timeout"
→ Ba log đúng nhưng không chứng minh cùng một request; có thể 3 wave độc lập
Chỉ có Trace mới thấy cascade¶
TraceID 4bf92f35... total 3200ms
├─ api-gateway HTTP POST /checkout 3200ms
│ └─ order-service createOrder 3150ms
│ ├─ inventory-service reserve 40ms OK
│ └─ payment-service chargeCard 3000ms
│ └─ psp-gateway POST /charge 2000ms OK (chậm)
│ … payment client timeout = 2000ms
│ → payment returns 504 at 2005ms
│ order retries payment ×1 1000ms ERROR
│ order marks order FAILED
│ gateway hits 3s timeout nearly simultaneously
Insight chỉ trace thấy:
- PSP không hẳn down — span PSP success nhưng 2000ms chạm đúng client timeout.
- Retry amplifies — một user request → 2 call PSP; metric payment rate tăng "lạ".
- Inventory vô tội — nhanh 40ms; metric inventory có thể vẫn xanh.
- Timeout hierarchy sai — gateway 3s ≈ order 3s ≈ payment 2s + retry → không còn budget; cần deadline propagation.
Timeline RCA với Tempo¶
T+0:00 Burn rate alert checkout SLI
T+0:01 Exemplar → TraceID → Tempo
T+0:02 Critical path = payment client timeout / PSP latency
T+0:03 Loki |= TraceID → confirm "timeout after 2000ms"
T+0:05 So sánh TraceQL: {resource.service.name="psp-gateway" && duration>1.5s}
T+0:10 Decision: tăng timeout có chủ đích HOẶC tối ưu PSP; bật budget deadline
T+0:15 Fix config: payment timeout 2s → 2.5s tạm; ticket tối ưu PSP p99
Bài học thiết kế¶
| Thiết kế | Trước | Sau |
|---|---|---|
| Timeout | Hardcode mỗi service | Deadline propagation từ gateway |
| Retry | Blind retry ×1 | Retry chỉ trên idempotent + budget còn |
| Sampling | Head 5% | Tail 100% ERROR + latency>1s |
| Alert | 4 alerts rời | Correlate by trace_id → 1 incident |
Ý TƯỞNG
Timeout cascade là lớp sự cố metric báo nhiều nơi, log nói cùng chữ "timeout", chỉ trace vẽ được mũi tên nhân quả. Nếu AIOps của bạn không ingest trace_id vào correlation, model sẽ học "mọi timeout đồng thời = nhiều root causes".
Tip
Thêm synthetic trace (blackbox checkout) mỗi 30s với attribute synthetic=true. Khi synthetic fail, tail policy keep 100% — bạn có "canary request" luôn đủ evidence, không phụ thuộc user traffic.
19. Common Mistakes¶
Bảng tóm tắt¶
| Sai lầm phổ biến | Triệu chứng | Khắc phục |
|---|---|---|
| Không cấu hình bloom filters | Mọi truy vấn đều phải tải toàn bộ các blocks từ S3 về để quét | Thiết lập tham số bloom_filter_false_positive = 0.01 |
| Chọn sai phiên bản vparquet | Tính năng tìm kiếm bằng TraceQL không hoạt động | Sử dụng cấu hình version: vparquet3 trong block config |
Cấu hình ingester replication_factor = 1 |
Mất mát dữ liệu khi một pod ingester bị lỗi | Luôn thiết lập hệ số replication_factor = 3 |
| Không cấu hình WAL cho ingesters | Mất mát dữ liệu khi ingester bị crash đột ngột | Sử dụng ổ đĩa PVC để lưu trữ WAL |
| Chạy nhiều hơn một replica cho Compactor | Gây ra lỗi hỏng dữ liệu block (block corruption) | Luôn đảm bảo compactor chạy ở dạng singleton (1 replica) |
| Thiếu cấu hình lan truyền bối cảnh trace (trace context propagation) | Các traces bị đứt đoạn (chỉ hiển thị các single-span rời rạc) | Bắt buộc áp đặt chuẩn W3C TraceContext trên toàn bộ các dịch vụ |
| Thiết lập tỷ lệ lấy mẫu tail sampling quá thấp cho baseline / sai policy | Thiếu traces bình thường hoặc mất error traces | 100% ERROR/slow + 1–5% baseline; SpanMetrics trước sample |
| Không tận dụng tính năng SpanMetrics | Phải thiết lập mã nguồn thủ công trong mọi dịch vụ để lấy RED metrics | Bật cấu hình connector SpanMetrics trong OTel Collector |
| Cho phép kích thước trace quá lớn (>5MB) | Gây áp lực tải lớn lên memory của ingesters | Thiết lập giới hạn max_bytes_per_trace = 5MB |
| Không sử dụng bộ đệm (cache) cho các truy vấn | Phát sinh lượng lớn yêu cầu tải lặp đi lặp lại từ S3 | Bổ sung Memcached làm bộ đệm block cache |
| Head sampling only trên production | "Không có trace" đúng lúc incident | Chuyển tail-based tại gateway |
| PII / secret trong span attributes | Rủi ro compliance; bloated spans | Allowlist attributes + redact collector |
| High-card dimensions trong SpanMetrics | Prometheus cardinality nổ | Chỉ label low-card |
| Multi-tenant cắt ngang request path | Trace đứt giữa chừng | Một tenant prod cho mesh services |
| Bỏ qua drop metrics của collector | Tưởng có coverage 100% | Alert dropped_spans severity high |
Phân tích sâu — vì sao sai và vì sao cách "fix" phổ biến vẫn hỏng¶
1. replication_factor = 1 "để tiết kiệm"¶
Vì sao sai: Ingester crash hoặc zone outage = mất mọi traces chưa flush S3 (có thể 30 phút dữ liệu). Đúng cửa sổ incident đang debug dở.
Fix sai lệch: "Chúng tôi flush mỗi 1 phút nên RF=1 được". Vẫn mất WAL window + inflight; RCA cần đúng phút fire, không phải "đa số thời gian".
2. Compactor nhiều replica "cho HA"¶
Vì sao sai: Compaction không phải stateless query — hai compactors đụng cùng blocks → corruption / race.
Đúng HA: 1 compactor + restart nhanh + alert nếu compaction lag; không scale ngang mù quáng.
3. Head 1% "vì chi phí S3"¶
Vì sao sai: Phân phối value của traces lệch nặng. Tiết kiệm 99% storage có thể xóa 99% traces có ích.
Đúng: Cắt cost bằng policy theo outcome, không bằng tỷ lệ mù trên mọi request.
4. Trace context "đã bật OTel SDK" nhưng vẫn đứt¶
Vì sao sai: SDK không tự qua được reverse proxy strip header, HTTP client không inject, Kafka thiếu context carrier, hoặc thread hop mất context.
Chẩn đoán: Tìm tỷ lệ single-span traces; nếu cao → propagation broken, không phải Tempo hỏng.
5. Dùng Tempo như ELK — search mọi field¶
Vì sao sai: Kiến trúc store-on-S3 không tối ưu inverted index mọi attribute. Ép index mọi thứ = trả lại chi phí kiểu Jaeger/ES.
Đúng: Đường vào chính = TraceID từ log/metric; TraceQL cho tập attribute đã thiết kế (service, status, http.route).
6. SpanMetrics sau sampling + dùng làm SLO¶
Vì sao sai: Policy keep 100% error + 1% success → tỷ lệ ERROR trong metric bị phóng đại hàng chục lần → page sai, error budget cháy giả.
Đúng: Metrics pipeline full; storage Tempo sampled; SLO không đọc từ store đã sample lệch.
7. decision_wait quá ngắn cho async¶
Vì sao sai: Span consumer Kafka về sau 15s; sampler đã drop/export incomplete → path finding thiếu hop cuối (đúng hop hay lỗi).
Đúng: decision_wait ≥ p99 latency async + margin; hoặc unlink async khỏi quyết định tail của parent.
Warning
Sai lầm đắt nhất trong AIOps tracing không phải "quên bật Tempo", mà là tưởng đã có traces trong khi sampling/propagation/drop đã im lặng ném evidence. Hãy đo coverage: % error logs có trace_id resolve được trong Tempo trong 15 phút gần nhất.
20. Monitoring Tempo¶
# Sức khỏe luồng nạp dữ liệu (Ingestion health)
rate(tempo_distributor_spans_received_total[5m]) # Tần suất nhận spans/giây
rate(tempo_ingester_traces_created_total[5m]) # Tần suất tạo trace mới/giây
rate(tempo_ingester_blocks_flushed_total[5m]) # Tần suất đẩy block lên S3/giây
# Memory của Ingester (Ingester memory)
tempo_ingester_traces_in_memory # Số lượng traces đang mở trên memory
tempo_ingester_bytes_received_total # Tổng dung lượng byte nhận được
# Hiệu năng truy vấn (Query performance)
tempo_query_frontend_queries_total # Tần suất thực hiện truy vấn
histogram_quantile(0.99,
rate(tempo_request_duration_seconds_bucket{route="/api/traces/{traceID}"}[5m])
) # Thời gian lấy trace P99
# Các hoạt động tương tác với S3 (S3 operations)
rate(tempodb_backend_requests_total{operation="GET"}[5m]) # Tần suất gọi S3 GETs
rate(tempodb_backend_failures_total[5m]) # Tần suất lỗi khi gọi S3
# Hiệu quả của Bloom filter (Bloom filter performance)
rate(tempodb_bloom_filter_checks_total[5m])
rate(tempodb_bloom_filter_positive_total[5m]) # Số lượng tải S3 được giảm thiểu nhờ Bloom filter
Critical Alerts¶
- alert: TempoIngestionHighLatency
expr: |
histogram_quantile(0.99,
rate(tempo_distributor_push_duration_seconds_bucket[5m])
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "Tempo ingestion P99 > 2s"
- alert: TempoIngesterNotFlushing
expr: |
rate(tempo_ingester_blocks_flushed_total[30m]) == 0
for: 30m
labels:
severity: critical
- alert: TempoS3Errors
expr: |
rate(tempodb_backend_failures_total[5m]) > 1
for: 5m
labels:
severity: critical
21. Scaling¶
Write Path Scaling¶
| Thành phần | Tín hiệu nghẽn tải | Giải pháp khắc phục |
|---|---|---|
| Distributor | CPU tăng cao, hàng đợi yêu cầu dồn ứ | Bổ sung thêm các replicas cho distributor |
| Ingester | Mức sử dụng memory tăng cao (chỉ số tempo_ingester_traces_in_memory) |
Bổ sung thêm các replicas cho ingester (hash ring sẽ tự động tái phân bổ tải) |
| S3 | Chỉ số tempodb_backend_failures_total tăng dần |
Kiểm tra giới hạn băng thông S3, sử dụng VPC endpoint |
Read Path Scaling¶
| Thành phần | Tín hiệu nghẽn tải | Giải pháp khắc phục |
|---|---|---|
| Querier | Thời gian truy vấn P99 > 10s | Bổ sung thêm các replicas cho querier |
| Block cache | Tỷ lệ hụt cache (Cache miss rate) > 50% | Tăng dung lượng memory cấp phát cho Memcached |
| Băng thông S3 | Lỗi các yêu cầu GET requests tăng | Thiết lập S3 VPC Endpoint, yêu cầu AWS nâng giới hạn API rate limit |
S3 VPC Endpoint (Khuyến nghị bắt buộc)¶
Nếu không sử dụng VPC Endpoint, toàn bộ lưu lượng dữ liệu truyền giữa Tempo và S3 sẽ phải đi qua internet gateway, làm phát sinh chi phí truyền tải dữ liệu (data transfer costs) và bị giới hạn băng thông của internet gateway.
# Terraform
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-east-1.s3"
route_table_ids = [aws_route_table.private.id]
}
22. Security¶
IRSA for S3 Access¶
# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: tempo
namespace: observability
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/tempo-s3-role
# IAM chính sách (IAM Policy)
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::tempo-traces-prod/*",
"arn:aws:s3:::tempo-traces-prod"
]
}
S3 Bucket Encryption¶
resource "aws_s3_bucket_server_side_encryption_configuration" "tempo" {
bucket = aws_s3_bucket.tempo.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.tempo.arn
}
}
}
mTLS for Internal Communication¶
# Cấu hình TLS cho kết nối gRPC giữa các thành phần nội bộ
server:
grpc_tls_config:
cert_file: /certs/tempo.crt
key_file: /certs/tempo.key
client_ca_file: /certs/ca.crt
client_auth_type: RequireAndVerifyClientCert
23. Cost¶
Storage Cost¶
Lấy mẫu (Sampling): Chọn lấy mẫu 10% của lượng 100K req/giây = 10K traces/giây
Trung bình số lượng spans trên mỗi trace: 15
Kích thước trung bình một span: 2KB
Kích thước sau khi nén (Parquet + Snappy): ~400 bytes/span
Tốc độ tăng dung lượng lưu trữ:
10,000 traces/giây × 15 spans × 400 bytes = 60MB/giây = 5.18TB/ngày
Với thời gian lưu giữ dữ liệu 14 ngày (14-day retention):
5.18TB/ngày × 14 ngày = 72.5TB
Chi phí lưu trữ S3: 72.5TB × $0.023/GB = $1,667/tháng
Nếu áp dụng chính sách lấy mẫu tail sampling tối ưu hơn (1% lưu lượng bình thường, 100% đối với lỗi):
→ Giảm dung lượng từ 6-10 lần → Chi phí S3 chỉ còn khoảng $170-280/tháng
Compute Cost¶
| Thành phần | Số lượng Replica | Loại Instance sử dụng | Chi phí hàng tháng |
|---|---|---|---|
| Distributor | 2 | c6i.large | $120 |
| Ingester | 3 | m6i.2xlarge (32GB RAM) | $1,080 |
| Querier | 2 | m6i.large | $240 |
| Query Frontend | 2 | t3.medium | $60 |
| Compactor | 1 | m6i.large | $120 |
| Tổng chi phí | ~$1,620/tháng |
Ghi nhận quan trọng: Chi phí lưu trữ S3 luôn chiếm tỷ trọng lớn nhất. Yếu tố cốt lõi để điều chỉnh chi phí là tỷ lệ lấy mẫu (sampling ratio). Việc áp dụng tail-based sampling thông minh (1% lưu lượng bình thường, 100% lỗi) giúp cắt giảm chi phí S3 từ $1,667 xuống chỉ còn khoảng ~$200/tháng.
24. Production Review¶
Principal Engineer Assessment¶
Các vấn đề phát hiện và giải pháp khắc phục:
-
Tinh chỉnh tỷ lệ dương tính giả của Bloom filter: Tại mức cấu hình
0.01(dương tính giả 1%), cứ mỗi 100 lần kiểm tra bloom filter sẽ xuất hiện 1 lần báo sai (dẫn đến 1 yêu cầu tải block từ S3 về quét không cần thiết). Ở quy mô lớn (hơn 1 triệu blocks), việc này gây lãng phí IO đọc dữ liệu (read amplification). Hãy cân nhắc điều chỉnh tỷ lệ này xuống mức0.005đối với các môi trường nhạy cảm về chi phí vận hành. -
Đặt WAL của Ingester trên ổ đĩa SSD: Tiến trình ghi WAL của ingester là đường ghi dữ liệu liên tục (hot write path). Việc sử dụng ổ đĩa EBS
gp3(với cấu hình tối thiểu 3000 IOPS) là phù hợp. Nếu tốc độ nạp vượt quá 100MB/s trên mỗi ingester, hãy cân nhắc sử dụng dòng ổ đĩaio2(Provisioned IOPS). Hãy giám sát chỉ sốioutiltrên các nodes chạy ingester. -
Cơ chế tìm kiếm TraceQL yêu cầu pipeline index chuyên dụng: Việc tìm kiếm theo thẻ tag bằng TraceQL rất mạnh mẽ nhưng đòi hỏi phải bật cấu hình pipeline xử lý local-blocks hoặc bộ lưu trữ tag index ở backend. Nhiều triển khai bỏ qua phần này và chỉ hỗ trợ tìm kiếm trực tiếp theo TraceID. Đối với AIOps (nơi bạn thường chuyển tới trace bằng TraceID từ exemplar hoặc log), điều này là hoàn toàn chấp nhận được — tuy nhiên cần ghi chú rõ giới hạn này trong tài liệu vận hành.
-
Tương quan traces giữa các tenant khác nhau: Trong cấu hình đa thuê (multi-tenant) của Tempo, một trace đi xuyên qua hai tenants khác nhau sẽ không thể xem chung trên một giao diện thống nhất được. Đối với AIOps, hãy sử dụng một tenant
productionchung duy nhất cho toàn bộ các dịch vụ. -
Thiếu SLO cho chính pipeline tracing: Team monitor app SLOs nhưng không monitor
% error logs có trace resolve được trong Tempo,rate dropped spans,ingestion P99. Khi observability mù, mọi model RCA phía sau đều degradation im lặng. -
Sampling policy không versioning: Policy tail sampling thay đổi không ghi changelog → không giải thích được vì sao tháng trước còn trace, tháng này mất. Coi sampling config là production config (PR review, canary, metric impact).
-
PII review bỏ qua traces: Security review log/metric nhưng quên span attributes — rủi ro compliance và phình storage.
-
AIOps fetch full trace đồng bộ trên mọi alert: Làm Tempo read path quá tải khi storm. Hãy fetch có điều kiện (severity error/latency), cache summary, bulk offline cho training.
Improvement Roadmap¶
Phase 0 — Foundation (tuần 1–2)¶
| Hạng mục | Done khi |
|---|---|
| W3C TraceContext end-to-end (HTTP + gRPC + messaging) | <5% single-span traces trên entry flows |
| Tempo distributed + S3 + RF=3 + WAL PVC | Chaos kill 1 ingester không mất quorum data |
| Grafana traces↔logs↔metrics | Exemplar click mở đúng Tempo; Logs for trace có kết quả |
trace_id bắt buộc trong structured logs |
>95% error logs có field |
Phase 1 — Cost-aware coverage (tuần 3–4)¶
| Hạng mục | Done khi |
|---|---|
| Tail sampling: 100% ERROR + slow + 1–5% baseline | S3 cost trong budget; spot-check incident có trace |
| SpanMetrics trước sample; không high-card dims | RED metrics full; Prometheus series ổn định |
| Alert drop spans + Tempo not flushing | Page trước khi "mất evidence" kéo dài |
| Attribute allowlist + redact | Không còn email/token trong sample audit |
Phase 2 — AIOps integration (tuần 5–8)¶
| Hạng mục | Done khi |
|---|---|
Correlation engine ghi related_trace_ids |
≥1 trace_id trên incident groups có HTTP path |
| Critical path extractor (ERROR/duration) | Evidence bundle tự động trong ticket/incident |
| Trace-based SLI trên root spans (không lệch sample) | Dashboard error budget ổn định |
| Case library: timeout cascade, retry storm | Runbook + training labels |
Phase 3 — Hardening (quý tiếp)¶
| Hạng mục | Done khi |
|---|---|
| Bloom FP tune + Memcached hit rate >70% hot queries | Query P99 TraceID p95 < 2s |
| TraceQL có chủ đích (service, status, route) | Không full-scan thói quen |
| Multi-window burn + synthetic traces | Detect path failure trước user complaint lớn |
| Parquet export offline cho model training | Pipeline feature store tái lập được |
| Privacy: retention/TTL theo tenant + KMS + RBAC Explore | Pass security review |
Roadmap tư duy:
Connect (propagation + IDs)
→ Store reliably (Tempo HA)
→ Sample intelligently (tail policies)
→ Correlate (trace_id in AIOps)
→ Automate RCA path finding
→ Close loop (remediation + learn)
Tip
KPI health của chương Tempo trong org: (1) MTTD từ metric, (2) % incidents có ít nhất một full trace useful, (3) median time từ alert → critical span, (4) $/tháng S3+compute tracing, (5) số lần "không có trace" trong postmortem. Tối ưu (2)(3) trước khi đổ tiền model LLM.
Chapter Scores¶
| Tiêu chí | Điểm số | Ghi chú |
|---|---|---|
| Technical Accuracy | 9.7/10 | Cấu trúc định dạng Parquet, bloom filters, cú pháp TraceQL đã được xác thực |
| Production Readiness | 9.6/10 | Có cấu hình đầy đủ, chạy StatefulSet, phân quyền IRSA |
| Depth | 9.7/10 | Sampling paradox, RCA path, edge PII/cardinality, case cascade |
| Practical Value | 9.8/10 | Decision tree cost/coverage, roadmap, workflow Grafana |
| Architecture Quality | 9.6/10 | Kiến trúc phân tán đầy đủ |
| Observability | 9.6/10 | Có các câu lệnh PromQL để tự giám sát hệ thống Tempo |
| Security | 9.6/10 | Có cấu hình IRSA, mTLS, mã hóa S3 KMS, PII guidance |
| Scalability | 9.6/10 | Phân tích điểm nghẽn cho từng thành phần cụ thể |
| Cost Awareness | 9.8/10 | Con số chi phí thực tế + decision tree sampling |
| Diagram Quality | 9.6/10 | Có đầy đủ biểu đồ tuần tự cho luồng ghi và luồng đọc |
| AIOps Fit | 9.7/10 | trace_id correlation, path finding, SLI patterns |
References¶
- Grafana Tempo Documentation
- TraceQL Reference
- SpanMetrics Connector
- Tempo Parquet Format
- Jaeger vs Tempo Comparison
- Apache Parquet Format
- OpenTelemetry Tail Sampling Processor
- W3C Trace Context
Acceptance contract của chapter¶
Chapter này tuân theo Acceptance Template chung: capability chỉ được coi là hoàn tất khi có scope, input quality, timeline scenario, expected output, negative control, hard gates, thresholds và evidence artifact tái tạo được. Checklist hoặc demo không thay thế benchmark/replay.