Skip to content

✍️ 필사 모드: 分散トレーシング完全ガイド 2025: OpenTelemetry、Jaeger、Tempo、Span解析、サンプリング戦略

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

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: 固有ID
  • trace_id: 所属trace
  • parent_span_id: 親span (無ければroot)
  • name: 作業名 (例: HTTP GET /users)
  • start_time, end_time
  • status: OK / ERROR
  • attributes: 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が標準になった理由

  1. ベンダー中立: コードはOTel APIのみ使用。バックエンドはJaeger、Tempo、Datadog、New Relicなど自由。
  2. 全言語サポート: Go、Java、Python、JS、C#、Ruby、PHP、Rust、Swift…
  3. Auto-instrumentation: Java agent、Python decoratorsなどでコード変更不要。
  4. 単一標準: 以前はベンダーごとに異なる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 比較表

JaegerTempoZipkin
出自UberGrafanaTwitter
言語GoGoJava
ストレージCassandra/ESObject StorageMySQL/ES
コスト
運用複雑シンプルシンプル
UI独自 + GrafanaGrafana独自
検索強力TraceQL (改善中)
OTelYesYesYes

4.5 クラウドマネージド

価格特徴
Datadog APM強力、商用標準
New Relicフルスタック
Honeycomb高カーディナリティ分析
Lightstep変更分析
Grafana Cloud合理的Tempoマネージド
AWS X-RayAWS統合

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 何を見るか

  1. Critical Path: 最長経路 — どのspanが時間を食うか、並列化可能か。
  2. Error Span: 赤いspan — 発生箇所、エラーメッセージ。
  3. 外部呼び出し: HTTP/DB/gRPC — 最遅、再試行パターン。
  4. 時間比較: 平均 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 コストドライバー

  1. データ量 (trace/sec × span/trace)
  2. 保存期間 (7/30/90日)
  3. インデックス (検索可能フィールド)
  4. ネットワークegress (region間)

8.2 削減戦略

  1. 積極的Sampling — 正常0.1%、エラー/遅い100%
  2. Cardinality削減user_idなど固有値を属性に入れない
  3. Object Storage (Tempo) — Elasticsearch比90%削減
  4. ローカル処理 — Collectorでメトリクス抽出
  5. プロバイダ比較 — 100億span/月: Datadog 30,000+vsSelfhostedTempoonS330,000+ vs Self-hosted Tempo on S3 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.methoddb.systemmessaging.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 セキュリティ — 機密情報を除外

passwordcredit_cardapi_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 30,000+vsSelfhostedTempoonS330,000+ vs Self-hosted Tempo on S3 500。

4. W3C Trace Contextとは?

: サービス間trace伝播の標準HTTPヘッダ。traceparentversion-trace_id-parent_span_id-trace_flags 形式で伝達。例: traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01。以前はベンダー固有 (X-B3-TraceIdX-Datadog-Trace-Id) だったが W3C標準で統一。OTel、Jaeger、Tempo、Datadogが対応。

5. 分散トレーシングの核心価値は?

: マイクロサービスで一リクエストの全体フローを可視化。モノリスではスタックトレース一つで十分だが、マイクロサービスではどのサービスが遅いか、どこでエラーが始まったか把握しづらい。Traceで: (1) ボトルネック即発見 (DBクエリ1100ms)、(2) エラー源追跡、(3) サービス依存把握、(4) 最適化優先度。デバッグ数日が分単位に。


参考資料

현재 단락 (1/324)

- **分散トレーシング = マイクロサービスデバッグの必須**: 一つのリクエストの全体フローを可視化

작성 글자: 0원문 글자: 11,032작성 단락: 0/324