> Source: https://www.ymotongpoo.com/works/oteps/trace/otep-0006/


# OTEP-0006: サンプリングAPI

## TL;DR {#tldr}

このセクションでは、このRFCで提案されているすべての変更点を要約します。

1. `Sampler` インターフェースをAPIからSDKパッケージに移動します。
   `Sampler` APIにいくつかの小さな変更を加えます。
2. `Span` の作成時にサンプリング判断に使用できる `Attributes` を記録する機能を追加します。
3. `Span` インターフェースから `addLink` APIを削除し、リンクの記録はスパンの構築時のみ許可します。

## 動機 {#motivation}

ライブラリ開発者、パッケージ化されたインフラストラクチャバイナリの開発者、アプリケーション開発者、オペレーター、テレメトリシステムの所有者など、OpenTelemetryのさまざまな利用者は、それぞれ異なるユースケースを持っていますが、これらは元のサンプリングAPIの設計の中で混同されてしまっていました。
したがって、それぞれがどのAPIに依存すべきかを明確にし、自分たちのニーズに応じてサンプリングとOpenTelemetryをどのように設定するかを明らかにする必要があります。

```

                    +----------+           +-----------+
           grpc     |  Library |           |           |
           Django   |  Devs    +---------->| OTel API  |
           Express  |          |   +------>|           |
                    +----------+   |  +--->+-----------+                  +---------+
                                   |  |          ^                        | OTel    |
                                   |  |          |                     +->| Proxy   +---+
                                   |  |          |                     |  |         |   |
                    +----------+   |  |    +-----+-----+------------+  |  +---------+   |
                    |          |   |  |    |           | OTel Wire  |  |                |
           Hbase    |  Infra   |   |  |    |           | Export     |+-+                v
           Envoy    |  Binary  +---+  |    |  OTel     |            |  |           +----v-----+
                    |  Devs    |      |    |  SDK      +------------+  |           |          |
                    +----------+---------->|           |            |  +---------->|  Backend |
                                   +------>|           | Custom     |  +---------->|          |
                                   |  |    |           | Export     |  |           +----------+
                    +----------+   |  |    |           |            |+-+             ^
                    |          +---+  |    +-----------+------------+                |
                    |  App     +------+       ^              ^                       |
                    |  Devs    +              |              |          +------------+-+
                    |          |              |              |          |              |
                    +----------+          +---+----+         +----------+   Telemetry  |
                                          |  SRE   |                    |   Owner      |
                                          |        |                    |              |
                                          +--------+                    +--------------+
                                                                          Lightstep
                                                                          Honeycomb

```

## 解説 {#explanation}

ここでは（重複する場合もある）5つの異なるユースケースを概説し、それぞれがOpenTelemetryとどのように関わるべきかを説明します。

### ライブラリ開発者 {#library-developer}

例: gRPC、Express、Djangoの開発者。

* OpenTelemetry APIにのみ依存しなければならず（MUST）、SDKに依存してはいけません。
  * テストのためだけであれば、InMemoryExporterを使ったSDKに依存してもよいです（MAY）。
* 他者のアプリケーションにリンクされるソースコードを配布しています。
* アプリケーションに対する明示的なランタイム制御を持ちません。
* どのトレースが興味深いか（たとえば異常なコントロールプレーンのリクエスト）、あるいは興味深くないか（たとえばヘルスチェック）についてある程度のシグナルは把握していますが、完全に汎用的なコードを書かなければなりません。

**解決策:**

* 現時点では、OpenTelemetry APIは最後のユースケースに対して `SamplingHint` 機能を提供しません。
  これは時期尚早な最適化を避けるための意図的な判断であり、APIを変更することが新しいAPIを追加することに比べて後方互換性を損なうという事実に基づいています。

### インフラストラクチャパッケージ/バイナリ開発者 {#infrastructure-packagebinary-developer}

例: HBase、Envoyの開発者。

* YAMLや類似のランタイム設定を受け付けるかもしれない自己完結型のバイナリを配布していますが、デフォルトのOpenTelemetry SDK、OpenTelemetry SDKTracer、OpenTelemetryワイヤーフォーマットエクスポーターを超える拡張性やプラグインのサポートは求められていません。
* サンプリングレートについて独自の推奨事項を持っているかもしれませんが、本番環境でバイナリを実行するわけではなく、パッケージ化されたバイナリを提供するだけです。
  そのため、サンプリングレートの設定とサンプリング戦略は、OpenTelemetryのSDKが提供する有限の「組み込み」セットである必要があります。
* 自分たちを呼び出すサービスによって行われた上流のサンプリング判断に対処する必要があります。

**解決策:**

* OpenTelemetry SDKにおいてデフォルトで異なるサンプリング戦略を許可し、それらすべてをYAMLやフィーチャーフラグを通じて簡単に設定できるようにします。
  [デフォルトサンプラー](#default-samplers)を参照してください。

### アプリケーション開発者 {#application-developer}

これは、OpenTelemetry全般において私たちが最も重視してきた人々です。

* OpenTelemetryの実装やSDKの設定に対して完全な制御権を持っています。
  SDKを使用する際には、カスタムエクスポーター、カスタムコード/サンプラーなどを設定できます。
* フィーチャーフラグの組み込みやYAMLファイルの読み込みなど、さまざまな手段でランタイム設定を実装することを選択でき、あるいはコード内でライブラリを設定することさえできます。
* gRPCやDjangoなど、自分たちが利用するライブラリが提供する範囲を超えて、アプリケーション固有の挙動を計装するためにOpenTelemetryを多用します。

**解決策:**

* アプリケーション開発者が公式SDKを使用する際に、カスタムサンプラーをリンクしたり、独自に作成したりできるようにします。
  * これには、目標のレートを達成するためのフィールド単位の動的サンプリングが含まれるかもしれません（たとえば <https://github.com/honeycombio/dynsampler-go>）。
* サンプリングの判断はスパン開始操作の中で行われます。
  これは、スパンに関連する属性がSpan開始操作に追加された後、かつ具体的なSpanオブジェクトが存在する前に行われます（これにより、サンプラーの判断に応じてNoOpSpanを作成することも、実際のSpanインスタンスを生成することも可能になります）。
* スパンがNoOpスパンである場合にコストの高いスパン属性/ログの計算をスキップできるようにするために、Span.IsRecording() が必要です。

### アプリケーションオペレーター {#application-operator}

多くの場合、アプリケーション開発者と同じ人々ですが、必ずしもそうとは限りません。

* 運用上のニーズ、デバッグ、コストを満たすためにサンプリングレートと戦略を調整することに関心があります。

**解決策:**

* アプリケーション開発者が作成した設定ファイルやフィーチャーフラグを使って、アプリケーションのサンプリングロジックを制御します。
* 設定ファイルを使って、ライブラリやインフラストラクチャパッケージの挙動を設定します。

### テレメトリインフラストラクチャの所有者 {#telemetry-infrastructure-owner}

彼らは、カスタムの `Exporter` や `Sampler`、フックなどを備えたSDKを使用するか、独自の実装を書くことによってOpenTelemetry APIの実装を提供する人々であり、エクスポートされたトレースを収集するためのインフラストラクチャを運用してもいます。

* 効率性、コスト効率、そして自分たちにとって意味のある方法でスパンを収集できることなど、さまざまな事柄に関心があります。

**解決策:**

* インフラストラクチャの所有者は、サンプリングフックがすでに実行された後にスパンに付加された情報を受け取ります。

## 内部の詳細 {#internal-details}

Dapperベースのシステム（あるいはサンプリング判断を遅延させないシステム）では、エクスポートされたすべてのスパンがバックエンドに保存されるため、これらのシステムの一部は通常、大量のトレースに対してスケールしないか、すべてのスパンを保存するコストが高すぎる場合があります。
このユースケースをサポートし、送信するデータの品質を確保するために、OpenTelemetryはいくつかの要件を伴ってネイティブにサンプリングをサポートする必要があります。

* できるだけ多くの完全なトレースを送信します。
  トレースの一部のスパンだけを送信すると、スパン間の相互作用が失われる可能性があるため、有用性が低くなります。
* アプリケーションオペレーターがサンプリング頻度を設定できるようにします。

すべてのスパンを収集し、後でサンプリングの判断を遅延させるかどうかを決める必要がある新しい現代的なシステムのために、OpenTelemetryはライブラリを設定してすべてのスパンを収集・エクスポートする方法をネイティブにサポートする必要があります。
これは（OpenTelemetryがサンプリングをサポートしているにもかかわらず）、常にすべてのスパンを収集するようにデフォルト設定を行うことで可能です。

### サンプリングフラグ {#sampling-flags}

OpenTelemetry APIには2つのフラグ/プロパティがあります。

* `RecordEvents`
  * このプロパティは `Span` インターフェースで公開されます（たとえば `Span.isRecordingEvents()`）。
  * `true` の場合、現在の `Span` はトレーシングイベント（属性、イベント、ステータスなど）を記録し、そうでない場合はすべてのトレーシングイベントが破棄されます。
  * ユーザーはこのプロパティを使って、コストの高いトレースイベントを回避できるかどうかを判断できます。
* `SampledFlag`
  * このフラグは `TraceOptions` を介して子Spanに伝播されます（たとえば `TraceOptions.isSampled()`）。
    詳細は[こちら][trace-flags]のW3Cの定義を参照してください。
  * Dapperベースのシステムでは、これは `Span` が `sampled` としてエクスポートされることと同等です。

`SampledFlag == false` かつ `RecordEvents == true` というフラグの組み合わせは、現在の `Span` はトレーシングイベントを記録するものの、子 `Span` はおそらく記録しないことを意味します。
この組み合わせが必要なのは次の理由からです。

* ユーザーが個々のSpanの記録を制御できるようにするため。
* OpenCensusはz-pagesをサポートするためにこれを持っているため、後方互換性を維持する必要があるため。

`SampledFlag == true` かつ `RecordEvents == false` というフラグの組み合わせは、分散トレースにギャップを生じさせる可能性があるため、OpenTelemetry APIはこの組み合わせを許可すべきではありません（SHOULD NOT）。

APIのユーザーは、コードを計装する際には `RecordEvents` プロパティのみにアクセスすべきであり（SHOULD）、コンテキストプロパゲーターで使用する場合を除いて `SampledFlag` にアクセスすべきではないと考えて差し支えありません。

### サンプラーインターフェース {#sampler-interface}

OpenTelemetry SDKでのみ利用可能なSamplerクラスのインターフェースです。

* `TraceID`
* `SpanID`
* 存在する場合は親の `SpanContext`
* `Links`
* スパン名
* `SpanKind`
* 構築中の `Span` の初期の `Attributes` セット

これは `SamplingResult` と呼ばれる出力を生成し、以下を含みます。

* `SamplingDecision` 列挙型 [`NOT_RECORD`, `RECORD`, `RECORD_AND_PROPAGATE`]。
* `Span` にも追加されるスパン属性のセット。
  * これらの属性は初期の `Attributes` セットの後に追加されます。
* （別のRFCで議論中）SamplingRateのfloat値。

### デフォルトサンプラー {#default-samplers}

これらはOpenTelemetry SDKに実装されるデフォルトのサンプラーです。

* ALWAYS_ON
* ALWAYS_OFF
* ALWAYS_PARENT
  * 親のサンプリング判断を信頼します（親の `SampledFlag` を信頼して伝播します）。
  * ルートSpan（親が存在しない）の場合は `NOT_RECORD` を返します。
* Probability
  * ユーザーが親の `SampledFlag` を無視するように設定できるようにします。
  * 確率が「ルートスパンのみ」「ルートスパンとリモートの親」「すべてのスパン」のいずれに適用されるかをユーザーが設定できるようにします。
    * デフォルトでは「ルートスパンとリモートの親」にのみ適用されます。
    * リモートの親プロパティはSpanContextに追加されるべきです（SHOULD）。仕様の[PR/216][specs-pr-216]を参照してください。
  * 1/Nの確率でサンプリングします。

**ルートSpanの判断:**

|Sampler|RecordEvents|SampledFlag|
|---|---|---|
|ALWAYS_ON|`True`|`True`|
|ALWAYS_OFF|`False`|`False`|
|ALWAYS_PARENT|`False`|`False`|
|Probability|`Same as SampledFlag`|`Probability`|

**子Spanの判断:**

|Sampler|RecordEvents|SampledFlag|
|---|---|---|
|ALWAYS_ON|`True`|`True`|
|ALWAYS_OFF|`False`|`False`|
|ALWAYS_PARENT|`ParentSampledFlag`|`ParentSampledFlag`|
|Probability|`Same as SampledFlag`|`ParentSampledFlag OR Probability`|

### Link {#links}

このRFCは、Linkの記録はSpan開始操作の間にのみ行われるべきだと提案します。
理由は以下の通りです。

* Linkの `SampledFlag` はサンプリング判断に使用できます。
* OpenTracingは `Span` の作成時にのみ参照の追加をサポートします。
* OpenCensusは任意のタイミングでのリンクの追加をサポートしていますが、これは主に子Linkを記録するために使われており、OpenTelemetryではサポートされていません。
* サンプリング判断が行われた後にリンクを記録できるようにすると、サンプラーが正しく機能しなくなり、サンプリングにおいて予期しない挙動を引き起こします。

### サンプリングはいつ行われるか {#when-does-sampling-happen}

サンプリングの判断は、実際の `Span` オブジェクトがユーザーに返される前に行われます。
理由は以下の通りです。

* 子スパンが作成される場合、それらは `SampledFlag` を知る必要があります。
* `SpanContext` がワイヤー上で伝播される場合、`SampledFlag` を設定する必要があります。
* ユーザーが何らかのトレーシングイベントを記録する場合、`Span` オブジェクトはそのデータが保持されるかどうかを知る必要があります。
  サンプリングの判断が行われるまですべてのイベントを常に収集しておくことも可能かもしれませんが、これは重要な最適化です。

考慮すべき重要なユースケースが2つあります。

* サンプリング判断に使用される可能性のあるすべての情報が、論理的な `Span` 操作を開始すべき時点で利用可能である場合。
  これが最も一般的なケースです。
* サンプリング判断に使用される可能性のある一部の情報が、論理的な `Span` 操作を開始すべき時点では利用できない場合（たとえば `http.route` は後から決定されることがあります）。

現在の[スパン作成ロジック][span-creation]は最初のユースケースをうまく処理しますが、2番目のユースケースでは、ユーザーが論理的な `start_time` を記録し、`Span` を開始するために必要なすべての情報をカスタムオブジェクトに収集し、すべてのプロパティが揃った時点でスパン作成APIを呼び出す必要があります。

このRFCは、現在の[スパン作成ロジック][span-creation]をそのまま維持し、遅延サンプリングについては優先度が高くなった時点で別のRFCで対処することを提案します。

SDKは、スパン開始操作の中で `Span` が作成されるたびに `Sampler` を呼び出さなければなりません（MUST）。

**検討した代替案:**

* 遅延スパン構築の仕組みを提供することを検討しました。
  * `Span` の構築に `Builder` パターンを使用する言語では、`Builder` が作成された時点でSpanの開始時刻が考慮される `Builder` をユーザーが作成できるようにします。
  * `Span` の構築に中間オブジェクトを使用しない言語では、おそらく `StartSpanOption` オブジェクトを介してユーザーが `Span` を開始できるようにします。
    `StartSpanOption` により、ユーザーはSpan開始時のすべてのプロパティを設定できます。
  * 利点:
    * 上記の2番目のユースケースを解決できます。
  * 欠点:
    * 2番目のユースケースについて、実際の事例をあまり多く特定できなかったため、時期尚早な決定を避けるために判断を先送りすることにしました。
* `Span` が作成される前にサンプリング判断が行われることを要求する代わりに、`Span` に明示的な `MakeSamplingDecision(SamplingHint)` を追加することを検討しました。
  `MakeSamplingDecision()` がまだ実行されていない場合、子 `Span` を作成しようとしたり、`SpanContext` にアクセスしようとしたりすると失敗します。
  * 利点:
    * サンプリングに使用される可能性のあるすべての属性が、論理的な `Span` 操作を開始すべき時点で利用できない場合を簡素化できます。
  * 欠点:
    * 最も一般的なケースで、追加のAPI呼び出しが必要になります。
    * エラーが発生しやすく、ユーザーが追加のAPIの呼び出しを忘れる可能性があります。
    * ユーザーが `MakeSamplingDecision()` を呼び出す前に子 `Span` を作成しようとすると、予期しない発見しにくいエラーが発生します。
* サンプリング判断を任意に遅延させることを許可しつつ、子 `Span` の作成前、`SpanContext` へのアクセス前、あるいは `Span.end()` の完了前には保証されるようにすることを検討しました。
  * 利点:
    * 前述の両方のユースケースをサポートする、類似した、より小さなAPIになります。
  * 欠点:
    * `SamplingHint` も遅延して記録する必要がある場合、それを設定するための追加のAPIがSpanに必要になります。
    * トレーシングイベントを記録しないという最適化ができなくなり、サンプリング判断が行われる前にすべてのトレーシングイベントを記録しなければなりません（MUST）。

## 先行技術と代替技術 {#prior-art-and-alternatives}

Zipkinやその他のDapperベースのシステムにおける先行事例として、すべてのクライアント側のサンプリング判断はヘッドで行われます。
したがって、これとの互換性を維持する必要があります。

## 未解決の問題 {#open-questions}

このRFCは、サンプリングレートの値を異なるスパンやプロセスの間でどのように伝播させるかという問題を必ずしも解決するものではありません。
このケースに対応するために、別のRFCが起票される予定です。

## 将来の可能性 {#future-possibilities}

将来的には、ライブラリ開発者がトレースをサンプリングするかどうかの推奨判断を、実行の途中まで遅延できるようにすることを提案します。

## 関連する問題 {#related-issues}

* [opentelemetry-specification/189](https://github.com/open-telemetry/opentelemetry-specification/issues/189)
* [opentelemetry-specification/187](https://github.com/open-telemetry/opentelemetry-specification/issues/187)
* [opentelemetry-specification/164](https://github.com/open-telemetry/opentelemetry-specification/issues/164)
* [opentelemetry-specification/125](https://github.com/open-telemetry/opentelemetry-specification/issues/125)
* [opentelemetry-specification/87](https://github.com/open-telemetry/opentelemetry-specification/issues/87)
* [opentelemetry-specification/66](https://github.com/open-telemetry/opentelemetry-specification/issues/66)
* [opentelemetry-specification/65](https://github.com/open-telemetry/opentelemetry-specification/issues/65)
* [opentelemetry-specification/53](https://github.com/open-telemetry/opentelemetry-specification/issues/53)
* [opentelemetry-specification/33](https://github.com/open-telemetry/opentelemetry-specification/issues/33)
* [opentelemetry-specification/32](https://github.com/open-telemetry/opentelemetry-specification/issues/32)
* [opentelemetry-specification/31](https://github.com/open-telemetry/opentelemetry-specification/issues/31)

[trace-flags]: https://github.com/w3c/trace-context/blob/main/spec/20-http_request_header_format.md#trace-flags
[specs-pr-216]: https://github.com/open-telemetry/opentelemetry-specification/pull/216
[span-creation]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#span-creation

