[アップデート] X-Ray がアダプティブサンプリングを利用できるようになりました
アップデート概要
X-Ray がアダプティブサンプリングに対応しました。
リクエスト数が多い環境で X-Ray を利用する際、トレースを全て記録するとコストが大きく増加してしまう可能性があります。
この際、サンプリングルールを設定して一部のトレースのみを記録することで、コストを抑えつつ X-Ray を利用できます。
特に、下記のような条件に合う環境ではサンプリングの効果が高いです。
・1 秒あたり 1000 件以上のトレースを生成している。
・ほとんどのトレースデータが健全なトラフィックを表しており、データの変動が少ない。
・エラーや高レイテンシーなど、問題が発生していることを示す共通の指標がある。
・エラーやレイテンシー以外にも、関連するデータを特定するためのドメイン固有の基準を持っている。
・データをサンプリングするか破棄するかを決定する共通のルールを述べることができる
・サービスごとに識別が可能であり、高トラフィックと低トラフィックのサービスが別々にサンプリングされる
・サンプリングされなかったデータを(「念のため」のシナリオのために)低コストのストレージシステムに保存できる
https://opentelemetry.io/ja/docs/concepts/sampling/
元々 X-Ray でもサンプリングレートを設定することでサンプリングを実現でき、リザーバと固定レートを設定可能でした。

リザーバで指定したリクエスト数は必ず記録した上で、それを超えた分は固定レートに従ってサンプリングされる形になります。

サンプリングルールは便利な仕組みですが、エラーになったりレイテンシの大きいリクエストのトレースを重点的に記録する機能が無かったため、本来重点的に調査したいリクエストが上手く記録されない可能性がありました。
今回追加されたアダプティブサンプリング機能を利用することで、エラーになったりレイテンシが大きいリクエストがあった場合に、サンプリングレートを動的に調整できるようになりました。

「サンプリングブースト」と「異常スパンのキャプチャ」
アダプティブサンプリングには、「サンプリングブースト」と「異常スパンのキャプチャ」の 2 種類の機能があり、下記違いがあります。
| 機能名 | 内容 |
|---|---|
| サンプリングブースト | 異常なリクエストが発生した場合に、サンプリングレートを一時的に上げる |
| 異常スパンのキャプチャ | 異常なスパンを必ず記録する (部分的なトレースであることに注意) |
これらは補完しあう関係にあり、「異常スパンのキャプチャ」を使えば「サンプリングブースト」は不要といった類のものでは無いことに注意が必要です。
X-Ray のアダプティブサンプリングはあくまでヘッドサンプリングであり、トレース全体を見てサンプリング対象を決めるわけではありません。
問題があるトレースがあれば X-Ray 側がサンプリングレートを変動させ、それを固定レートとしてサンプリングするというシンプルな動きをします。
ヘッドベースのサンプリング – サンプリングの決定はルートサービスで行われ、サンプリングフラグはコールチェーン内のすべてのサービスにダウンストリームに渡されます。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-adaptive-sampling.html#adaptive-sampling-features
一度問題のあるトレースが見つかればその周辺時間に同様の特性を持つトレースが存在する可能性は高いので、トレースの詳細を確認して記録対象を選別するような高度なことをせずとも見たいトレースを重点的に記録できるであろうと考えられます。
一般にテイルサンプリング (トレース全体を見て記録対象を選別するサンプリングアプローチ) は複雑な仕組みになりがちなので、あくまでヘッドベースのアプローチの中でサンプリングレートを動的に変動させる手法を取っていると考えられます。
また、「異常スパンのキャプチャ」機能も合わせて利用することで、異常なスパンが見つかった際にそれらを必ず記録することができます。
ただし、記録されるのはあくまで部分的なトレースなので、可能であればサンプリングブーストを利用して上手く見たいキャプチャを記録する方が望ましいと言えます。
異常スパンキャプチャにより、完全なトレースがサンプリングされていない場合でも、異常を表す重要なスパンが常に記録されます。この機能は、将来のトレースのサンプリングを増やすのではなく、異常自体のキャプチャに焦点を当てることで、サンプリングブーストを補完します。
ADOT SDK が異常を検出すると、サンプリングの決定に関係なく、そのスパンがすぐに出力されます。SDK は異常に関連するスパンのみを出力するため、これらのトレースは完全な end-to-end トランザクションではなく部分的なトレースです。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-adaptive-sampling.html#adaptive-sampling-features
この 2 つは併用できるため、コストを抑えつつ上手く必要な情報を取得できるために両方を用いて設計するのが良いでしょう。
アダプティブサンプリングの設定値
サンプリングブーストを利用する場合、固定レート、ブーストした後のレート、クールダウンウィンドウ (1 回ブーストした後、次に再ブーストされるまでの時間) を設定可能です。

X-Ray 側でこれらの設定を行うことで、アプリケーション側 (SDK 側) で修正を行うことなくサンプリングブーストを適用可能です。
新しい SamplingRateBoost フィールドを追加することで、既存の X-Ray サンプリングルールでアダプティブサンプリングを直接有効にできます。詳細については、「サンプリングルールのカスタマイズ」を参照してください。これにより、アプリケーションコードを変更したり、アプリケーションのデプロイを適用したりすることなく、アダプティブサンプリングを一元的に有効にできます。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-adaptive-sampling.html#adaptive-sampling-features
この際、サンプリングブーストを行うためのトリガーは 5xx エラーになります。
異常条件設定が指定されていない場合、ADOT SDK はデフォルトの異常条件として HTTP 5xx エラーコードを使用してサンプリングブーストをトリガーします。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-adaptive-sampling.html#adaptive-sampling-features
またローカル SDK 側でエラーもしくはレイテンシで条件を作成して、各機能を適用するかどうかを細かく設定することも可能です。
特に、「異常スパンのキャプチャ」を利用したい場合はローカル SDK 側で設定が必要です。
例えば公式ドキュメントに記載されている例ですと、下記のような設定になります。
- 500 番台のエラーが発生したら「サンプリングブースト」と「異常スパンのキャプチャ」を適用
/apiパスで 429 番もしくは 500 番台のエラーが発生かつレイテンシーが 300ms 以上の場合に「サンプリングブースト」を適用- レイテンシーが 100ms 以上の場合に「異常スパンのキャプチャ」を適用
version: 1.0
anomalyConditions:
- errorCodeRegex: "^5\\d\\d$"
usage: both
- operations:
- "/api"
errorCodeRegex: "^429|5\\d\\d$"
highLatencyMs: 300
usage: sampling-boost
- highLatencyMs: 1000
usage: anomaly-span-capture
anomalyCaptureLimit:
anomalyTracesPerSecond: 1
アダプティブサンプリングの対応言語
今後対応言語が増えるとは思いますが、現状 Java のみの対応であることに注意が必要です。
・サポートされている SDK – アダプティブサンプリングには、最新バージョンの ADOT SDK が必要です。
・サポートされている言語 – Java (バージョン v2.11.5 以降)
https://docs.aws.amazon.com/xray/latest/devguide/xray-adaptive-sampling.html
また、v2.11.5 以上の ADOT SDK を利用する必要があります。
試してみる
環境セットアップ
ADOT エージェントを仕込んだ Java アプリケーションを ECS 上にデプロイして試してみます。
インフラは CDK で定義しており、下記リポジトリに上げているので、もし興味があればご確認下さい。
以下抜粋して説明します。
X-Ray の設定を元にサンプリングするため、OTEL_TRACES_SAMPLER: "xray" と設定する必要があります。
always_on になっているとそもそもサンプリングされないので注意して下さい。
簡易的にエラーを発生させるため、アプリケーションの中で /random-error パスを定義して、50% の確率で 200 レスポンスを、50% の確率で 500 レスポンスを返すようにします。
Dockerfile で v2.11.5 の aws-otel-java-instrumentation をインストールしてコンテナ内に配置します。
バージョンが低いと本機能は使えないので注意して下さい。
アダプティブサンプリングを使わないでリクエストを送ってみる
まずはデフォルトのサンプリングルールで試してみます。
リザーバサイズが 1r/s で、固定レートが 5 % になります。

X-Ray の仕様上、同時に複数リクエストを投げて検証したいので、k6 経由でリクエストを投げてみます。
import http from "k6/http";
import { check, sleep } from "k6";
// テスト対象のAPIエンドポイント
const API_ENDPOINT =
"http://Spring-Alb16-t9CIsS3zFdOi-461608505.ap-northeast-1.elb.amazonaws.com/random-error";
export let options = {
// 段階的な負荷
stages: [{ duration: "20s", target: 10 }],
// レポート設定
summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"],
};
export default function () {
// GETリクエスト実行
const response = http.get(`${API_ENDPOINT}`);
// 成功/失敗の記録
const success = check(response, {
"status is 200": (r) => r.status === 200,
});
sleep(0.5);
}
20s で 82 回の正常リクエスト (HTTP ステータス 200) と 77 回の異常リクエスト (HTTP ステータス 500) を送信しました。

リクエストが 20 秒続いているので、リザーバサイズより必ず 20 回分のトレースは記録されます。
それを超えた分は 5 % が記録される設定になるので、固定レート分でも 7 回分程度は記録されるはずです。
(159 - 20)*0.05 ≒ 7
したがって、リザーバサイズ分と固定レート分で計 27 トレース程度は記録される計算になります。
まず、X-Ray のサービスページでフィルタ式を用いて正常なリクエストを検索してみると、12 リクエスト記録されていました。
http.url CONTAINS "/random-error" AND ok

異常なリクエストも検索してみると、12 リクエスト記録されていました。
http.url CONTAINS "/random-error" AND !ok

計 24 リクエスト分記録されているので試算より 3 リクエスト程度少ないですが、概ね計算通りと言えそうです。
今回はそもそもリクエスト数が少ないので誤差が大きく見えるのだと思います。
また、アダプティブサンプリングを設定しない場合、正常なリクエストと異常なリクエストを分け隔てなく記録することがわかります。
「異常スパンのキャプチャ」を使ってリクエストを送ってみる
検証しやすそうな「異常スパンのキャプチャ」から使ってみます。
ADOT SDK 側で設定する必要があるようなので、ECS タスクの環境変数に AWS_XRAY_ADAPTIVE_SAMPLING_CONFIG を指定します。
Anomaly spans capture can only be enabled or customized through the Local SDK configuration.
https://docs.aws.amazon.com/xray/latest/devguide/xray-adaptive-sampling.html#adaptive-sampling-features
エラーコードが 500 番のスパンは全て記録するようにします。
AWS_XRAY_ADAPTIVE_SAMPLING_CONFIG="{version: 1.0, anomalyConditions: [{errorCodeRegex: \"^500$\", usage: \"anomaly-trace-capture\"}], anomalyCaptureLimit: {anomalyTracesPerSecond: 100}}"
anomalyCaptureLimit は最大のトレース記録数として動作します。
「異常スパンのキャプチャ」や「サンプリングブースト」を利用した際も、この秒間この数を超えてトレース数が記録されることは無くなります。
コストの上限を定められるので嬉しいですね。
anomalyTracesPerSecond – Maximum number of traces with anomaly spans captured per second, to prevent excessive span volume (Default value is 1 if anomalyCaptureLimit is not present).
https://docs.aws.amazon.com/xray/latest/devguide/xray-adaptive-sampling.html#local-sdk-configuration
同じように k6 でリクエストを送ります。

正常なリクエストは前回同様の 12 リクエスト分記録されました。

異常なリクエストは全て記録されました。

良い感じですね。
「異常スパンのキャプチャ」によってキャプチャされたスパンはメタデータに
"aws.trace.flag.sampled": false と付与されるので、サンプリングされたものと区別可能です。
ADOT SDK は、異常スパンを検出すると、同じトレースからできるだけ多くのスパンを出力しようとします。この機能で出力されるすべてのスパンには、 属性 がタグ付けされます aws.trace.flag.sampled = 0。これにより、トランザクションの検索と分析で部分トレース (異常キャプチャ) と完全なトレース (通常のサンプリング) を簡単に区別できます。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-adaptive-sampling.html#adaptive-sampling-features
今回も "aws.trace.flag.sampled": false のトレースを確認できましたが、そもそもシンプルなアーキテクチャーだったために欲しい情報は全て取れてますね...

今回は「異常スパンのキャプチャ」だけ使えば良さそうに見えてますが、より複雑なマイクロサービスだと部分トレースしか取れていないケースも現れてくると思います。
サンプリングブーストを適用させてみる
最後にサンプリングブーストも利用してみます。
まず、X-Ray のサービスページでサンプリングルールを作成します。
ブーストした後の固定レートは大きく変わった方が確認しやすいので 100 % にします。

ECS の環境変数にレイテンシとエラーコードそれぞれでサンプリングブーストするような設定を入れました。
'{version: 1.0, anomalyConditions: [{highLatencyMs: 100, usage: "sampling-boost"},{errorCodeRegex: "^500$", usage: "sampling-boost"}], anomalyCaptureLimit: {anomalyTracesPerSecond: 100}}'
リクエストを投げてみます。
今回は正常なリクエストの割合が多めでしたが、総リクエスト数はほぼ一緒です。

正常なリクエストは 19 個記録されました。
http.url CONTAINS "/random-error" AND ok

異常なリクエストは 16 個記録されました。
http.url CONTAINS "/random-error" AND !ok

上手くブーストが効いてそうです。
CloudWatch メトリクスの SamplingRate メトリクスからも、サンプリングレートが 5% から 100% に上がっていることを確認できます。

ブースト後のレートを 100% にした割には記録されたトレース数が増えてないですが、トリガー遅延が 10 秒程度あるのに対して リクエストを投げる期間が 20 秒しかないことが原因だと思います。
・トリガー遅延 — X-Ray が異常を検出してから 10 秒後にサンプリングブーストが開始されると予想できます。
・ブースト期間 – X-Ray がブーストをトリガーした後、基本サンプリングレートに戻るまで最大 1 分間続きます。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-adaptive-sampling.html#adaptive-sampling-features
最後に
まだまだ奥が深そうで細かい仕様を検証しきれなかった部分も多いですが、X-Ray をより便利に利用できるような熱いアップデートであることは間違いなさそうです。
X-Ray で既にサンプリングを行っている場合、全環境で採用を検討しても良いくらいの機能に感じました。
対応言語が現状 Java だけという話はありますが...
魅力的な機能には間違い無いので細かい仕様は引き続き検証していきたいと思います!






