- Published on
分散トレーシング完全ガイド 2025: OpenTelemetry、Jaeger、Tempo、Span解析、サンプリング戦略
- Authors

- Name
- Youngju Kim
- @fjvbn20031
TL;DR
- 分散トレーシング = マイクロサービスデバッグの必須: 一つのリクエストの全体フローを可視化
- OpenTelemetryが標準: CNCF卒業、全言語サポート、ベンダー中立
- 3大バックエンド: Jaeger (Uber)、Tempo (Grafana)、Zipkin (Twitter)
- Span構造: trace_id + span_id + parent_id + 属性 + イベント
- Samplingが鍵: 100%保存はコスト爆発 → Head/Tail/Adaptive
- W3C Trace Context: サービス間のtrace伝播標準ヘッダ
1. 分散トレーシングが必要な理由
1.1 モノリスのデバッグ
リクエスト -> [モノリスアプリ]
|- Auth check
|- DB query
|- Cache lookup
|- Response
スタックトレース一つで十分。全コードが一プロセス内。
1.2 マイクロサービスの悪夢
リクエスト -> [API Gateway]
-> [Auth Service]
-> [User Service]
-> [DB] [Cache]
-> [Email Service]
-> [SMS Service]
問題:
- どのサービスが遅い?
- エラーはどこから始まった?
- ネットワーク遅延かコードの問題か?
- 一つのリクエストのログをどう集める?
→ 分散トレーシングが答え。
1.3 分散トレーシングの約束
Total: 1245ms
|- API Gateway (5ms)
|- Auth Service (50ms)
| |- JWT verify (45ms)
|- User Service (1180ms) WARN
| |- DB query (1100ms) <- ボトルネック
| |- Cache lookup (5ms)
|- Response (5ms)
即座に判明: DBクエリが1100ms。インデックス欠落。
2. 中核概念
2.1 Trace
Trace = 一リクエストの全体フロー。trace_idで識別。
trace_id: abc123...
|- Span A (root)
|- Span B (child of A)
|- Span C (child of B)
|- Span D (child of A)
2.2 Span
Span = Traceの単位作業。
必須フィールド:
span_id: 固有IDtrace_id: 所属traceparent_span_id: 親span (無ければroot)name: 作業名 (例:HTTP GET /users)start_time,end_timestatus: OK / ERRORattributes: key-valueメタデータ
オプション: events, links, kind (SERVER/CLIENT/PRODUCER/CONSUMER/INTERNAL)。
2.3 Span例
{
"trace_id": "abc123def456...",
"span_id": "789xyz...",
"parent_span_id": "456abc...",
"name": "GET /api/users/123",
"start_time": "2025-04-15T10:00:00.000Z",
"end_time": "2025-04-15T10:00:00.150Z",
"duration_ms": 150,
"status": { "code": "OK" },
"attributes": {
"http.method": "GET",
"http.url": "/api/users/123",
"http.status_code": 200,
"user.id": "123",
"db.query.count": 3
},
"events": [
{ "name": "cache_miss", "timestamp": "2025-04-15T10:00:00.020Z" }
]
}
2.4 Context Propagation
サービス間でtraceをどう伝える?
W3C Trace Context 標準ヘッダ:
GET /api/users/123 HTTP/1.1
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
traceparent 形式: version-trace_id-parent_span_id-trace_flags。次のサービスがこれを受け取り、同じtrace_idで子spanを作る。
3. OpenTelemetry — 標準化の勝利
3.1 OTelとは
OTel = CNCFのobservabilityプロジェクト。Trace/Metric/Logsを統合。
歴史:
- 2015: GoogleがOpenCensusを発表
- 2016: Uber/LightstepがOpenTracingを発表
- 2019: 両プロジェクトが統合 → OpenTelemetry
- 2021: CNCF Incubating
- 2024: Trace/Metric GA (Stable)
3.2 OTelが標準になった理由
- ベンダー中立: コードはOTel APIのみ使用。バックエンドはJaeger、Tempo、Datadog、New Relicなど自由。
- 全言語サポート: Go、Java、Python、JS、C#、Ruby、PHP、Rust、Swift…
- Auto-instrumentation: Java agent、Python decoratorsなどでコード変更不要。
- 単一標準: 以前はベンダーごとに異なるSDK、今はOTel一つで互換。
3.3 OTelアーキテクチャ
[Application]
| (OTel SDK)
[Spans/Metrics/Logs]
| (OTLP)
[OpenTelemetry Collector]
| (export)
[Jaeger / Tempo / Datadog / ...]
OTel Collector: 受信・変換・複数バックエンドへエクスポート。
3.4 Node.js コード例
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
const sdk = new NodeSDK({
serviceName: 'my-service',
traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces'
}),
instrumentations: [getNodeAutoInstrumentations()]
})
sdk.start()
// HTTP、Express、MongoDBが自動トレーシング
3.5 手動Span
const { trace } = require('@opentelemetry/api')
const tracer = trace.getTracer('my-service')
async function processOrder(orderId) {
const span = tracer.startSpan('process_order')
span.setAttribute('order.id', orderId)
try {
await chargePayment(orderId)
span.addEvent('payment_charged')
await updateInventory(orderId)
span.addEvent('inventory_updated')
span.setStatus({ code: SpanStatusCode.OK })
} catch (error) {
span.recordException(error)
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
throw error
} finally {
span.end()
}
}
4. バックエンド比較 — Jaeger vs Tempo vs Zipkin
4.1 Jaeger (Uber)
- Uber発 (2017)、CNCF卒業、Go製
- Cassandra/Elasticsearchバックエンド
- 強力なUI (検索、比較、依存グラフ)
- 成熟・広く採用・OTel互換
- 欠点: ストレージコスト (Elasticsearch)、設置が複雑
4.2 Tempo (Grafana)
- Grafana Labs (2020)
- Object Storage (S3, GCS) — 非常に安価
- インデックス無し、trace_idで直接取得
- Grafana統合が自然
- 欠点: 検索が弱い (TraceQLで改善中)、Jaegerより新しい
TraceQL:
{ resource.service.name = "checkout" && duration > 1s }
4.3 Zipkin
- Twitter (2012)、Java、MySQL/Cassandra/Elasticsearch
- 最古・安定・シンプル
- 新機能の追加が遅い、多くがJaeger/Tempoへ移行中
4.4 比較表
| Jaeger | Tempo | Zipkin | |
|---|---|---|---|
| 出自 | Uber | Grafana | |
| 言語 | Go | Go | Java |
| ストレージ | Cassandra/ES | Object Storage | MySQL/ES |
| コスト | 高 | 低 | 中 |
| 運用 | 複雑 | シンプル | シンプル |
| UI | 独自 + Grafana | Grafana | 独自 |
| 検索 | 強力 | TraceQL (改善中) | 中 |
| OTel | Yes | Yes | Yes |
4.5 クラウドマネージド
| 価格 | 特徴 | |
|---|---|---|
| Datadog APM | 高 | 強力、商用標準 |
| New Relic | 高 | フルスタック |
| Honeycomb | 高 | 高カーディナリティ分析 |
| Lightstep | 高 | 変更分析 |
| Grafana Cloud | 合理的 | Tempoマネージド |
| AWS X-Ray | 中 | AWS統合 |
5. Sampling戦略
5.1 なぜSamplingが必要か
100%保存のコスト:
- 10k req/sec × 30日 = 26億trace
- 5KB/trace平均 = 130 TB/月
- ストレージ + 処理コストが爆発
解決: Sampling — 一部のみ保存。
5.2 Head Sampling
リクエスト開始時に決定。
# 10% sampling
if random.random() < 0.1:
span = tracer.start_span(...)
長所: 単純・高速・安価。短所: エラーtraceも90%捨てる。
5.3 Probabilistic Sampling
trace_idのhashによる一貫したSampling。分散環境で同じ判定。
processors:
probabilistic_sampler:
sampling_percentage: 10
5.4 Tail Sampling
リクエスト完了後に決定。
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: error-traces
type: status_code
status_code: { status_codes: [ERROR] }
- name: slow-traces
type: latency
latency: { threshold_ms: 1000 }
- name: probabilistic
type: probabilistic
probabilistic: { sampling_percentage: 1 }
長所: エラー100%保存、遅いtrace 100%保存、正常は1%のみ。短所: 全spanをメモリにバッファ・複雑。
5.5 Adaptive Sampling
トラフィックに応じ自動調整。少トラフィック → 高率、多トラフィック → 低率。DatadogやHoneycombが自動対応。
5.6 ベストプラクティス
正常トラフィック: 1%
遅いtrace (>1s): 100%
エラー: 100%
新endpoint: 100% (1週間)
重要endpoint (/checkout): 10%
→ Tail Samplingで実装。
6. Auto-instrumentation
6.1 Java
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=my-app \
-Dotel.exporter.otlp.endpoint=http://collector:4318 \
-jar my-app.jar
HTTP (Servlet、Spring MVC)、DB (JDBC、Hibernate)、メッセージング (Kafka、RabbitMQ)、gRPC、100以上のライブラリ自動トレーシング。
6.2 Python
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap --action=install
opentelemetry-instrument python my_app.py
Flask、Django、FastAPI、requests、SQLAlchemyなど自動。
6.3 Node.js
node --require @opentelemetry/auto-instrumentations-node/register my-app.js
6.4 Go
コンパイル言語のためAuto-instrumentationが難しい。明示コードが必要。
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
tracer := otel.Tracer("my-service")
func handleRequest(ctx context.Context) {
ctx, span := tracer.Start(ctx, "handle_request")
defer span.End()
}
最新: eBPFベースのAuto-instrumentation登場 (Grafana Beyla)。
7. Traceの分析
7.1 何を見るか
- Critical Path: 最長経路 — どのspanが時間を食うか、並列化可能か。
- Error Span: 赤いspan — 発生箇所、エラーメッセージ。
- 外部呼び出し: HTTP/DB/gRPC — 最遅、再試行パターン。
- 時間比較: 平均 vs 今回、デプロイ前後。
7.2 よくあるパターン
N+1 クエリ
parent_span (200ms)
|- db_query (5ms) <- user情報
|- db_query (5ms) <- user 1の投稿
|- db_query (5ms) <- user 2の投稿
... (47 more)
→ JOINまたはbatchで解決。
直列 vs 並列
parent_span (300ms)
|- call_service_a (100ms)
|- call_service_b (100ms) <- 直列!
|- call_service_c (100ms)
→ Promise.allで並列化、100msへ。
キャッシュミス
parent_span (200ms)
|- cache_check (5ms) <- miss
|- db_query (190ms)
→ ヒット率を分析。
7.3 REDメソッドとの組合せ
Rate、Errors、DurationをTraceから算出。
processors:
spanmetrics:
metrics_exporter: prometheus
→ Prometheusにメトリクスを自動生成。
8. コスト最適化
8.1 コストドライバー
- データ量 (trace/sec × span/trace)
- 保存期間 (7/30/90日)
- インデックス (検索可能フィールド)
- ネットワークegress (region間)
8.2 削減戦略
- 積極的Sampling — 正常0.1%、エラー/遅い100%
- Cardinality削減 —
user_idなど固有値を属性に入れない - Object Storage (Tempo) — Elasticsearch比90%削減
- ローカル処理 — Collectorでメトリクス抽出
- プロバイダ比較 — 100億span/月: Datadog 500
9. 実戦 — マイクロサービスデバッグ
9.1 シナリオ
ユーザー不満: 「注文がたまに30秒かかる」。
9.2 Trace検索
{ resource.service.name = "checkout" && duration > 5s }
100件発見。
9.3 パターン分析
共通点: 決済サービスで時間消費、payment.gateway.call spanが15-25s。
9.4 深掘り
checkout (28s)
|- validate (10ms)
|- inventory (50ms)
|- payment (27s)
|- db_save (10ms)
|- stripe_api_call (26.9s) <- !!
|- http_retry (3 attempts, 9s each)
Stripe呼び出しが毎回timeout、3回再試行。
9.5 追加調査
OTel属性:
"http.url": "https://api.stripe.com/v1/charges",
"http.status_code": 0,
"http.error": "EAI_AGAIN"
→ DNS問題。checkoutサービスの/etc/resolv.confが壊れていた。
9.6 解決
DNS修正後、再デプロイ:
checkout (1.2s) OK
|- payment (800ms)
|- stripe_api_call (750ms)
時間節約: 分散トレーシングなしでは数日かかるデバッグが分単位。
10. ベストプラクティス
10.1 良いSpan名
Bad: db_query / http_request
Good: SELECT users by id / GET /api/users/{id}
→ Cardinality制御: 変数は属性、名前はパターン。
10.2 意味ある属性
span.set_attribute("user.id", user_id)
span.set_attribute("user.tier", "premium")
span.set_attribute("db.statement", query)
OTel Semantic Conventionsに従う: http.method、db.system、messaging.system等。
10.3 エラー記録
try:
do_something()
except Exception as e:
span.record_exception(e)
span.set_status(Status(StatusCode.ERROR, str(e)))
raise
10.4 span過多を避ける
細かい関数ごとにspanを作ると騒音。意味ある作業単位で。
10.5 セキュリティ — 機密情報を除外
password、credit_card、api_keyを属性に入れない。マスクかIDのみ。
10.6 Trace + Log + Metric連携
同じtrace_idで結びつける:
import logging
from opentelemetry import trace
current_span = trace.get_current_span()
ctx = current_span.get_span_context()
logger.info("Order processed", extra={
"trace_id": format(ctx.trace_id, "032x"),
"span_id": format(ctx.span_id, "016x"),
"order_id": order_id
})
→ ログからtrace_idで検索、全体フロー可視化。
クイズ
1. OpenTelemetryが標準になった理由は?
答: (1) ベンダー中立 — コードはOTel APIのみ、バックエンドは自由、(2) 全言語サポート、(3) Auto-instrumentation (Java agent、Python decorators)、(4) CNCF卒業、(5) OpenCensus + OpenTracingの統合。結果: 全observabilityツール (Jaeger、Tempo、Datadog、New Relic) がOTel互換。
2. Head Sampling vs Tail Sampling?
答: Head: リクエスト開始時に決定 (例: 10%確率)。単純だが エラーtraceも90%捨てる。Tail: 完了後に決定。「エラー100%、遅い100%、正常1%」のようなポリシーが可能でデバッグに有用。短所: 全spanをメモリに保持しコスト増。大規模はTail Samplingが標準、シンプル環境はHeadで十分。
3. TempoがJaegerより安い理由は?
答: Object Storage使用。JaegerはCassandraまたはElasticsearch (検索インデックス必要で高価)。TempoはS3/GCSに直接保存し インデックス無し。trace_idでのみ取得可能。検索は弱いが ストレージ90%削減。TraceQLで改善中。100億span/月: Datadog 500。
4. W3C Trace Contextとは?
答: サービス間trace伝播の標準HTTPヘッダ。traceparentに version-trace_id-parent_span_id-trace_flags 形式で伝達。例: traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01。以前はベンダー固有 (X-B3-TraceId、X-Datadog-Trace-Id) だったが W3C標準で統一。OTel、Jaeger、Tempo、Datadogが対応。
5. 分散トレーシングの核心価値は?
答: マイクロサービスで一リクエストの全体フローを可視化。モノリスではスタックトレース一つで十分だが、マイクロサービスではどのサービスが遅いか、どこでエラーが始まったか把握しづらい。Traceで: (1) ボトルネック即発見 (DBクエリ1100ms)、(2) エラー源追跡、(3) サービス依存把握、(4) 最適化優先度。デバッグ数日が分単位に。
参考資料
- OpenTelemetry
- W3C Trace Context
- Jaeger
- Grafana Tempo
- Zipkin
- OTel Semantic Conventions
- Distributed Systems Observability — Cindy Sridharan
- Mastering Distributed Tracing — Yuri Shkuro
- Honeycomb Observability — Charity Majors
- SigNoz — オープンソースOTelバックエンド
- Beyla — eBPF Auto-instrumentation