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 NotesやOTEP 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の名前 - 作成される
SpanのSpanKind - 作成される
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の構築を参照)
IsAdjustedCountReliable が false を返すことは、THRESHOLD値をサンプリングの決定に使用できる場合であっても、その決定がTraceState: Probability Samplingで概説されているConsistent Probability Samplingの原則に適合しない可能性があり、また関数名が示すとおり、収集されたスパンに基づいて(呼び出し回数などの)メトリクスを推定することができないことを示します。
たとえば、THRESHOLD値を導出する過程で使用されるサンプリング確率は、トレースのランダム性の値、親の sampled フラグ、またはその他の非確率的な要因に直接的または間接的に依存している場合があります。
その例についてはConsistentParentBasedを参照してください。
Requirements for the basic samplers
ConsistentAlwaysOff サンプラーは、以下を満たす SamplingIntent を提供しなければなりません(MUST)。
- THRESHOLD値が
null(またはそれに相当する値)である IsAdjustedCountReliableがfalseを返すGetAttributesが空集合を返すUpdateTraceStateが、その引数を変更せずにそのまま返す
ConsistentAlwaysOn サンプラーは、以下を満たす SamplingIntent を提供しなければなりません(MUST)。
- THRESHOLD値が
00000000000000(またはそれに相当する値)である IsAdjustedCountReliableがtrueを返すGetAttributesが空集合を返すUpdateTraceStateが、その引数を変更せずにそのまま返す
ConsistentFixedThreshold サンプラーは、本質的には TraceIdRatioBased サンプラーですが Composable インターフェースを実装しており、以下を満たす SamplingIntent を提供しなければなりません(MUST)。
- 提案されているRejection Thresholdに従って計算された閾値を表すTHRESHOLD値である
IsAdjustedCountReliableがtrueを返す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は、Span の Attributes(またはサンプリングの決定を行う際に利用可能なその他の情報)にアクセスできる論理式を表し、アクセス可能な値に対してテストを実行します。
たとえば、SERVERスパンのターゲットURLが特定のパターンに一致するかどうかをテストできます。
Predicate インターフェースにより、ユーザーはサンプリングの決定を行う時点で利用可能な情報に基づいて、カスタムのカテゴリーを作成できます。
一貫性のある確率サンプリングの整合性を保つため、Predicateは親の sampled フラグにも、TraceId の下位56ビット(ランダム性 の値を表しうる部分)にも依存してはなりません(MUST NOT)。
SpanMatches
これは Predicate に対する操作であり、与えられた Span が一致する場合、つまりPredicateによって記述されたカテゴリーに属する場合に true を返します。
Required Arguments for Predicates
引数は、ShouldSample に対して利用可能となる値を表します。
- 親
Spanを含むContext - 作成される
SpanのTraceId - 作成される
Spanの名前 - 作成される
Spanの初期のAttributesの集合 - 作成される
Spanに関連付けられるリンクのコレクション
Required Arguments for ConsistentRuleBased
- オプションの
SpanKind - (
Predicate、Composable)のペアのリスト
SamplingIntent を計算するために、Span の種類が指定された種類に一致する場合、または種類が指定されていない場合、サンプラーは与えられた順序でリストを走査し、受け取ったのと同じ引数を渡して各 Predicate の SpanMatches を呼び出します。
ある呼び出しが true を返した場合、結果は対応する Composable に対して呼び出された GetSamplingIntent が返す値となり、他の Predicate は評価されません。
SpanKind が一致しない場合、または SpanMatches の呼び出しがいずれも true を返さなかった場合、結果は ConsistentAlwaysOffSampler に対して GetSamplingIntent を呼び出すことで得られます。
ConsistentParentBased
ConsistentParentBased サンプラーの機能は、標準の ParentBased サンプラーに対応します。
引数として1つの Composable サンプラーのデリゲートを取ります。
このデリゲートは、ROOTスパンに対するサンプリングの決定を行うために使用されます。
ConsistentParentBased の振る舞いは、親スパンのサンプリングに非確率的なサンプラーが使用されていた場合に対応します。
GetSamplingIntent 操作が呼び出されると、サンプラーは有効な親スパンのコンテキストが存在するかどうかを確認します。
存在しない場合、サンプラーはデリゲートに対して GetSamplingIntent を呼び出した結果を返さなければなりません(MUST)。
そうでない場合、サンプラーは親のトレース状態から閾値を抽出しようとします。
サンプラーは、以下のとおり SamplingIntent を返さなければなりません(MUST)。
親のトレース状態が有効な閾値Tを持つ場合:
- 結果のTHRESHOLD値はTです
IsAdjustedCountReliableはtrueを返します
親のトレース状態に有効な閾値がない場合、サンプラーはtraceparentの sampled フラグを調べます。
フラグが設定されている場合:
- 結果のTHRESHOLD値は
00000000000000(またはそれに相当する値)です IsAdjustedCountReliableはfalseを返します
フラグが設定されていない場合:
- 結果のTHRESHOLD値は
null(またはそれに相当する値)です IsAdjustedCountReliableはfalseを返します
デフォルトでは、有効な親コンテキストを持つすべてのケースにおいて:
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)
})
注: 厳密には、ROOT は SpanKind ではなく、無効な親コンテキストを持つすべての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
すでに、独立したコントリビューションとして数多くのコンポジットサンプラーが利用可能です(RuleBasedRoutingSampler、Stratified Sampling、LinksBasedSampler Java版および.NET版)。 また、歴史的には、JaegerRemoteSamplerによって一部のSpanの分類が導入されていました。
本提案は、これらのアイデアを一般化し、コンポジットサンプラーの振る舞いについてもう少し形式的な仕様を提供することを目指しています。