このページの目次

分散トレーシング

リクエストが10以上のマイクロサービスにまたがる場合、どこに時間がかかっているかをどうやって特定するか?分散トレーシングは、各リクエストに trace ID を割り当て、サービス間の呼び出し関係を context propagation を通じて span context(trace ID + parent span ID + サンプリングフラグ)として伝播させ、各ノードに散在する断片を結合して完全な呼び出しチェーンを構築します。サンプリング戦略はコスト管理の鍵となります:全量サンプリングは高コストすぎ、固定比率サンプリングはロングテールのエラーを見逃す可能性があり、tail-based サンプリングは処理完了後にどの trace を保持するかを決定します。

Dapper (Google 2010) → OpenTelemetry

Google の Dapper 論文はオープンソースエコシステムに影響を与えました:Zipkin (Twitter) → Jaeger (Uber) → OpenTracing + OpenCensus → OpenTelemetry (CNCF、現在の標準)。

Trace と Span

Trace と Span の構造: 1つの Trace は複数の Span で構成されます Trace — 1つのエンドツーエンドリクエストにおけるすべての span の集合 Span — 1つの RPC 呼び出しの記録で、以下の 7 つのフィールドを含みます trace_id trace の一意の ID span_id この span の一意の ID parent_id 呼び出し元の span の ID(root span には parent がない) operation "GET /api/users" / "SELECT * FROM users" start/end タイムスタンプ status OK / ERROR(エラー詳細を含む) attributes カスタムタグ(http.status_code, db.statement, ...) 重要な構造: parent_id により、各サービスノードに散在する span が 1 つの木構造に結合される—— root span には parent がない。子 span はすべて呼び出し元の span_id を記録する。 Span の呼び出し階層ツリー: /api/order リクエストの親子構造 Trace: ユーザーリクエスト → /api/order Span A · GET /api/order root span(parent なし) Span B · auth-service.ValidateToken ユーザートークンの検証 Span C · order-service.CreateOrder 注文の作成 Span D · postgres: INSERT INTO orders 実際のデータベース書き込み操作(リーフ span) parent_id により、auth-service / order-service / postgres の span が 1 つの木構造に結合される: root span (A) には parent がない。子 span は呼び出し元の span_id を記録し、階層が決定される。

Context Propagation

サービス間での trace の伝播が重要である——各サービスは「自分がどの trace のどの span であるか」を把握していなければならない:

HTTP: traceparent: 00-<trace_id>-<span_id>-<trace_flags>
W3C Trace Context (標準): traceparent ヘッダー
gRPC: メタデータ (grpc-trace-bin)
Kafka: メッセージヘッダー

trace_flags: bit 0 = SAMPLED(サンプリング対象かどうか)、他の bits は予約済み。

内部実装: トレーサーは下流サービスへの呼び出し時に新しい span(呼び出し元 span の子)を作成し、trace_id + new_span_id を outbound ヘッダーに格納する。下流のミドルウェア/ライブラリ(HTTP クライアント、gRPC スタブ、DB ドライバー)は、レスポンス受信時に trace context を抽出し、自動的に子 span を作成する。

サンプリング

全量収集 → オーバーヘッドが許容できない(各 span につき約 1μs のオーバーヘッド + ネットワーク/ストレージコスト)。サンプリング戦略が必要:

  • Head-based (確率的): trace 開始時にサンプリングするかどうかを決定する(ランダムで 1/100 または 1/1000)。単純だが、エラーを含む trace を見逃す可能性がある。
  • Tail-based (インテリジェント): すべての span をキャッシュ(ローカルバッファ)し、trace 完了後に結果(ERROR? SLO を超えるレイテンシ?)に基づいて保持するかどうかを決定する。エラーを見逃さないが、メモリオーバーヘッドが大きい。OpenTelemetry の tailsampling プロセッサーがこの戦略を実装している。

Jaeger 展開 (参考)

# Agent (ホストごとに 1 つ、UDP を介してアプリから span を受信)
# Collector (セントラル、エージェントから受信 → ストレージに書き込み)
# Storage (Cassandra/Elasticsearch)
# Query (UI + API)

参考

  • 論文⁠: "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure" (Google, 2010)
  • OpenTelemetry: opentelemetry.io/docs/specs/otel/trace
  • Jaeger: jaegertracing.io

Keywords: 分散トレーシング, Dapper, OpenTelemetry, Span, trace context propagation, W3C Trace Context, サンプリング