サンプリングでコストを制御する:SDK と Collector の設定
SDK でサンプリングするか、2 層の Collector で tail サンプリングを使うかを判断します。
すべてのトレースを保持すると、すぐに高価になり、そのほとんどは、誰も開くことのない通常のリクエストです。サンプリングは、代表的な、または興味深いサブセットを保持します。OpenTelemetry には、決定を行う場所が 2 つあります。トレースが始まるときの SDK(head サンプリング)と、トレースが完了した後の Collector(tail サンプリング)です。どちらも純粋に設定です。このガイドでは、それぞれと、その選び方を説明します。
SDK での head サンプリング
決定は、ルートのスパンが作成されたときに行われるため、安価で、追加のインフラも必要ありません。リクエストが失敗するか遅くなるかは、まだ起きていないため、知ることができません。
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
仕様が定義するサンプラーの名前には、always_on、always_off、traceidratio、parentbased_always_on(デフォルト)、parentbased_always_off、parentbased_traceidratio があります。parentbased_ のバリアントを使ってください。それらは、受信リクエストの親のサンプリングの決定に従うため、トレースは、サービスをまたいで、全体として保持されるか破棄されるかのどちらかになります。各サービスで単純な traceidratio を使うと、独立した決定が行われ、壊れたトレースが生成されます。
宣言的な設定ファイルでは、同じ考え方は次のようになります。
tracer_provider:
sampler:
parent_based:
root:
trace_id_ratio_based:
ratio: 0.1
ファイル形式でサポートされているサンプラーの名前については、お使いの SDK のドキュメントを確認してください。
Collector での tail サンプリング
tail サンプリングは、トレースが完了するまで待ち、その後でポリシーを適用します。すべてのエラーとすべての遅いトレースに加えて、残りの小さな割合を保持でき、head サンプリングにはできないことです。コストは、より多くのインフラとメモリです。
processors:
tail_sampling:
decision_wait: 10s
num_traces: 50000
expected_new_traces_per_sec: 100
policies:
- name: keep-errors
type: status_code
status_code:
status_codes: [ERROR]
- name: keep-slow
type: latency
latency:
threshold_ms: 2000
- name: sample-the-rest
type: probabilistic
probabilistic:
sampling_percentage: 5
仕組み:
decision_waitは、トレースの最初のスパンを見てから、決定するまでに processor が待つ時間です。最も遅いトレースの所要時間より長くする必要があり、そうでないと、不完全なトレースに基づいて決定することになります。num_tracesは、待機中にメモリに保持されるトレースの数です。トレースの到着率にdecision_waitを掛けたものに、余裕を持たせて、サイズを決めます。policiesは、各トレースに対して評価されます。いずれかのポリシーが保持すると言えば、トレースは保持されるため、上の 3 つは、「エラーを保持し、遅いものを保持し、さらにすべての 5% を保持する」という意味になります。- 他のポリシーのタイプには、
string_attribute(顧客のティアなどの値に一致)、numeric_attribute、always_sample、and(ポリシーの組み合わせ)、composite(ポリシー間でレートを割り当て)があります。
tail_sampling は contrib ディストリビューションにあり、そのオプション名は進化するため、お使いのバージョンの README を確認してください。
落とし穴:1 つのトレースは 1 つの Collector に届く必要がある
tail サンプリングは、トレースのすべてのスパンが同じ Collector インスタンスに到着する場合にのみ機能します。単純なロードバランサーの背後に複数のレプリカがあると、1 つのトレースのスパンが散らばり、断片に基づいて決定が行われます。標準的な解決策は、2 つの層です。
- 層 1(ロードバランシングの層) は、
loadbalancingexporter を使って、トレース ID で、スパンを一貫した層 2 のインスタンスにルーティングします。 - 層 2(サンプリングの層) は、
tail_samplingを実行し、保持されたトレースをエクスポートします。
層 1:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
exporters:
loadbalancing:
routing_key: traceID
protocol:
otlp:
tls:
insecure: true
resolver:
dns:
hostname: otel-sampler-headless.observability.svc.cluster.local
port: 4317
service:
pipelines:
traces:
receivers: [otlp]
exporters: [loadbalancing]
層 2:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 20
tail_sampling:
decision_wait: 10s
policies:
- name: keep-errors
type: status_code
status_code:
status_codes: [ERROR]
- name: sample-the-rest
type: probabilistic
probabilistic:
sampling_percentage: 10
batch:
exporters:
otlp/backend:
endpoint: tempo:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, tail_sampling, batch]
exporters: [otlp/backend]
DNS が層 2 の Pod を列挙するように、headless Service を使います。Pod がスケールアップまたはダウンすると、ルーティングが変わり、いくつかの処理中のトレースが、不完全なデータで決定される可能性があることに注意してください。層を分けておくことは、障害を分離することにもなります。サンプリングの問題が、取り込みをブロックすることはありません。
head と tail の組み合わせ
一般的で賢明な構成:
- トラフィックが非常に多いサービスで、ソースでのボリュームを減らすための、SDK での控えめな head サンプラー(たとえば 50%)。
- エラーと遅いリクエストが生き残ることを保証するための、Collector での tail サンプリング。
注意してください。head で破棄されたものは、tail では回復できません。すべてのエラーを保持する必要がある場合は、それらのサービスに、積極的な head サンプリングを行わないでください。
メトリクスは、先に導出すればサンプリングを生き残る
トレースを破棄すると、そこから計算できる数値、たとえばリクエスト率やレイテンシーのパーセンタイルも失われます。ダッシュボードが正確なままであるように、サンプリングのステップの前に、spanmetrics connector でメトリクスを生成します。
connectors:
spanmetrics:
service:
pipelines:
traces/all:
receivers: [otlp]
exporters: [spanmetrics, loadbalancing]
metrics/derived:
receivers: [spanmetrics]
processors: [batch]
exporters: [otlphttp/prometheus]
ここでは、すべてのスパンが connector に供給され、サンプリングされたセットだけがストレージへと進みます。
選択
| 目的 | アプローチ |
|---|---|
| 新しいインフラなしで、シンプルにコストを削減する | parentbased_traceidratio での head サンプリング |
| すべてのエラーと遅いリクエストを保持する | 2 層の Collector での tail サンプリング |
| 正確なレートとパーセンタイルを保つ | サンプリングの前の spanmetrics |
| サービスが少なく、ボリュームが小さい | サンプリングなし。コストが問題になったら再検討 |
確認する
- 層 2 で、Collector 自身のメトリクス(ポート 8888)を使って、入力と出力のスパン数を比較し、比率がポリシーと一致することを確認します。
- 既知の失敗したリクエストを検索し、そのトレース全体が存在することを確認します。
- 層 2 のメモリを監視します。
decision_waitの間、トレースを保持しているためです。増加する場合は、num_tracesを減らすかレプリカを追加し、memory_limiterを前に置いたままにします。
人気のガイド
-
OpenTelemetry を解説する:SDK、OTLP、Collector YAML を 1 行も書く前に、構成要素と、設定が存在する 3 つの場所を理解します。 -
Collector の設定モデル:コンポーネント、パイプライン、プロバイダー Collector の YAML のルールを学びます。命名、パイプライン、connector、ファイルのマージ、環境変数。 -
OTEL_* 環境変数で SDK を設定する 標準の変数を使って、任意の言語のサービス名、exporter、サンプリング、制限を設定します。 -
コードを変更せずにアプリを計装する:Java、Python、Node.js 環境変数とコマンドラインのフラグだけで、OpenTelemetry のエージェントをアタッチします。 -
Collector を本番対応にする:メモリ制限、バッチ処理、リトライ 過負荷や障害の間もテレメトリが生き残るように、processor、キュー、永続ストレージを設定します。 -
Helm で Kubernetes に Collector をデプロイする 公式チャートをインストールし、ログとメトリクスに preset を使い、values ファイルで設定を上書きします。 -
Collector と SDK のトラブルシューティング テレメトリが届かないときの体系的なチェックリスト:debug exporter、ヘルスチェック、内部メトリクス、よくある間違い。