# OpenTelemetry によるテイルサンプリング：なぜ有用か、どう実装するか、何を考慮すべきか

LLMS index: [llms.txt](/llms.txt)

---

テイルサンプリングは、オブザーバビリティのコストを抑えつつ、分散システムの問題を特定するのに役立ちます。
この記事では、OpenTelemetry Collector を使用したテイルサンプリングの実装方法を説明します。
また、サンプリング戦略を策定する際に考慮すべき、一般的な懸念点と OpenTelemetry 固有の懸念点についても紹介します。

## サンプリングとは何か、なぜ行うべきか {#what-is-sampling-and-why-should-you-do-it}

分散トレーシングでは、分散システム内のあるサービスから別のサービスへ移動するリクエストを観測できます。
サービス間の接続の理解やレイテンシーの問題の診断など、多くの理由で非常に実用的です。
分散トレーシングが初めてのトピックであれば、分散トレーシングとサンプリングに関するこちらの記事を必ず読んでください。

しかし、リクエストの大部分が HTTP 200 で成功し、レイテンシーやエラーなく完了しているなら、そのすべてのデータが本当に必要でしょうか。
重要なのは、適切なインサイトを得るために大量のデータが常に必要というわけではないということです。
_必要なのは、適切にサンプリングされたデータです。_

![すべてのデータをトレースする必要はなく、データのサンプルで十分であることを示す図](traces-venn-diagram.png)

サンプリングの背景にある考え方は、オブザーバビリティバックエンドに送信するスパンを制御し、取り込みコストを削減することです。
組織ごとにサンプリングしたい理由だけでなく、_何を_ サンプリングしたいかについても独自の理由があります。
サンプリング戦略をカスタマイズする理由には、以下のようなものがあります。

- **コスト管理**: すべてのスパンをエクスポートして保存すると、関連するクラウドプロバイダーやベンダーから高額な請求が発生するリスクがあります。
- **興味のあるトレースへの集中**: たとえば、フロントエンドチームは特定のユーザー属性を持つトレースだけを見たい場合があります。
- **ノイズのフィルタリング**: たとえば、ヘルスチェックをフィルタリングしたい場合があります。

## テイルベースサンプリングとは {#what-is-tail-based-sampling}

テイルベースサンプリングでは、リクエスト内のすべてのスパンが完了した _後に_ トレースをサンプリングするかどうかを決定します。
これは、ルートスパンの処理が始まるリクエストの _最初に_ 決定が行われるヘッドベースサンプリングとは対照的です。
テイルベースサンプリングでは、特定の条件に基づいてトレースをフィルタリングできますが、ヘッドベースサンプリングではこれはできません。

![スパンがルートスパンから生成される様子を示す図。スパンが完了した後、テイルサンプリングプロセッサーがサンプリングの判断を行います。](tail-sampling-process.png)

テイルサンプリングにより、関心のあるトレースだけを確認できます。
また、トレースのうちあらかじめ決められたサブセットのみをエクスポートするため、データの取り込みと保存のコストも削減できます。
たとえば、アプリケーション開発者として、デバッグのためにエラーやレイテンシーのあるトレースだけに関心がある場合があります。

## OpenTelemetry Collector でテイルサンプリングを実装する方法 {#how-to-implement-tail-sampling-in-the-opentelemetry-collector}

OpenTelemetry でテイルサンプリングを使用するには、[テイルサンプリングプロセッサー](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/635d4254a3018eb3ca8f1736e71fcb54f8ed6e5a/processor/tailsamplingprocessor?from_branch=main)というコンポーネントを実装する必要があります。
このコンポーネントは、選択して定義できる一連のポリシーに基づいてトレースをサンプリングします。
**まず、すべてのスパンを確実にキャプチャするために、SDK ではデフォルトのサンプラーまたは AlwaysOn サンプラーを使用してください。**

それでは、Collector 設定ファイルの `processors` セクションに配置される、テイルサンプリングプロセッサーのサンプル設定を見ていきましょう。

```yaml
processors:
  tail_sampling:
    decision_wait: 10s
    num_traces: 100
    expected_new_traces_per_sec: 10
    policies:
      [
        {
          name: errors-policy,
          type: status_code,
          status_code: { status_codes: [ERROR] },
        },
        {
          name: randomized-policy,
          type: probabilistic,
          probabilistic: { sampling_percentage: 25 },
        },
      ]
```

- `tail_sampling` は、テイルサンプリングを実装するために使用するプロセッサーの名前です。
- 最初の 3 行はオプションの設定項目です。
  - `decision_wait` は、トレースの最初のスパンが作成されてからサンプリング判断が行われるまでの時間（秒）です。
    デフォルトは 30 秒です。
  - `num_traces` は、メモリに保持されるトレースの数です。
    デフォルトは 50,000 です。
  - `expected_new_traces_per_sec` は、予想される新しいトレースの数で、データ構造の割り当てに役立ちます。
    デフォルトは 0 です。
- `policies` は、サンプリングポリシーを定義する場所です。
  デフォルトはなく、プロセッサー設定で唯一必須のセクションです。
  この場合、2 つのポリシーが定義されています。
  - `status_code` は、この例ではステータスコード `ERROR` のトレースをフィルタリングするため、`errors-policy` と名付けられています。
  - `probabilistic` は、`randomized-policy` と名付けられています。
    エラーのあるすべてのトレースをフィルタリングすることに加えて、エラーのないトレースの 25% もランダムにサンプリングされます。

次の画像は、このサンプル設定を実装した場合にバックエンドで表示される可能性のある例です。

![エラーのあるトレースとランダムに選択されたトレースの両方に基づくサンプリングを示す図](backend-visual-errors-and-random.png)

右側の青い点と四角形は、リクエストのすべてのスパンが完了したトレースの終わりにサンプリング判断が行われることを示しています。
緑の点はサンプリングされたスパンを、灰色の点はサンプリングされなかったスパンを表しています。
赤い点はエラーが検出されたスパンを表しています。
この設定では、エラーのあるすべてのトレースと、設定したレートに基づく他のトレースのランダムサンプリングが取得されます。

エラーなどの特定のフィルターのみに基づいてサンプリングしたい場合は、probabilistic ポリシーを削除できます。
しかし、他のすべてのトレースのランダムサンプリングを行うことで、他の問題を浮き彫りにし、ソフトウェアのパフォーマンスと動作のより広い視点を得ることができます。
ステータスコードポリシーのみを定義した場合の表示は以下のとおりです。

![ランダムなトレースを含まず、エラーのみに基づくテイルサンプリングの例を示す図](backend-visual-errors-only.png)

他のポリシーを追加する柔軟性もあります。
以下にいくつかの例を示します。

- `always_sample`: すべてのトレースをサンプリングします。
- `latency`: トレースの所要時間に基づいてサンプリングします。
  たとえば、5 秒以上かかるすべてのトレースをサンプリングできます。
- `string_attribute`: 文字列属性値に基づいてサンプリングします。
  完全一致と正規表現による値のマッチングの両方がサポートされています。
  たとえば、特定のカスタム属性値に基づいてサンプリングできます。

## テイルサンプリングの潜在的な問題 {#potential-issues-with-tail-sampling}

- **コストの予測困難性**: サンプリングは一般的にデータの取り込みと保存のコスト管理に役立ちますが、まれにアクティビティのスパイクが発生することがあります。
  たとえば、レイテンシーのあるトレースをサンプリングしていて、深刻なネットワーク輻輳が発生した場合、トレーシングソリューションはレイテンシーの問題を持つ大量のトレースをエクスポートし、この期間中にコストの予期しないスパイクにつながります。
  しかし、これはまさにテイルサンプリングが存在する理由でもあります。こうした問題を素早く把握し、対処できるようにするためです。
- **パフォーマンス**: テレメトリーデータをローカルに保存している場合、サンプリング判断が完了するまでスパンを保存する必要があります。
  ローカルに保存する場合はアプリケーションのリソースを消費し、そうでない場合は追加のネットワーク帯域幅を消費する可能性があります。
- **適切なポリシーの策定**: また、適切なインサイトを得るために大量のデータが必ずしも必要ではありませんが、適切なサンプリングは必要であり、それを策定するのは困難な場合があります。
  正常なリクエストのベースラインはどのようなものか、テイルサンプリングのためにどれだけのリソースを確保できるか、など多くの質問をする必要があります。
  そうしないと、システムの問題を明らかにできるはずのリクエストをうっかりフィルタリングしてしまったり、当初の想定以上のリソースを消費してしまう可能性があります。
- **テイルサンプリングの待機時間の設定**: テイルサンプリングのもう 1 つの課題は、トレースが実際にいつ完了するかを予測するのが難しいことです。
  トレースは本質的に子スパンが親を参照するスパンのグラフであるため、新しいスパンはいつでも追加される可能性があります。
  これを解決するために、サンプリング判断を行う前に待機する許容時間を設定できます。
  ここでの前提は、トレースが設定した時間内に完了するということですが、その時間枠外に完了する興味深いスパンを失うリスクがあり、断片化されたトレースにつながる可能性があります。
  断片化されたトレースは、スパンが欠落し、可視性にギャップが生じることで発生します。

## OpenTelemetry の制限事項 {#limitations-of-opentelemetry}

OpenTelemetry に関連する考慮すべき制限事項もいくつかあります。
これらの制限事項の一部は、OpenTelemetry だけでなく、クライアントホスト型のテイルベースサンプリングソリューション全般に広く当てはまることに注意してください。

まず、Collector を立ち上げる必要があります。
Collector は設定の一元化やデータの処理という点で最終的に非常に実用的ですが、システムに実装と保守が必要なもう 1 つのコンポーネントです。
さらに、テイルサンプリングが機能するためには、**特定のトレースのすべてのスパンが同じ Collector で処理される必要があり**、スケーラビリティの課題につながります。

シンプルな構成では、1 つの Collector で十分であり、ロードバランシングは不要です。
しかし、メモリに保持されるリクエストが増えるほど、より多くのメモリが必要になり、各スパン属性を確認するための追加の処理能力とコンピューティングパワーも必要になります。
**システムが成長するにつれて、1 つの Collector だけではすべてを処理できなくなるため、[Collector のデプロイメントパターン](https://www.youtube.com/watch?v=WhRrwSHDBFs)とロードバランシングについて考える必要があります。**

1 つの Collector では不十分なため、Collector をエージェント・コレクター構成でデプロイする 2 層構成を実装する必要があります。
また、各 Collector は受信するトレースの完全なビューを持つ必要があります。
つまり、同じトレース ID を持つすべてのスパンが同じ Collector インスタンスに送られる必要があり、そうでなければ断片化されたトレースになってしまいます。
テイルサンプリングプロセッサーを使用して複数のエージェント/コレクターインスタンスを実行している場合は、[ロードバランシングエクスポーター](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/8d0362c4a63f6b4e6f106f007977b6d75e4bd272/exporter/loadbalancingexporter/README.md?from_branch=main)を使用できます。
このエクスポーターは、同じトレース ID を持つすべてのスパンが同じエージェント/コレクターに到達することを保証します。
ただし、このエクスポーターの実装には、設定などの追加のオーバーヘッドが発生する場合があります。

考慮すべきもう 1 つの制限事項は、OpenTelemetry はバックエンドがカウントを再重み付けできるようなメタデータ（たとえば P95、P99、イベント合計数）を伝搬しないため、**サンプリングされたデータの測定値しか得られず、*すべての* データに関する正確な測定値は得られない**ということです。
サンプラーをすべてのトレースの 25% を保持するように設定したとしましょう。
バックエンドが全データの 25% でしか動作していないことを知らなければ、生成されるすべての測定値は不正確になります。
これを回避する 1 つの方法は、サンプルレートをバックエンドに伝えるメタデータをスパンに付与することで、バックエンドが特定の期間のスパン合計数などを正確に測定できるようにすることです。
Sampling SIG は現在このコンセプトに取り組んでいます。

最後に、OpenTelemetry はまだ進化中のプロジェクトであるため、テイルサンプリングプロセッサーや Collector を含む多くのコンポーネントが活発に開発されています。
テイルサンプリングプロセッサーについては、[collector-contrib リポジトリ](https://github.com/open-telemetry/opentelemetry-collector-contrib)に、このプロセッサーの将来について議論する[オープンイシュー](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/1797)があり、イベントの連鎖が明確に定義され理解されるように、個別のプロセッサーに置き換えることを中心に議論されています。
コミュニティが解決しようとしている主な問題の 1 つは、テイルサンプリングを個別のプロセッサーで行う方がテイルサンプリングプロセッサー単体よりもパフォーマンスが高いかどうかです。
後方互換性のあるソリューションなしには何もリリースされないことに注意してください。
Collector については、Collector を監視するための選択肢が限られており、これはトラブルシューティングにおいて重要です。
詳細については、[Collector のトラブルシューティング](/docs/collector/troubleshooting/)を参照してください。

_この記事のあるバージョンは、もともと New Relic のブログに[掲載されたもの][originally posted]です。_

[originally posted]: <https://newrelic.com/blog/best-practices/open-telemetry-tail-sampling>
