OTEP-0250: コンポジットサンプラーの提案

本提案は、OpenTelemetry SDKで説明されているヘッドベースサンプリングを扱います。 本提案では、追加の コンポジットサンプラー を導入します。 コンポジットサンプラーは、サンプリングの決定を行うために他のサンプラー(デリゲート または )を使用します。 コンポジットサンプラーはデリゲートサンプラーを呼び出しますが、最終的な決定は自身で行います。

ここで提案する新しいサンプラーは、Consistent Probability Samplers(一貫性のある確率サンプラー)と連携するように設計されています。 この概念の詳細な説明については、probability sampling(OTEP 235)を参照してください。 また、ドラフトPR 3910のW3C Trace Context Level 2に基づく確率サンプラーも参照してください。

文書の履歴:

目次:

Motivation

ヘッドサンプリングの設定に対するニーズは、Sampling SIG内でも、より広いコミュニティにおいても、明示的または暗黙的にたびたび示されてきました。 議論の一部は何年も前にさかのぼります。 以下に例を示します。

  • issue 173: すべての言語で自動計装のTracerを使う際にヘルスチェックのトレースを無視する方法はあるか?
  • issue 1060: URLをトレーシングから除外する
  • issue 1844: コンポジットサンプラー

残念ながら、Sampling SIGのミーティングで交わされた価値ある意見の中には、発案された当時に記録されなかったものもありますが、いくつかの例についてはSampling SIG Research NotesOTEP 240: A Sampling Configuration proposal配下のコメントを参照してください。

The Goal

本提案の目標は、事前定義済みのビルディングブロックを使って高度なサンプリング設定を構築できるようにすることです。 次のようなサンプリング要件の例を考えてみましょう。 多くのユーザーが同様のパターンに従う要件を持っていると考えられます。 ここで特に注目すべき要素は、ターゲットURLに基づくトレースの分類、特別な扱いを必要とする一部のスパン、そしてエクスポートされるスパンの総量に対する妥当な上限の設定です。

これはサンプラー仕様の新しい部分であるため、OpenTelemetry SDKにとってはオプションのコンポーネントとなることが想定されています。 ただし、SDKが opts-in(オプトイン)する場合、本書で説明するすべてのサンプラーを実装すべきです(SHOULD)。

Example

ヘッドベースサンプリングの要件:

  • ルートスパンの場合:
    • /healthcheck へのすべてのリクエストをドロップする
    • /checkout へのすべてのリクエストをキャプチャする
    • それ以外のすべてのリクエストの25%をキャプチャする
  • ルート以外のスパンの場合
    • 親のサンプリング決定に従う
  • ただし、サービス /foo へのすべての呼び出しをキャプチャする(トレースが不完全になる場合でも)
  • いずれの場合でも、1000スパン/分を超えない

: 提案するサンプラーのいくつかは、Attribute(属性)セットの 和集合 (union) を計算することを求めます。 このような和集合が構築される際、属性キーが競合する場合には、そのキーを使用する最後のセットにおける属性定義が有効になります。 同様に、Tracestate の変更が連続して行われる場合、キーが競合する際には最後の変更が以前の値を上書きします。

Proposed Samplers

新しいサンプラーすべてに共通する動作原理は、ShouldSample がコンポジットサンプラーによって形成される木構造のルートに対してのみ、一度だけ呼び出されるという点です。 サンプラーの合成によって提供されるすべてのロジックは、GetSamplingIntent を通じて閾値を計算することによって処理され、必要に応じてその計算は下流に委譲されます。

New API

このアプローチを可能にするために、サンプラーの合成に参加するすべてのConsistent Probability Sampler(一貫性のある確率サンプラー)は、標準のSampler APIに加えて、以下のAPIを実装する必要があります。 本書では、この新しいAPIを提供し、ここで説明する規則に準拠する一貫性のある確率サンプラーを指して、Composable(合成可能) サンプラーという用語を使用します。 以下で説明するすべてのサンプラーは Composable サンプラーです。

GetSamplingIntent

これは、すべての Composable サンプラーに共通する操作です。 その目的は、あるスパンに対してサンプリングの決定を行うよう求められた場合にサンプラーが実行するであろう動作について、実際のサンプリングDecision(決定)を構築することなく問い合わせることです。

Required Arguments for GetSamplingIntent

引数は、TraceId を除いてShouldSampleと同じです。

  • Span を含む Context
  • 作成される Span の名前
  • 作成される SpanSpanKind
  • 作成される Span の初期の Attributes の集合
  • 作成される Span に関連付けられるリンクのコレクション

Return value

戻り値は、以下の要素を持つ構造体(SamplingIntent)です。

  • 14文字の16進数文字列として表現されるTHRESHOLD値。値が null の場合は非確率的な DROP の決定を表す(実装は、より高性能または便利であると思われる場合、異なる表現方法を使用してもよい(MAY))
  • 調整カウント(サンプリング確率の逆数として計算される)がスパンのメトリクスを推定するために忠実に使用できることを示す boolean 値を提供する関数(IsAdjustedCountReliable
  • 最終的なサンプリングの決定が肯定的である場合に Span に追加される Attributes の集合を提供する関数(GetAttributes
  • 入力の Tracestate とサンプリングのDecisionが与えられたときに、Span に関連付けられる Tracestate を提供する関数(UpdateTraceState)。これらの関数の内部で、サンプラーは ot キーに対する th 値を追加または変更すべきではありません(SHOULD NOT)。コンポジットサンプラーの木構造のルートノードのみが、この値を設定またはクリアする責任を負います(下記の SamplingResult の構築を参照)

IsAdjustedCountReliablefalse を返すことは、THRESHOLD値をサンプリングの決定に使用できる場合であっても、その決定がTraceState: Probability Samplingで概説されているConsistent Probability Samplingの原則に適合しない可能性があり、また関数名が示すとおり、収集されたスパンに基づいて(呼び出し回数などの)メトリクスを推定することができないことを示します。 たとえば、THRESHOLD値を導出する過程で使用されるサンプリング確率は、トレースのランダム性の値、親の sampled フラグ、またはその他の非確率的な要因に直接的または間接的に依存している場合があります。 その例についてはConsistentParentBasedを参照してください。

Requirements for the basic samplers

ConsistentAlwaysOff サンプラーは、以下を満たす SamplingIntent を提供しなければなりません(MUST)。

  • THRESHOLD値が null(またはそれに相当する値)である
  • IsAdjustedCountReliablefalse を返す
  • GetAttributes が空集合を返す
  • UpdateTraceState が、その引数を変更せずにそのまま返す

ConsistentAlwaysOn サンプラーは、以下を満たす SamplingIntent を提供しなければなりません(MUST)。

  • THRESHOLD値が 00000000000000(またはそれに相当する値)である
  • IsAdjustedCountReliabletrue を返す
  • GetAttributes が空集合を返す
  • UpdateTraceState が、その引数を変更せずにそのまま返す

ConsistentFixedThreshold サンプラーは、本質的には TraceIdRatioBased サンプラーですが Composable インターフェースを実装しており、以下を満たす SamplingIntent を提供しなければなりません(MUST)。

  • 提案されているRejection Thresholdに従って計算された閾値を表すTHRESHOLD値である
  • IsAdjustedCountReliabletrue を返す
  • GetAttributes が空集合を返す
  • UpdateTraceState が、その引数を変更せずにそのまま返す

Constructing SamplingResult

コンポジットサンプラーの木構造のルートサンプラーに対する ShouldSample の呼び出しに応じて、最終的な SamplingResult を構築するプロセスは、以下のステップで構成されます。

  • サンプラーは自身の SamplingIntent を取得する。これは(サンプラーが末端でない限り)以下で説明する再帰的なプロセスである
  • 受け取ったTHRESHOLD値が null の場合、サンプリングの決定は DROP となる。そうでない場合は
  • サンプラーは受け取った IsAdjustedCountReliable 関数を呼び出し、true の場合はRandomness Value (R)で説明されているとおりTraceStateまたはTraceIdからランダム性の値Rを導出し、false の場合は新しいランダムな56ビットの数値を16進数エンコードして新しいランダム性の値Rを生成する
  • サンプラーは、Decision algorithmで説明されているとおり、受け取ったTHRESHOLD値とランダム性の値Rを(実装に応じて辞書式順序またはその他の方法で)比較し、最終的なサンプリング Decision を導出する
  • サンプラーは、受け取った UpdateTraceState 関数に親の Tracestate と最終的なサンプリング Decision を渡して呼び出し、Span に関連付けられる新しい Tracestate を取得する。これもまた、多くの場合は再帰的なステップである
  • サンプリングの決定が肯定的な場合:
    • サンプラーは、受け取った GetAttributes 関数を呼び出して Span に追加する Attributes の集合を決定する。これも多くの場合は再帰的なステップである
    • IsAdjustedCountReliable 関数が true を返した場合、受け取ったTHRESHOLDに従って Tracestate 内の ot キーに対する th 値を変更する。返された値が false であった場合は、Tracestate から ot キーに対する th 値を削除する
  • サンプリングの決定が否定的な場合、Tracestate から ot キーに対する th 値を削除する

ConsistentRuleBased

ConsistentRuleBased は、(サンプリングの決定が Span の属性に依存する場合などに)Span の分類とサンプリングを行うコンポジットサンプラーです。 Spanは個別のカテゴリーにグループ化でき、各カテゴリーは異なるSamplerを使用できます。 Spanの分類は Predicates によって支援されます。

Predicate

Predicateは、SpanAttributes(またはサンプリングの決定を行う際に利用可能なその他の情報)にアクセスできる論理式を表し、アクセス可能な値に対してテストを実行します。 たとえば、SERVERスパンのターゲットURLが特定のパターンに一致するかどうかをテストできます。 Predicate インターフェースにより、ユーザーはサンプリングの決定を行う時点で利用可能な情報に基づいて、カスタムのカテゴリーを作成できます。 一貫性のある確率サンプリングの整合性を保つため、Predicateは親の sampled フラグにも、TraceId の下位56ビット(ランダム性 の値を表しうる部分)にも依存してはなりません(MUST NOT)。

SpanMatches

これは Predicate に対する操作であり、与えられた Span が一致する場合、つまりPredicateによって記述されたカテゴリーに属する場合に true を返します。

Required Arguments for Predicates

引数は、ShouldSample に対して利用可能となる値を表します。

  • Span を含む Context
  • 作成される SpanTraceId
  • 作成される Span の名前
  • 作成される Span の初期の Attributes の集合
  • 作成される Span に関連付けられるリンクのコレクション

Required Arguments for ConsistentRuleBased

  • オプションの SpanKind
  • PredicateComposable)のペアのリスト

SamplingIntent を計算するために、Span の種類が指定された種類に一致する場合、または種類が指定されていない場合、サンプラーは与えられた順序でリストを走査し、受け取ったのと同じ引数を渡して各 PredicateSpanMatches を呼び出します。 ある呼び出しが true を返した場合、結果は対応する Composable に対して呼び出された GetSamplingIntent が返す値となり、他の Predicate は評価されません。 SpanKind が一致しない場合、または SpanMatches の呼び出しがいずれも true を返さなかった場合、結果は ConsistentAlwaysOffSampler に対して GetSamplingIntent を呼び出すことで得られます。

ConsistentParentBased

ConsistentParentBased サンプラーの機能は、標準の ParentBased サンプラーに対応します。 引数として1つの Composable サンプラーのデリゲートを取ります。 このデリゲートは、ROOTスパンに対するサンプリングの決定を行うために使用されます。

ConsistentParentBased の振る舞いは、親スパンのサンプリングに非確率的なサンプラーが使用されていた場合に対応します。

GetSamplingIntent 操作が呼び出されると、サンプラーは有効な親スパンのコンテキストが存在するかどうかを確認します。 存在しない場合、サンプラーはデリゲートに対して GetSamplingIntent を呼び出した結果を返さなければなりません(MUST)。

そうでない場合、サンプラーは親のトレース状態から閾値を抽出しようとします。 サンプラーは、以下のとおり SamplingIntent を返さなければなりません(MUST)。

親のトレース状態が有効な閾値Tを持つ場合:

  • 結果のTHRESHOLD値はTです
  • IsAdjustedCountReliabletrue を返します

親のトレース状態に有効な閾値がない場合、サンプラーはtraceparentの sampled フラグを調べます。

フラグが設定されている場合:

  • 結果のTHRESHOLD値は 00000000000000(またはそれに相当する値)です
  • IsAdjustedCountReliablefalse を返します

フラグが設定されていない場合:

  • 結果のTHRESHOLD値は null(またはそれに相当する値)です
  • IsAdjustedCountReliablefalse を返します

デフォルトでは、有効な親コンテキストを持つすべてのケースにおいて:

  • GetAttributes 関数は空集合を返します
  • UpdateTraceState 関数は、その引数を変更せずにそのまま返します

ただし、ConsistentParentBased の実装は、親Spanがローカルかリモートかに応じて、追加されるAttributesをカスタマイズしたり、トレース状態を変更したりできるようにすべきです(SHOULD)。

ConsistentAnyOf

ConsistentAnyOf は、Composable サンプラー(デリゲート)の空でないリストを引数に取るコンポジットサンプラーです。 その意図は、デリゲートの いずれか が肯定的な決定を行う場合に、肯定的なサンプリングの決定を行うことです。

GetSamplingIntent 操作が呼び出されると、リスト全体を走査し、受け取ったのと同じ引数を渡して各デリゲートサンプラーの GetSamplingIntent 操作を呼び出さなければなりません(MUST)。

ConsistentAnyOf サンプラーは、以下のとおり構築される SamplingIntent を返さなければなりません(MUST)。

  • いずれかのデリゲートが null でない閾値を返した場合、結果の閾値は、それらの null でない値の集合のうち辞書式順序で最小の値Tとなります。そうでない場合は null です
  • IsAdjustedCountReliable は、閾値Tと等しい値を返したデリゲートのいずれかが、自身の IsAdjustedCountReliable 関数の呼び出しで true を返す場合に true を返します。そうでない場合は false を返します
  • GetAttributes 関数は、宣言された順序で、各デリゲートに対する GetAttributes 関数の呼び出しが返す Attribute セットの和集合を計算します
  • UpdateTraceState 関数は、デリゲートが返す UpdateTraceState 関数を連鎖的に呼び出します。受け取った Tracestate を後続の呼び出しの引数として渡し、最後に受け取った値を返します

各デリゲートサンプラーは、上記のとおり SamplingIntent の計算に参加する機会を必ず与えられなければならず(MUST)、また同じ引数の値を見なければなりません(MUST)。 デリゲートサンプラーの順序は、最終的なサンプリング Decision には影響しません。

ConsistentRateLimiting

ConsistentRateLimiting は、別のサンプラー(デリゲート)にサンプリングのヒントを提供させつつ、サンプリングされるスパンの平均レートを制御するのに役立つコンポジットサンプラーです。

Required Arguments for ConsistentRateLimiting

  • Composable(デリゲート)
  • サンプリングの最大目標レート(スループット)

サンプラーは、受信するスパンの平均レートを計測して保持し、それによって受信スパンレートと目標スパンレートの間の望ましい比率も保持すべきです(SHOULD)。 GetSamplingIntent 操作が呼び出されると、コンポジットサンプラーは、受け取ったのと同じ引数を渡してデリゲートサンプラーから SamplingIntent を取得しなければなりません(MUST)。

返される SamplingIntent は、以下のとおり構築されます。

  • 取得した閾値を最終的な閾値としてそのまま使用すると、宣言された目標レートを超えてスパンをサンプリングすることになる場合、サンプラーは閾値を目標レートを満たす値に設定すべきです(SHOULD)。閾値の調整にはいくつかのアルゴリズムを使用できますが、仕様として特定の振る舞いは規定されていません
  • IsAdjustedCountReliable は、デリゲートが提供する SamplingIntent に対してこの関数を呼び出した結果を返します
  • GetAttributes 関数は、デリゲートが提供する SamplingIntent に対してこの関数を呼び出した結果を返します
  • UpdateTraceState 関数は、デリゲートの SamplingIntent から UpdateTraceState を呼び出して返される Tracestate を返します

Summary

Example - sampling configuration

サンプリング要件のに戻ると、サンプラーとその引数に関する非公式な記法を使って、この特定のケースをサポートするようにヘッドサンプラーを設定できます。 まず、ROOTスパンに対する要件を次のように表現してみましょう。

S1 = ConsistentRuleBased(ROOT, {
 (http.target == /healthcheck) => ConsistentAlwaysOff,
 (http.target == /checkout) => ConsistentAlwaysOn,
 true => ConsistentFixedThreshold(0.25)
 })

注: 厳密には、ROOTSpanKind ではなく、無効な親コンテキストを持つすべてのSpan(つまり種類を問わないROOTスパン)に一致する特別なトークンです。

次のステップでは、ルート以外のスパンも扱えるようにサンプラーを構築します。

S2 = ConsistentParentBased(S1)

サービス /foo を呼び出す特殊なケースは、これで次のようにサポートできます。

S3 = ConsistentAnyOf(S2, ConsistentRuleBased(CLIENT, {
           (http.url == /foo) => ConsistentAlwaysOn
     }))

最後のステップとして、エクスポートされるスパンのストリームに上限を設けます。

S4 = ConsistentRateLimiting(S3, 1000)

これが完全な例です。

S4 = 
ConsistentRateLimiting(
    ConsistentAnyOf(
        ConsistentParentBased(
            ConsistentRuleBased(ROOT, {
                (http.target == /healthcheck) => ConsistentAlwaysOff,
                (http.target == /checkout) => ConsistentAlwaysOn,
                true => ConsistentFixedThreshold(0.25),
            })
        ),
        ConsistentRuleBased(CLIENT, {
            (http.url == /foo) = > ConsistentAlwaysOn,
        }),
    ),
    1000,
)

Limitations

Composable サンプラーの開発者は、自身が意図として宣言するサンプリングDecisionが、最終的なサンプリングDecisionとは異なる場合があることを考慮すべきです。

Prototyping

JavaのためのComposableサンプラーのプロトタイプ実装が利用可能です。 ConsistentSamplerとそのサブクラスを参照してください。

Prior art

すでに、独立したコントリビューションとして数多くのコンポジットサンプラーが利用可能です(RuleBasedRoutingSamplerStratified Sampling、LinksBasedSampler Java版および.NET版)。 また、歴史的には、JaegerRemoteSamplerによって一部のSpanの分類が導入されていました。

本提案は、これらのアイデアを一般化し、コンポジットサンプラーの振る舞いについてもう少し形式的な仕様を提供することを目指しています。