ソフトウェア プログラミング サーバー 公開日 2026.05.20 更新日 2026.06.13

OpenTelemetry とは?トレース・メトリクス・ログを統一する観測性の標準

OpenTelemetry (OTel) は トレース・メトリクス・ログ を統一して扱う観測性(Observability)の業界標準。「SDK でアプリに計装 → Collector で集約 → 任意のバックエンド(Datadog / New Relic / Grafana / Jaeger / X-Ray)に送る」 のが基本構造。ベンダーロックインを避けつつ観測性を入れたい現代の開発で必須の技術を整理します。

先に要点

  • OpenTelemetry(OTel) は、トレース・メトリクス・ログの 3 シグナルを統一して扱う 観測性(Observability)の業界標準。CNCF(Cloud Native Computing Foundation)のプロジェクトで、Datadog / New Relic / Grafana / Jaeger / X-Ray など主要観測ツールがほぼ全て対応する。
  • 基本構造は SDK(アプリに計装)→ Collector(集約・変換・送信)→ Backend(可視化)。ベンダーロックインを避けて後から観測ツールを切り替えやすいのが最大の価値で、「計装は OTel 標準、可視化は好きなツール」 が成立する。
  • 運用で詰まるのは入口よりも Collector のメモリと費用。tail_sampling は全 Trace を一時保持するため num_traces の設定次第で簡単に数 GB を消費し、memory_limiter を入れないと OOM で落ちる。本記事は実 YAML としきい値で踏み込む。
  • Auto-Instrumentation は HTTP / DB / 外部 API7〜8 割を自動で取るが、独自スレッドプール・fire-and-forget・gRPC ストリーミング・メッセージキューの非同期境界では Trace が途切れる。どこで切れるかを知っておくのが実務の差になる。
  • 判断軸: 新規はほぼ常に OTel、既存は段階移行、独自エージェント(Datadog Agent など)からの乗り換えはバックエンドが OTLP 対応なら容易。計装を OTel 標準にしておくと将来の移行コストが大きく下がる。

「サービスが遅い」 「エラーが多発」 のような問題が起きたとき、どこで詰まっているかを素早く特定できるかは観測性(Observability)の質で決まります。「Datadog を入れたから安心」 「New Relic を使っているから OK」 で終わると、ベンダーを変えるときに計装を全部やり直す、という落とし穴に当たります。

OpenTelemetry(OTel) は、計装を業界標準で書いておけばバックエンドはあとから何にでも切り替えられる、という考え方の標準仕様です。CNCF のプロジェクトとして主要ベンダー全てが対応しており、現代の観測性のデファクトスタンダードと言える存在になっています。

この記事では概念の整理だけで終わらせず、Collector のメモリ枯渇・tail_sampling の実設定・Auto-Instrumentation が取りこぼす典型ケース・計装オーバーヘッドの目安まで、実際の YAML と数値を添えて踏み込みます。「導入したあと運用で何が起きるか」 を先に知っておくための記事です。

まず Observability(観測性)とは

OpenTelemetry を理解するには、まず観測性という言葉を押さえると話が早いです。

Monitoring と Observability の違い

Monitoring は事前に決めた指標(CPU・メモリ・エラー率など)を監視すること。Observability は内部の動作を外部から推測できる状態を作ること。Observability があれば事前に想定していなかった問題でも分析できる。マイクロサービス時代に重要性が増した概念。

3 つのシグナル

観測性を支える 3 つのデータが TraceMetricLog。それぞれ違う粒度と役割を持ち、組み合わせて使うことで初めて問題の原因まで辿れる。OTel はこの 3 つを統一仕様で扱う。

マイクロサービス時代の課題

1 リクエストが 10 個のマイクロサービスを経由し、サービス間で言語もバラバラ、各サービスが別の観測ツールを使っている、という状態では端から端まで追えない。OTel は言語横断・サービス横断・ツール横断で観測性を統一する仕組み。

ベンダーロックインの問題

Datadog エージェントを全アプリに入れた結果、移行コストが莫大になるといった事態。OTel で計装しておけば Collector の送信先(Exporter)を変えるだけで別ベンダーへ切り替えできる。

OpenTelemetry の 3 シグナル

OTel の中心は Trace / Metric / Log の 3 種類のデータ(シグナル)です。それぞれの役割を整理します。

シグナル 表すもの 典型用途
Trace リクエストが複数サービスを経由する経路を span(個別の処理単位)の連なりとして表現 どこで時間がかかったかを可視化、分散システムの因果関係を追う API リクエスト → 認証 → DB クエリ → 外部 API → レスポンスの流れ
Metric 時系列で集計された数値(カウンタ、ゲージ、ヒストグラム) 全体傾向の監視アラート HTTP 5xx レート、レイテンシ p95、メモリ使用量、キャッシュヒット率
Log 個別のイベントテキスト(構造化ログ推奨) 個別事案の詳細調査、デバッグ 「ユーザー X が支払いに失敗、エラーコード Y」 のような個別イベント

3 つを Trace ID で繋ぐ のが OTel の強みです。「このリクエストの Trace を見る → 該当 span に紐づく Log を表示 → 関連 Metric も同時に見る」 が 1 つの画面で完結する設計になっています。

OpenTelemetry の基本構造

OTel は SDKCollectorBackend の 3 階層で動きます。

SDK(計装)

各言語(Node.js / Java / Python / .NET / Go / Ruby / PHP / Rust)で OTel SDK をアプリに組み込み、span / metric / log を生成する。Auto-Instrumentation を使えば主要ライブラリ(HTTP / DB / メッセージキュー)の呼び出しが自動で計装される。

OTLP(プロトコル)

SDK から Collector / Backend にデータを送る OTel 公式プロトコル。gRPC(4317)・HTTP/protobuf(4318)・HTTP/JSON の 3 形式に対応する。ベンダー横断の共通プロトコルとして機能する。

Collector(集約)

Receiver(入力)・Processor(変換)・Exporter(送信)の 3 段構成。SDK → Collector → 複数のバックエンドで、同じデータの複数ツールへの同時送信・サンプリング・機密情報マスキングなどができる。運用上のチューニング点が集中する場所でもある。

Backend(可視化)

Datadog / New Relic / Grafana / Jaeger / Tempo / Loki / Mimir / AWS X-Ray / Azure Monitor / Google Cloud Trace / Honeycomb など。OTLP に対応していれば Collector から直接送れる。

「SDK → Collector → Backend」 という 3 階層を分離することで、バックエンド変更の影響が Collector の設定だけで済む のが OTel の戦略的価値です。なお Collector には、各アプリと同じホストに置く Agent モード と、集約用に独立して立てる Gateway モード があり、後述の tail_sampling は基本的に Gateway 側に置きます。

Auto-Instrumentation で 5 分で始める

手書きで span を作るのは大変そうに見えますが、現代の OTel は自動計装が成熟しており、多くの言語で数行のセットアップで主要ライブラリの呼び出しが全部トレースされます。

読み込み中...

ここまでは公式チュートリアル通りで快適です。問題は、自動計装が「全部」 を取ってくれるわけではない点です。

Auto-Instrumentation が取りこぼす典型ケース

自動計装は 同期的な呼び出しチェーンと、ライブラリが提供する明示的なフックに強い一方、実行コンテキスト(Trace の親子関係を運ぶ入れ物)がスレッドや非同期境界を越える瞬間に弱いです。span が途切れると、Trace が途中でぶつ切りになり「親のないトレース」 が大量に発生します。

取りこぼす場面 なぜ切れるか 回避の方針
自前のスレッドプール / ExecutorService Context は ThreadLocal 系で伝播するため、別スレッドへ submit した時点で親 span を見失う Java なら Context.taskWrapping(executor) でラップ、Python なら opentelemetry-instrumentation-threading を有効化する
fire-and-forget(投げっぱなしの非同期) 親 span が子の開始前に終了し、子 span が孤児化する/時系列がずれる バックグラウンド処理を別 Trace(span link 付き)として明示的に開始する
メッセージキュー(Kafka / SQS / RabbitMQ) producer と consumer はプロセスもタイミングも別。trace context をメッセージヘッダに乗せないと繋がらない 対応 instrumentation を使い、自前送受信なら traceparent ヘッダを inject / extract する
gRPC のストリーミング、WebSocket 単発 RPC は取れるが、長命ストリームは 1 本の span で表現しづらく中身が見えない メッセージ単位で手書き span を起こすか、メトリクスで補完する
マイナーな / 自作の DB ドライバ・HTTP クライアント instrumentation が用意されていないライブラリは丸ごと無視される 対応一覧を確認し、無ければ呼び出しを手書き span で囲む

現象 → 原因 → 確認 → 回避 で 1 例を具体化します。Java + 独自スレッドプールでよくあるケースです。

現象

HTTP ハンドラの Trace は出るのに、その中で executor.submit() した重い集計処理の span が Trace に現れない。集計処理側は「ルート span から始まる別 Trace」 として孤立して見える。

原因

OTel Java SDK の Context は ThreadLocal で伝播する。別スレッドに処理を渡した瞬間、そのスレッドの ThreadLocal は空なので親 span を引き継げない。エージェントは多くのフレームワークを自動でラップするが、アプリが new ThreadPoolExecutor() で自作したプールまでは面倒を見ない。

確認

バックエンドで該当サービスの「Trace 数」 が HTTP リクエスト数より明らかに多い、かつ平均 span 数が極端に少ない Trace が大量にあれば孤児 span のサイン。Jaeger なら親 span 名が空の Trace を検索する。

回避

プール生成箇所を Context.taskWrapping(executor) で包む。これで submit 時点の Context が実行スレッドへ運ばれ、親子が繋がる。二重ラップ(タスク側でも wrap)は重複の原因になるので片方だけにする。

つまり Auto-Instrumentation は「7〜8 割を無料で取り、残り 2〜3 割の非同期境界とドメイン処理を人間が補う」 という分業で考えるのが実務的です。

Collector のメモリ枯渇を防ぐ

OTel を本番投入して最初に踏むトラブルの定番が Collector の OOM(メモリ枯渇) です。とくに tail_sampling を有効にした Gateway Collector は、判定が終わるまで Trace をメモリに溜め込むため、設定を誤ると数分で落ちます。

現象

Collector が定期的に再起動し、再起動直前に span のドロップが急増。kubectl describe podOOMKilled、Reason: Error が記録される。バックエンド側ではトレースが歯抜けになる。

原因

tail_sampling は num_traces 件の Trace を decision_wait の間メモリ上の循環バッファに保持する。トラフィックが想定を超えると保持中の Trace データだけで heap を食い尽くす。memory_limiter 未設定だとバックプレッシャがかからず一直線に OOM へ向かう。

確認

Collector 自身のメトリクス(self-telemetry)で otelcol_processor_tail_sampling_count_traces_sampledGo ランタイムの otelcol_process_runtime_heap_alloc_bytes を Grafana で観察。コンテナの RSS が limit に張り付いていれば確定。

回避

memory_limiter をパイプライン先頭に置き、num_traces を実トラフィックに合わせて下げ、Gateway を水平スケールする。後述の YAML を参照。

メモリ消費のざっくり試算

tail_sampling の保持メモリは、おおまかに次の式で見積もれます。

保持メモリ ≒ num_traces × Trace あたりの平均 span 数 × span あたりのおおよそのバイト数

たとえば num_traces: 100000、1 Trace あたり平均 20 span、1 span がデコード後におよそ 1〜2KB だとすると、保持中の Trace データだけで 2〜4GB 規模になります。これに Receiver のバッファや Go ランタイムのオーバーヘッド(プロセス全体は heap よりおよそ 50MiB 程度高め)が乗ります。num_traces の既定値は 50,000 ですが、span が太い・Trace が長命なサービスでは既定でも数 GB に達する点に注意してください。

memory_limiter + tail_sampling の実 YAML

memory_limiter は パイプラインの最初のプロセッサに置くのが鉄則です。これにより、ソフトリミット超過時のバックプレッシャが Receiver まで届き、データ損失を最小化できます。

processors:
  # 1) 必ず先頭に置く。limit_mib はコンテナ memory request の 70〜80% を目安に
  memory_limiter:
    check_interval: 1s        # 短いほどスパイクに追従しやすい
    limit_mib: 4000           # ハードリミット(これを超えると強制 GC)
    spike_limit_mib: 800      # ソフトリミット = 4000 - 800 = 3200。limit の 20% が目安

  # 2) tail_sampling。Gateway Collector 側に置く
  tail_sampling:
    decision_wait: 10s            # この秒数 Trace を保持してから判定。長いほどメモリを食う
    num_traces: 50000             # 同時保持する Trace 上限(循環バッファ)
    expected_new_traces_per_sec: 2000  # 内部バッファ最適化のヒント
    policies:
      - name: errors              # エラーは必ず残す
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: slow                # 遅いリクエストも残す
        type: latency
        latency:
          threshold_ms: 1000
      - name: baseline            # それ以外は 5% だけ
        type: probabilistic
        probabilistic:
          sampling_percentage: 5

  # 3) 送信効率化。既定は send_batch_size: 8192 / timeout: 200ms
  batch:
    timeout: 5s
    send_batch_size: 8192

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, tail_sampling, batch]
      exporters: [otlp/backend]

memory_limiter の挙動は 2 段階です。ソフトリミット超過で Receiver にエラーを返してデータ受信を拒否(=リトライとバックプレッシャ)、ハードリミット超過でさらに強制 GC を実行します。これで「落ちる代わりに一部を捨てる」 という安全側の挙動になります。

tail_sampling を使う際の運用ポイントは、同一 Trace の全 span が同じ Collector インスタンスに届く必要があることです。Gateway を複数台に並べる場合は、手前に loadbalancingexporter を置いて Trace ID 単位でルーティングしないと、判定が分散して正しく動きません。これを忘れて単純にラウンドロビン負荷分散すると、エラー Trace の一部だけが残る、という分かりにくい不具合になります。

サンプリング戦略の選び分け

本番で全 Trace を送ると、データ量で料金が破綻し、ネットワーク帯域も圧迫します。サンプリングは必須です。Head-based と Tail-based の使い分けが核心です。

方式 判定タイミング 長所 短所 / コスト
Head-based リクエスト先頭。SDK 側で確率的に決める 実装がシンプル。Collector のメモリをほぼ使わない。送信量も最初から減る 内容を見る前に決めるため、重要なエラー Trace を捨てるリスク
Tail-based Trace 完了後。Collector が内容を見て決める エラー 100% / 高レイテンシ 100% など内容ベースの精緻な選別ができる 全 Trace を一時保持するため Collector メモリを大きく消費。前述の OOM リスク

現代的な定石は 「Tail-based を基本に、エラーと高レイテンシは 100%、それ以外は数 % だけ残す」 です。サンプリング率の典型レンジは ベースライン 1〜10%、エラーは 100% です。

コスト感も持っておきましょう。Trace 1 件あたりおよそ 1〜10KB として、毎秒 5,000 リクエスト・1 件 5KB なら無サンプリングで月およそ 60TB 規模になります。Datadog や New Relic のような従量課金 SaaS では、ベースラインのサンプリング率を 10% から 1% に落とすだけでトレース取り込み費用がおよそ 1 桁変わるため、月数十万円単位の差が出ることも珍しくありません。ただしエラー Trace は安価に 100% 残せるので、「正常系は薄く、異常系は厚く」 が費用対効果の最適点になります。

計装オーバーヘッドの目安

「計装で遅くならないか」 は必ず聞かれる点です。結論は 通常は無視できるですが、内訳を分けて理解しておくと判断を誤りません。

アプリ内 SDK の処理

span の生成・属性付与・バッファへの enqueue は、1 リクエストあたりおおむね 1〜5ms 程度に収まることが多い。span を非同期にバッチ送信する BatchSpanProcessor(既定で別スレッド送信)を使う限り、リクエストの応答時間にネットワーク送信は乗らない。

同期エクスポートは厳禁

SimpleSpanProcessor は span ごとに同期送信するため、バックエンドのレイテンシがそのままリクエストに乗る。本番では必ず BatchSpanProcessor を使う。これがオーバーヘッド事故の最頻原因。

属性の付けすぎ

span に高カーディナリティな属性(ユーザー ID、生 URL、巨大ペイロード)を大量に付けると、CPU とメモリ、そして後段の保存費用が膨らむ。属性は「調査に効くもの」 に絞る。

Lambda などサーバレス

AWS Lambda OpenTelemetry Layer は初期化分だけ cold start を数百 ms 増やすことがある。warm では小さい。最小限の instrumentation に絞り、レイヤサイズと初期化処理を軽くするのがコツ。

Collector 側の送信効率は batch プロセッサが握ります。既定は send_batch_size: 8192(この件数に達したら送信)・timeout: 200ms(達しなくてもこの時間で送信)です。timeout0s にすると即時送信になり send_batch_size は無視されるため、効率を取りたい本番では数秒程度の timeout を設定するのが定石です。

主要バックエンドとの連携パターン

OTel のデータを送る先は用途で選び分けます。Collector が間にいるため、複数バックエンドへの同時送信や「本番は Datadog、開発は Jaeger」 のような使い分けが容易です。

バックエンド 特徴 典型用途
Datadog 商用 SaaS、UI が最も洗練、Trace / Metric / Log / RUM / Synthetics 統合 エンタープライズ、運用を丸ごと任せたい
New Relic 商用 SaaS、データ量課金、AI による異常検知が強い 大規模プロダクション
Grafana スタック(Tempo / Loki / Mimir / Prometheus) OSS、自前ホスト、UI は Grafana で統一 コスト最適化、データを自社で持ちたい組織
Jaeger(Trace 専用) OSS、Trace に特化、軽量 Trace だけ見たいシンプルな構成
AWS X-Ray AWS マネージド、Lambda / ECS / API Gateway とのネイティブ連携 AWS 中心のスタック
Honeycomb / Lightstep 高カーディナリティに強く、深い分析が得意 複雑なマイクロサービス環境

Collector が Docker コンテナKubernetes の DaemonSet / Deployment として動くため、API サーバ群とは独立してスケールできるのも利点です。

OTel が今、必須に近い理由

「まだ Datadog Agent でいいのでは?」 という疑問への答えです。

ベンダーの戦略的選択

Datadog / New Relic / Grafana など主要ベンダーが OTel をネイティブ対応している。独自エージェントを段階的に OTel SDK へ置き換える流れが進んでいる。

マルチクラウド時代

AWS と GCP と Azure を併用し、マネージドサービスと自前運用が混在する現代では単一ベンダーで全部監視するのが難しい。OTel の標準化がマルチクラウド観測性の現実解。

OSS の充実

Grafana / Jaeger / Prometheus / Loki などの OSS スタックが成熟し、自前ホストでも商用 SaaS に近い体験が可能に。コスト最適化で OSS を選ぶ組織が増えている。

AI / LLM の観測ニーズ

LLM アプリの監視で、プロンプト・レスポンス・トークン使用量・コストを観測したいニーズが急増。OTel に GenAI セマンティック規約が追加され、LLM 呼び出しを標準的に観測する仕組みが整いつつある。

OpenTelemetry に関するよくある質問

Q. Datadog Agent を使っているけど、OTel に移行する価値はありますか?

A. 中長期で見れば価値が大きいです。短期的には Agent で十分動いていますが、ベンダー変更・マルチクラウド対応・OSS 観測スタックの併用といった将来課題で OTel の標準化が効きます。新規サービスから OTel SDK で書き、既存は段階的に置き換えるのが現実的なルートです。Datadog は OTLP もネイティブ受信できます。

Q. Auto-Instrumentation だけで十分ですか?

A. 7〜8 割のユースケースで十分です。HTTP リクエスト・DB クエリ・外部 API 呼び出しなど主要ライブラリの計装は自動で入ります。一方で独自スレッドプール・fire-and-forget・メッセージキューの非同期境界・自作ドライバでは Trace が途切れます。これらと、ビジネスロジック固有の処理だけ手書き span で補うと労力対効果が高い計装になります。

Q. Collector のメモリが足りず OOM で落ちます。何から直せばいいですか?

A. まず memory_limiter をパイプラインの先頭に追加してください(check_interval: 1slimit_mibコンテナ memory request の 7〜8 割)。次に tail_sampling の num_traces を実トラフィックに合わせて下げます。目安は「num_traces × 平均 span 数 × 約 1〜2KB」 が保持メモリ。Collector の otelcol_process_runtime_heap_alloc_bytes監視し、張り付くなら Gateway を水平スケール(手前に loadbalancingexporter)します。

Q. tail_sampling と head sampling、どちらを使うべきですか?

A. エラーや遅い Trace を確実に残したいなら tail_samplingです。完了後に内容を見て選別できるためです。代償として Collector が全 Trace を一時保持しメモリを食います。逆に送信量とコストを単純に減らしたいだけなら、SDK 側の head sampling(確率的)が軽量です。実務では「tail で エラー/高レイテンシ 100% + ベースライン 1〜10%」 が定番です。

Q. Collector って絶対に必要ですか?

A. 推奨だが必須ではありません。SDK から直接バックエンドに送る構成も可能です。ただしバックエンド切り替え・サンプリング・機密情報マスキング・障害時のバッファリングなどは Collector があると運用上のメリットが大きいです。最初は SDK 直送で始め、tail_sampling やマスキングが要る段階で Collector を挟むのが現実的です。

Q. パフォーマンス影響はどれくらいですか?

A. 通常は無視できる程度で、SDK の処理はおおむね 1〜5ms / リクエストです。前提として BatchSpanProcessor(既定で別スレッド送信)を使うこと。SimpleSpanProcessor による同期送信はバックエンドのレイテンシをそのままリクエストに乗せるので本番では使いません。属性の付けすぎも CPU と費用を膨らませます。

Q. AWS Lambda で使えますか?

A. 使えますAWS が公式に AWS Lambda OpenTelemetry Layer を提供しています。Layer を Lambda に追加し環境変数で送信先を指定すれば、自動計装と X-Ray / Datadog などへの送信が可能です。初期化分だけ cold start が数百 ms 増えることがあるため、必要な instrumentation に絞った軽量設定で始めるのがコツです。

Q. ローカル開発で OTel を試すには?

A. Jaeger を Docker で立てるのが最も手軽です。docker run -p 4317:4317 -p 16686:16686 jaegertracing/all-in-one で起動し、ブラウザで http://localhost:16686 を開けば UI が見えます。本番は Datadog、開発は Jaeger という構成が、コストをかけずに開発で観測性を試す現実的なパターンです。

まとめ

OpenTelemetry は観測性のデファクトスタンダードとして、現代の Web / API 開発で必須に近い技術になっています。「SDK で計装 → Collector で集約 → 任意のバックエンドで可視化」 という分離構造が、ベンダー変更や OSS 移行の自由度を生むのが本質的価値です。

ただし入門の先には実運用の壁があります。Auto-Instrumentation は非同期境界で Trace を取りこぼし、tail_sampling は num_traces 次第で Collector を OOM させ、サンプリング率は費用を 1 桁単位で動かします。memory_limiter を先頭に置く、num_traces をトラフィックに合わせる、エラーは 100% で正常系は薄く残す、BatchSpanProcessor を使う、という勘所を押さえれば、観測性が業務を救うフェーズで困らないインフラを整えられます。今は Datadog で十分という組織でも、計装を OTel 標準にしておくだけで将来の選択肢が大きく広がります。

参考リンク

あとで見返すならここで保存

読み終わったあとに残しておきたい記事は、お気に入りからまとめて辿れます。