TT Lab
はじめる
学ぶ 学習パス コース

OTCA — OpenTelemetry認定アソシエイト

テールサンプリングはタダではない — 三つのコスト

TT Labで続きを見る

一言でいうと

ヘッドサンプリングは、まだ何も起きていない時点で判断するため、まれな事象を、そのまれさの分だけ正確に捨てます。テールサンプリングは結果を見て判断しますが、3つのコストを払います。ネットワークは減らず、メモリを大きく消費し、trace IDに基づくルーティングが必要です。ヘッドで捨てたものは、テールで復活させられません。

なぜ必要なのか

全量保存は、ほとんどの組織でコストが合いません。問題は、何を捨てるかです。ヘッドサンプリング1%で、1時間あたり5件のエラーを調査するとしましょう。

보존되는 오류 트레이스 = 5 × 0.01 = 시간당 0.05건
1건을 보려면 평균 20시간 대기

このコードブロックの韓国語は、残るエラートレースが5×0.01で1時間あたり0.05件になり、1件を見るのに平均20時間待つことになる、という意味です。

これがヘッドサンプリングの実質的な結末です。調査が必要なときにそのトレースはなく、残っているトレースはすべて正常なリクエストで、見る理由がありません。

どう動くのか

テールサンプリングは、トレースが完了するまでスパンをバッファにためておき、結果を見て判断します。エラーがあれば残し、遅ければ残し、残りは少量だけ残します。ポリシーはそれぞれ「残す条件」で、複数のポリシーのうち1つでも一致すれば保持されます。そのため、ヘルスチェックを除外するには、一致を反転させるinvert_matchを使います。

項目 ヘッドサンプリング テールサンプリング
判断のタイミング ルートスパン生成時 decision_wait経過後
エラー・遅いリクエストの保持 確率的にのみ ルールで100%
エージェント・ネットワークのコスト 比率の分だけ減少 減少なし
バックエンドの保存コスト 減少 減少
コレクターのメモリ 無視できる 1秒あたりのトレース数×待ち時間の分のバッファ
運用要件 なし trace IDに基づくルーティングが必須

コスト1: ネットワークは減りません。判定するにはすべてのスパンがコレクターに到着する必要があるため、節約できるのはバックエンドの保存とインデックス作成だけです。コレクター自体のCPUとネットワークは、むしろ増えます。

コスト2: メモリ。decision_waitの間、すべてのスパンを保持し続ける必要があります。計算式は単純です。

초당 트레이스 수 × decision_wait(초) × 트레이스당 스팬 수 × 스팬 크기 = 버퍼 메모리

예: 10,000 trace/s × 30s × 12 스팬 × 1.2KB
  = 4,320,000 KB = 약 4,219 MB (여유 2배면 약 8,438 MB)

このコードブロックの韓国語は、バッファメモリが「1秒あたりのトレース数×decision_wait(秒)×トレースあたりのスパン数×スパンのサイズ」で求まることと、その計算例、および余裕を2倍とった場合の値を示しています。

num_tracesは、同時にメモリに保持するトレース数の上限で、超過すると最も古いトレースが強制的に判定されます。必要な値も同じ掛け算から出ます。1秒あたりのトレース数×decision_waitです。10,000 trace/sで30秒待機なら、300,000が必要です。

コスト3: ルーティング。同じトレースのすべてのスパンが、同じコレクターインスタンスに到着する必要があります。コレクターを複数台に増やして、前に通常のロードバランサーを置くと、1つのトレースのスパンが複数のインスタンスに散らばり、それぞれが断片を見て判定することになり、結果は、ランダムに切れたトレースです。解決策は、前段にrouting_key: traceIDを使うloadbalancingエクスポーターの層を置くことです。

同じ理由で、DaemonSetではテールサンプリングは不可能です。ノードごとにコレクターが起動する構造では、1つのトレースのスパンが複数のノードに散らばって到着するため、各エージェントがトレースの一部しか見られません。テールサンプリングは、Deployment(ゲートウェイ)の層でのみ使います。

現場での姿

最も高い授業料を払った事故は、num_tracesを50,000にしたままトラフィックが増えたケースでした。ピークが10,000 trace/sでdecision_waitが30秒だったので、必要な値は300,000でした。上限が6分の1だったため、古いトレースが強制判定され続け、メモリ圧迫が重なって、コレクターがOOMで再起動を繰り返し、5分間分のデータを失いました。メトリクスは、ほとんど異常を報告しませんでした。データは「正常に」送信されていて、バックエンドで特定のトレースだけがない形だったからです。

教訓は2つあります。1つ目は、tail_samplingを有効にする前に、先に掛け算をすることです。2つ目は、データが消えているのにメトリクスが静かなら、パイプラインからtail_samplingを一時的に外して、再現するかを見ることです。これが最も早い切り分け方です。

実務での組み合わせは、おおむね次のとおりです。トラフィックが非常に多いサービスは、ヘッドで10–50%に事前に減らし、その上にテールを重ねて、エラーと遅いリクエストを拾います。ヘッドですでに捨てたものはテールで復活させられないため、ヘッドの比率は、耐えられる範囲でできるだけ高く設定するのが原則です。

次のラボですること

/root/otca-sampling/の下に、テールサンプリングポリシーを5つ書きます。エラーと遅延を100%残す結果ベースのポリシー、invert_matchでヘルスチェックを除外するポリシー、VIPテナントと遅延をandで組み合わせた複合ポリシー、そして残りのための確率ポリシーです。そのあと、前段のloadbalancingエクスポーターを設定し、バッファメモリを自分で計算してファイルに残し、最後にその値をそのまま入れたOpenTelemetryCollector CRを書きます。