Cloud Trace Fundamentals - Cơ chế Distributed Tracing
Tại sao lại cần distributed tracing trong production
Trong hệ thống monolith, bạn đặt breakpoint debugger vào function, step through code. Nhưng trong microservices, khi một request đi qua 8 services rồi, bạn không thể:
- Sử dụng local debugger
- Nhìn stack trace trực tiếp vì request không chạy trên một process
- Tính latency bằng cách nhìn timestamps của logs vì mỗi service có clock riêng (clock skew)
Distributed tracing giải quyết bằng cách: gắn một trace ID duy nhất vào request, theo dõi request đi qua mỗi service như thế nào, và sau đó reconstruct toàn bộ flow trong Cloud Trace console.
Internal Model: Trace, Span, và Context
Trace ID - tiêu đề nhận dạng của cả request flow
Một trace là chuỗi hoạt động của một request từ lúc vào hệ thống đến khi hoàn thành. Mỗi trace có một trace ID duy nhất, là một chuỗi hex 32 ký tự (128 bit):
trace_id = a0f3c87f9a1b2c3d4e5f6a7b8c9d0e1fTrace ID này được:
- Sinh ra lần đầu tiên request đến frontend service
- Truyền đi trong mỗi HTTP/gRPC request tới downstream services (dùng header như
X-Cloud-Trace-Contexthoặc W3Ctraceparent) - Lưu trữ bên Cloud Trace cùng với tất cả span data
- Truy vấn để xem toàn bộ flow
Span - đơn vị công việc
Mỗi operation bên trong trace gọi là một span. Ví dụ:
- Span "Handle HTTP request" ở frontend service
- Span "Query user database" ở user service
- Span "Fetch product" ở product service
- Span "Render template" ở frontend
Mỗi span có:
{
name: "db.query",
span_id: "1a2b3c4d5e6f7a8b",
parent_span_id: "8b7a6f5e4d3c2b1a", // span nào gọi span này
trace_id: "a0f3c87f9a1b2c3d4e5f6a7b8c9d0e1f",
start_time: "2026-06-24T10:30:00.000000Z",
end_time: "2026-06-24T10:30:00.050000Z",
duration: 50ms,
attributes: {
"db.system": "postgresql",
"db.statement": "SELECT * FROM users WHERE id = ?",
"db.rows_affected": 1
},
status: "OK" // hoặc ERROR
}Span Hierarchy - tạo cây quan hệ
Spans tạo thành một cây:
trace_id=abc123
├── span[http_request] (parent_span_id=null)
│ ├── span[db_query] (parent_span_id=http_request)
│ ├── span[cache_get] (parent_span_id=http_request)
│ └── span[call_downstream_service]
│ └── span[downstream_process] (parent_span_id=call_downstream_service)
└── span[response_render] (parent_span_id=http_request)Quan hệ parent-child này cho phép:
- Tính latency của từng operation
- Xác định bottleneck (span nào lâu nhất)
- Thấy parallelism (span nào chạy cùng lúc)
Context Propagation - vấn đề của distributed tracing
Khi request từ service A gọi service B:
Service A Service B
+----------+ +----------+
| Span A1 | HTTP GET | Span B1 |
| (db) | + headers -----> | (http) |
| | ?trace_id=... | |
| | &parent_span=...| ... |
+----------+ +----------+Service B cần biết:
trace_idlà gì (để biết request này thuộc trace nào)parent_span_idlà gì (để biết ai gọi nó)
Nếu không có context propagation, mỗi service sẽ tạo trace ID mới → sau này không thể reconnect chúng lại.
Trace Collection Architecture
Span Generation
Khi instrumented code chạy:
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("process_order") as span:
# span được emit ở đây
span.set_attribute("order_id", 123)
# ... codeSpan được sinh ra in-memory trong application process, sau đó cần được export tới Cloud Trace.
Export Mechanism - OTLP (OpenTelemetry Protocol)
Application không gửi span trực tiếp tới Google Cloud API (quá chậm, overhead cao). Thay vào đó:
- Local Collector (optional) hoặc Application Buffer: Spans được lưu tạm trong memory buffer
- Batch Export: Mỗi N spans (thường N=512) hoặc mỗi T milliseconds (thường T=5000ms), spans được batch lại
- OTLP Protocol: Batch được gửi qua OTLP (OpenTelemetry Protocol) tới Cloud Trace API
- GCP Ingest: Cloud Trace API nhận batch, decompress, validate, lưu vào backend storage
Application
|
+-> [In-Memory Buffer]
| |
| v (mỗi 5s hoặc 512 spans)
| [OTLP Batch]
| |
+----> gRPC ---> [Cloud Trace API]
|
v
[Validation]
|
v
[Storage Backend]Sampling - Quyết định không export
Nếu export mỗi span, overhead sẽ không chịu nổi:
Một service xử lý 10,000 request/giây, mỗi request có 20 spans → 200,000 spans/giây. Nếu export tất cả:
- Network overhead: 200KB/s+
- API call volume: rate limiting
- Storage cost: xuyên tế chi phí ngất ngưởng
Sampling decision quyết định: "Span này có nên export hay không?"
Head-Based Sampling
Quyết định được làm ở đầu trace (khi request đầu tiên đến):
# Sampler quyết định: "Có sample trace này không?"
sampler = TraceIdRatioBased(0.1) # 10% sampled
if sampler.should_sample(trace_id):
# export span
else:
# đừng exportLợi ích:
- Nhanh, quyết định cục bộ
- Overhead thấp
Nhược điểm:
- Không thể capture tail latencies (những request chậm mà lẻ tẻ)
- Nếu sample 0.1%, bạn miss 99.9% traces
Tail-Based Sampling
Quyết định được làm sau khi trace hoàn thành:
[Receive all spans]
|
v
[Wait for trace completion]
|
v
[Analyze: does trace match sampling criteria?]
|
v (e.g., latency > 1s? error occurred?)
[Keep or discard]Ví dụ: "Keep tất cả traces với latency > 1 giây"
Lợi ích:
- Capture tail latencies
- Có thể prioritize interesting traces
Nhược điểm:
- Cần lưu tạm tất cả spans cho đến khi trace complete
- Storage overhead cao
- Cloud Trace không hỗ trợ native tail-based sampling (phải dùng OpenTelemetry Collector)
GCP Sampling Strategy trong thực tế
Cloud Trace không hỗ trợ tail-based sampling natively. Thay vào đó, Google recommend:
Probability-based head sampling (đơn giản nhất):
sample_rate = 0.1 # 10%Với high-traffic systems (10K+ QPS), 0.1% hay 0.01% là normal.
OpenTelemetry Collector với Tail Sampling Processor (nếu cần):
- Deploy collector ở mỗi region
- Collector nhận tất cả spans, buffer locally
- Sau trace complete, collector quyết định keep/discard
- Chỉ send sampled spans tới Cloud Trace
Trade-off: Collector deployment complexity, local storage overhead, nhưng bạn get tail insights.
Storage & Retention
Trace Data Structure
Cloud Trace lưu:
{
project_id: "my-gcp-project",
trace_id: "a0f3c87f9a1b2c3d4e5f6a7b8c9d0e1f",
spans: [
{ span_id, parent_span_id, name, start_time, end_time, ... },
{ ... }
],
process_time: "2026-06-24T10:30:00.100000Z"
}Mỗi trace có tối đa 10,000 spans (limit này hiếm khi hit).
Retention
- Default retention: 30 ngày
- Tier 1 (In-Situ Analysis): 30 ngày, tối ưu cho real-time analysis
- Tier 2 (Archive): Optional, giữ lâu hơn (phí storage cao hơn)
Quyết định retention phải cân nhắc:
- Compliance requirement (PCI, HIPAA có thể yêu cầu 1-7 năm)
- Cost (lưu lâu = tốn tiền)
- Investigation window (bạn thường investigate lỗi trong bao lâu?)
Quotas
Cloud Trace có quota:
| Metric | Limit |
|---|---|
| Traces ingested per minute | ~ 1M |
| Spans per trace | 10,000 |
| API read requests | 20K/min |
| Spans per project per day | Unlimited (pay per GB) |
Nếu exceed quota:
- Spans bị drop silently (điều này nguy hiểm!)
- Bạn không biết bạn đã miss data
- Cost tăng nhanh không kiểm soát
Cost Model
Cloud Trace pricing (GCP 2026):
- Ingestion: $0.50 per million spans (trên 1 triệu spans/tháng miễn phí)
- Storage (Archive tier): $0.03 per GB per month
- Analysis queries: $6 per 1M traces queried
Điều này có nghĩa:
- Sampling at 0.1% thay vì 100% = 1000x cost reduction
- Quyết định sampling là quyết định business, không chỉ technical
Sampling Decision Framework
Trade-off Matrix
| Factor | Thiên về High Sampling | Thiên về Low Sampling |
|---|---|---|
| Cost | Cao | Thấp |
| Coverage | Tất cả requests đều có trace | Có blind spot |
| Tail latency capture | Dễ thấy slow requests | Có thể miss slow requests |
| Investigation resolution | Cao | Thấp |
| Data noise | Nhiều traces fast & normal | Ít traces, chỉ sampled |
Heuristic Sampling
Thay vì uniform probability (0.1% tất cả), có thể dùng heuristic:
def should_sample(request):
# Luôn sample lỗi
if request.has_error:
return True
# Luôn sample slow requests
if request.latency > 1000ms:
return True
# Sample requests tới critical endpoints
if request.endpoint in CRITICAL_ENDPOINTS:
return True
# Uniform sample 0.1% của cái còn lại
return random() < 0.001Kết quả: Bạn sample 5-10% thay vì 0.1%, nhưng 95% trong đó là "interesting" (slow, error, critical).
Trace Query & Analysis
Trace Explorer
Cloud Trace console cho phép:
- Timeline visualization: Xem mỗi span kéo dài bao lâu
- Span hierarchy: Xem parent-child relationships
- Latency breakdown: "90% latency ở đâu?" (database query, external API, etc)
- Stats: "P50, P99 latency across millions of traces"
SQL-based Analytics (Observability Analytics)
Nếu muốn query ngàn traces:
SELECT
PERCENTILE_CONT(span.duration_millis, 0.99) as p99_latency,
COUNT(*) as trace_count,
span.attributes['endpoint'] as endpoint
FROM traces.spans
WHERE trace.start_time >= "2026-06-24T00:00:00Z"
GROUP BY endpointĐiều này useful cho:
- SLO analysis (có meet latency target không?)
- Trend detection (latency tăng theo thời gian?)
- Service comparison (service A vs B, ai chậm hơn?)
Constraints & Failure Modes
Constraint 1: Clock Skew
Mỗi server có clock riêng. Khi span từ service A (server 1) gọi span từ service B (server 2), nếu clock khác nhau:
Server A (clock +5s) Server B (clock -2s)
Span start: 10:30:00 Span start: 10:29:53
(nhưng thực tế cùng lúc)Kết quả: trace timeline có thể bị sai lệch, thậm chí parent_span_id bị recorded sau child_span_id.
Mitigation:
- Deploy NTP daemon trên tất cả servers
- Cloud Trace có clock skew detection/correction algorithm
- Metric: "clock skew" được track như một observability signal
Constraint 2: Lossy Due to Sampling
Nếu sampling 0.1%, bạn sẽ miss 99.9% traces. Điều này có nghĩa:
- Một lỗi hiếm (xảy ra 1/5000 requests) có thể không bao giờ bị capture
- Bạn không thể verify "lỗi đã fix chưa" vì bạn chỉ thấy 0.1% data
Mitigation:
- Combine tracing với metrics (error rate, latency percentile) - nếu metrics spike, biết là có vấn đề
- Tail sampling cho error/slow spans
- Alert nếu sampling rate quá thấp
Constraint 3: Context Propagation Failure
Nếu một service không pass trace ID headers:
Service A Service B
Span A123 ----HTTP---> (no headers) Span B999 ← span ID mới!Result: Trace bị break, service B có separate trace. Không thể see full flow.
Mitigation:
- Validate context propagation: check logs để verify headers đúng format
- Auto-instrumentation (OpenTelemetry agents) handle propagation automatically
Constraint 4: Trace Completeness
Khi trace có 1000+ spans từ nhiều services, một service delay hoặc timeout → trace incomplete.
Cloud Trace sẽ:
- Timeout wait 30-60 seconds
- Publish partial trace
- Mark as incomplete
Khi xem trace, bạn thấy dấu "⚠️ incomplete" → không full picture.
Performance Implications
Span Generation Overhead
Tạo span (gọi tracer.start_span()) có cost:
- Allocate span object: ~100 nanoseconds
- Set attributes: ~10 nanoseconds mỗi attribute
- Close span: ~50 nanoseconds
Với 10,000 spans/second, CPU cost ≈ 1.6ms/s = 0.16% CPU. Chấp nhận được.
Export Overhead
Batching + export OTLP:
- Local processing: minimal
- Network: 1-10ms per batch (depend on latency)
- GCP ingestion: typically <100ms end-to-end
Điều quan trọng: Export không block application thread (async), nên latency application không bị impact.
Memory Overhead
In-memory buffer lưu spans:
- Per span: ~500-1000 bytes (depend on attributes)
- Buffer size: typically ~512 spans = 250-500 KB
Chấp nhận được với modern JVMs/Go processes (mem sử dụng gigs).
Production Patterns
Pattern 1: Baseline Sampling + Alert-Triggered High Sampling
Bình thường: sample 0.1%
Khi alert fire (latency spike detected):
- Tạm thời increase sample rate lên 5-10%
- Capture more traces
- Debug issue
Sau khi debug xong:
- Quay lại 0.1%
Implementation: Sampler lấy sample rate từ dynamic config service.
Pattern 2: Sampling by Criteria
def should_sample(request):
# Tất cả internal services: 1%
if request.is_internal:
return random() < 0.01
# External API: 0.1% (cost conscious)
if request.is_external_api:
return random() < 0.001
# Custom: critical customers get higher sampling
if request.customer_tier == "enterprise":
return random() < 0.5
# Default
return random() < 0.01Pattern 3: Tail Sampling với OpenTelemetry Collector
Deploy collector regional:
App1 ─┐
App2 ─┼─> [OTel Collector] (tail sampling logic)
App3 ─┘ |
v
Cloud Trace (only sampled traces)Collector filtering:
- Keep: latency > 1 second
- Keep: status == ERROR
- Drop: otherwise
Anti-patterns
Anti-pattern 1: "Vô điều kiện export mỗi span"
# BAD
exporter.export_every_span = TrueVấn đề:
- 10K QPS × 20 spans = 200K spans/sec = huge cost
- Network saturation
- GCP quota exceeded
- Service bị throttle
Anti-pattern 2: "Không sample gì cả, chỉ 0%"
# BAD
sampler.ratio = 0.0 # never sampleVấn đề:
- Không có trace data
- Không có debug capability
- Latency issue xảy ra, bạn không biết where
Anti-pattern 3: "Sampling rate fixed, không adjust"
# BAD
sampler.ratio = 0.001 # hardcodedVấn đề:
- High-traffic periods: miss important traces
- Low-traffic periods: wasting quota sampling boring traces
- Không adaptive