OTEP-0006: サンプリングAPI

TL;DR

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

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

動機

ライブラリ開発者、パッケージ化されたインフラストラクチャバイナリの開発者、アプリケーション開発者、オペレーター、テレメトリシステムの所有者など、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

解説

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

ライブラリ開発者

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

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

解決策:

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

インフラストラクチャパッケージ/バイナリ開発者

例: HBase、Envoyの開発者。

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

解決策:

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

アプリケーション開発者

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

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

解決策:

  • アプリケーション開発者が公式SDKを使用する際に、カスタムサンプラーをリンクしたり、独自に作成したりできるようにします。
  • サンプリングの判断はスパン開始操作の中で行われます。 これは、スパンに関連する属性がSpan開始操作に追加された後、かつ具体的なSpanオブジェクトが存在する前に行われます(これにより、サンプラーの判断に応じてNoOpSpanを作成することも、実際のSpanインスタンスを生成することも可能になります)。
  • スパンがNoOpスパンである場合にコストの高いスパン属性/ログの計算をスキップできるようにするために、Span.IsRecording() が必要です。

アプリケーションオペレーター

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

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

解決策:

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

テレメトリインフラストラクチャの所有者

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

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

解決策:

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

内部の詳細

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

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

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

サンプリングフラグ

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

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

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

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

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

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

サンプラーインターフェース

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

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

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

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

デフォルトサンプラー

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

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

ルートSpanの判断:

SamplerRecordEventsSampledFlag
ALWAYS_ONTrueTrue
ALWAYS_OFFFalseFalse
ALWAYS_PARENTFalseFalse
ProbabilitySame as SampledFlagProbability

子Spanの判断:

SamplerRecordEventsSampledFlag
ALWAYS_ONTrueTrue
ALWAYS_OFFFalseFalse
ALWAYS_PARENTParentSampledFlagParentSampledFlag
ProbabilitySame as SampledFlagParentSampledFlag OR Probability

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

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

サンプリングはいつ行われるか

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

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

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

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

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

このRFCは、現在のスパン作成ロジックをそのまま維持し、遅延サンプリングについては優先度が高くなった時点で別の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)。

先行技術と代替技術

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

未解決の問題

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

将来の可能性

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