サンプリング戦略と実装 - トレースデータの爆発的増加を制御する ​

マイクロサービスアーキテクチャでは、わずか数秒のユーザーリクエストでも、複数のサービスを通過する複雑なトレースが生成されます。小規模な環境では全トレースを記録しても問題ありませんが、本番環境では状況が大きく異なります。毎秒1万リクエストを処理するシステムで全トレースを記録すれば、ストレージとネットワークは瞬く間に飽和してしまうでしょう。サンプリング戦略は、こうした課題を解決するための必須の技術です。

トレースデータの指数的増加 ​

マイクロサービス時代の監視コスト ​

マイクロサービスの数が増えるほど、単一リクエストが生成するスパンは増加します。これに毎秒のリクエスト数を掛けると、必要なストレージは指数関数的に膨らむわけです。

例えば、毎秒5,000リクエスト、平均5サービスを通過するシステムでは、毎日約21.6億スパンが生成されます。これを全て保存すれば、ストレージ費用だけで月数百万円に達することも珍しくありません。さらに、ネットワーク帯域幅、データベースの書き込み処理能力も同じペースで逼迫していきます。

サンプリングが解決する3つの課題 ​

1. ストレージコスト 全トレースを記録すればストレージは肥大化します。サンプリングにより、必要なデータだけを効率的に保存できます。

2. ネットワーク負荷 トレース送信にもネットワーク帯域を消費します。不要なデータ送信を削減することで、ネットワーク効率が向上します。

3. 分析効率 膨大なトレース群から必要な情報を見つけるのは困難です。戦略的にサンプリングすれば、重要なトレースに絞った高速な分析が可能になります。

主要なサンプリング方式の比較 ​

サンプリング方式はリクエスト処理のどのタイミングで判断するかで大きく分類されます。

方式判断タイミング実装複雑度柔軟性遅延適用場面
確定的リクエスト開始時低低最小開発環境・デバッグ
確率的リクエスト開始時低低最小本番環境(標準)
動的リクエスト開始時中中最小リクエスト属性ベース
Tail-basedトレース完成後高高ありエラー優先・複雑な条件

確定的サンプリング ​

確定的サンプリングは、最もシンプルな方式です。全トレースを記録するもの(サンプリング率100%)と、全トレースを破棄するもの(サンプリング率0%)の2つのみです。

この方式は開発環境に向いています。開発中は問題の原因追跡が重要なため、全トレースを記録しておくと便利です。本番環境では、絶対に必要な情報だけを記録する方式を選びます。

確率的サンプリング ​

最も一般的な方式が確率的サンプリングです。指定した確率(例:10%)で、トレースをランダムに記録します。

サンプリング率 10% = 毎100リクエスト中、10リクエストのトレースを記録

重要な性質として、このサンプリング判断はトレースIDに基づいて決定的に行われます。つまり、同じトレースIDのスパンは必ず同じ判断を受けます。これにより、複数のサービスにまたがるトレースも、一貫して記録されたり破棄されたりするわけです。

動的サンプリング ​

リクエスト属性に基づいて、サンプリング率を動的に変える方式です。

例:重要度別のサンプリング

  • VIP顧客のリクエスト → サンプリング率100%(全て記録)
  • 通常ユーザーのリクエスト → サンプリング率5%(5%のみ記録)
  • 定期スケジュール処理 → サンプリング率1%(わずかに記録)

また、エンドポイント単位での調整も可能です。重要な決済処理エンドポイントは多く記録し、ヘルスチェックエンドポイントはほぼ記録しないといった使い分けができます。

Tail-based サンプリング ​

ここまでの3方式は、リクエスト開始時に判断していました。Tail-basedサンプリングは異なり、トレースが完成した後に判断を下します。

Collector側で完成したトレースを検査し、「このトレースは保存する価値があるか」を判断するイメージです。たとえば、以下のような条件が設定できます。

Tail-basedサンプリングの例

  • エラーを含むトレースは必ず記録
  • 処理時間が1秒以上のトレースは記録
  • 特定サービスに関連するトレースは記録
  • 上記いずれにも該当しないトレースの一部をランダムに記録

Head-based vs Tail-based: 使い分けの指針 ​

Head-based サンプリング ​

メリット

  • リクエスト開始時に判断するため、判断に必要な処理が最小限です
  • アプリケーション側で実装でき、Collector不要です
  • 遅延がなく、レスポンスタイムに影響しません

デメリット

  • トレース完成時点で初めて分かることがサンプリングに活かせません
  • エラー情報やレイテンシー情報を判断基準にできません

Tail-based サンプリング ​

メリット

  • エラーは必ず記録など、完成したトレース全体の情報に基づいた判断ができます
  • 複雑な条件指定が可能で、運用ニーズに合った柔軟なサンプリングが実現できます

デメリット

  • Collector側で全スパンを一時保持する必要があり、メモリ使用量が増えます
  • 判断待機時間(decision_wait)を設定する必要があり、タイムリーな処理に遅延が生じます
  • Collectorの運用が複雑になります

実践的な選択基準 ​

Head-basedで充分な場合

  • シンプルな確率的サンプリングで対応できる
  • サンプリング率を固定値で良い

Tail-basedが必要な場合

  • エラートレースは確実に記録したい
  • リクエスト属性だけでなく、トレース結果を反映したサンプリングがしたい
  • 複雑なビジネスロジックに基づいたサンプリングが必要

高トラフィック環境では、両者を組み合わせる パターンが最適です。まずHead-basedで20%にサンプリングして容量を削減し、その20%の中からTail-basedでさらに厳選するといった使い分けが有効です。

実装パターン ​

シンプルな確率的サンプリング ​

OpenTelemetryでは、TracerProviderのSamplerを指定することで、サンプリング率を設定します。

例えば、10%のサンプリング率を設定する場合、アプリケーション起動時に以下のように構成します。

Sampler: ProbabilitySampler(0.1)

これにより、トレースIDのハッシュ値に基づいて、確定的に10%のトレースが記録されます。

エラーを強制記録する ​

OpenTelemetryでは、スパンのステータスを Error に設定することが、エラートレースを確実に記録するためのネイティブな方法です。多くのサンプラーはステータスが Error のスパンを含むトレースを自動的にサンプリング対象とするように設計されています。

例えば、例外ハンドラーで以下のような処理を実装します。

try:
    実処理
except Exception:
    # スパンのステータスをErrorに設定
    current_span.set_status(Status(StatusCode.ERROR))

リクエスト属性に基づく動的調整 ​

リクエストのヘッダーやコンテキストから優先度情報を抽出し、それに応じてサンプリング率を変える方式です。

例えば、カスタムヘッダー X-User-Priority を確認し、VIPユーザーなら100%、通常ユーザーなら10%といった調整ができます。この判断はリクエスト処理の最初の段階で行い、以降のスパン記録に反映させます。

Tail-based サンプリング(Collector側) ​

OpenTelemetry Collectorの tail_sampling プロセッサーを使用します。

yaml
processors:
  tail_sampling:
    policies:
      # エラーを含むトレースは必ず記録
      - name: error-policy
        type: status_code
        status_code:
          status_codes: [ERROR]

      # 1秒以上のレイテンシーは記録
      - name: latency-policy
        type: latency
        latency:
          threshold_ms: 1000

      # それ以外は1%でサンプリング
      - name: default-policy
        type: probabilistic
        probabilistic:
          sampling_percentage: 1

このような設定により、Collector到達後のトレースに対して複雑なサンプリング戦略を適用できます。

本番環境での設定値の決め方 ​

基本的な計算式 ​

ピーク時トラフィック × サンプリング率 = 管理可能なトレース数

まず、業務上許容できるトレース保持数と保持期間を決めます。例えば、「1日のトレースは最大1000万件」という上限があるなら、これを基準に逆算します。

例:ピーク時トラフィック 5,000 RPS の場合

1日のリクエスト総数:

5,000 RPS × 86,400秒 = 432,000,000リクエスト

1日のトレース保持目標が1000万件なら:

目標トレース数 ÷ リクエスト総数 = 1,000万 ÷ 4億3200万 ≈ 2.3%

つまり、約2.3%のサンプリング率が目安になります。

SLA・予算からの逆算 ​

インフラコストの視点から決めることもあります。例えば、「トレーシングシステムの月額予算は50万円」と決めた場合、その予算内で保持できるトレース数を計算します。

ストレージコスト、転送コスト、処理コストなどを合計し、1GBあたりの総運用コストを算出。そこから、保持できる総データ量を逆算します。

事業的に重要なエンドポイント優先 ​

すべてのエンドポイントを同じサンプリング率とするのではなく、重要度に応じた差別化が効果的です。

例:ECサイトのサンプリング戦略

  • 決済エンドポイント(/checkout) → 100%記録(ビジネス上最重要)
  • 注文確認エンドポイント(/order/:id) → 50%記録(重要)
  • 商品検索エンドポイント(/search) → 5%記録(参考情報)
  • ヘルスチェック(/health) → 0.1%記録(ほぼ不要)

この差別化により、限られたリソースを最大の価値を生む場所に集中投下できます。

監視と調整 ​

設定したサンプリング率が適切かどうかは、実運用を通じて検証が必要です。

定期的に確認すべき項目

  • 実際のサンプリング率が設定値通りか
  • ストレージ使用量の伸び
  • Collectorのメモリ使用量(Tail-basedの場合)
  • エラートレースのサンプリング率(本当に全て記録されているか)

これらのメトリクスを監視し、必要に応じてサンプリング率を調整します。業務負荷の変化やシステム規模の成長に応じて、定期的な見直しが重要です。

サンプリング戦略は、観測可能性とコストのバランスを取る重要な技術です。適切に設計・運用することで、本番環境での効率的なトレーシング基盤を実現できるでしょう。