注記: このドキュメントは、ネイティブヒストグラム(Prometheus v2.40で実験的機能として追加)が追加される前に作成されたものです。ネイティブヒストグラムが安定した機能に近づいたら、このドキュメントは徹底的に更新されます。

ヒストグラムとサマリー

ヒストグラムとサマリーは、より複雑なメトリックタイプです。単一のヒストグラムまたはサマリーは多数のタイムシリーズを作成するだけでなく、これらのメトリックタイプを正しく使用することもより困難です。このセクションでは、ユースケースに適したメトリックタイプを選択して構成するのに役立ちます。

ライブラリのサポート

まず、ヒストグラムサマリーのライブラリのサポートを確認してください。

一部のライブラリは2つのタイプのうち1つのみをサポートするか、サマリーを限定的にしかサポートしていません(分位数の計算がない)。

観測値のカウントと合計

ヒストグラムとサマリーはどちらも観測値をサンプリングし、通常はリクエストの持続時間または応答サイズをサンプリングします。これらは、観測値の数観測された値の合計を追跡するため、観測された値の平均を計算できます。観測値の数(Prometheusでは_countサフィックスが付いたタイムシリーズとして表示されます)は本質的にカウンタであることに注意してください(上記のように、増加するだけです)。観測値の合計(_sumサフィックスが付いたタイムシリーズとして表示されます)も、負の観測値がない限り、カウンタのように動作します。明らかに、リクエストの持続時間や応答サイズは負になることはありません。ただし、原則として、サマリーとヒストグラムを使用して負の値(例:摂氏温度)を観測できます。その場合、観測値の合計は減少するため、rate()を適用できなくなります。rate()を適用する必要があり、負の観測値を回避できないまれなケースでは、正の観測値と負の観測値(後者は符号が反転)の2つの別々のサマリーを使用し、後で適切なPromQL式を使用して結果を組み合わせることができます。

http_request_duration_secondsというヒストグラムまたはサマリーから過去5分間の平均リクエスト持続時間を計算するには、次の式を使用します。

  rate(http_request_duration_seconds_sum[5m])
/
  rate(http_request_duration_seconds_count[5m])

Apdexスコア

ヒストグラム(サマリーではない)の簡単な使用方法の1つは、特定のバケットの観測値に分類される観測値をカウントすることです。

300ms以内に95%のリクエストを提供するというSLOがあるかもしれません。その場合、0.3秒の上限を持つバケットを持つヒストグラムを構成します。その後、300ms以内に提供されたリクエストの相対量を直接表現し、値が0.95を下回った場合に簡単にアラートを出すことができます。次の式は、過去5分間に提供されたリクエストについて、ジョブごとにそれを計算します。リクエストの持続時間は、http_request_duration_secondsというヒストグラムで収集されました。

  sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m])) by (job)
/
  sum(rate(http_request_duration_seconds_count[5m])) by (job)

よく知られたApdexスコアを同様の方法で近似できます。ターゲットリクエスト時間の上限としてバケットを1つ、許容リクエスト時間(通常はターゲットリクエスト時間の4倍)の上限としてバケットをもう1つ構成します。例:ターゲットリクエスト時間は300msです。許容リクエスト時間は1.2秒です。次の式は、過去5分間の各ジョブのApdexスコアを生成します。

(
  sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m])) by (job)
+
  sum(rate(http_request_duration_seconds_bucket{le="1.2"}[5m])) by (job)
) / 2 / sum(rate(http_request_duration_seconds_count[5m])) by (job)

両方のバケットの合計を割っていることに注意してください。理由は、ヒストグラムのバケットが累積的であるためです。le="0.3"バケットはle="1.2"バケットにも含まれています。2で割ることで、それを修正します。

この計算は、満足部分と許容部分の計算に含まれるエラーがあるため、従来のApdexスコアとは正確に一致しません。

分位数

サマリーとヒストグラムの両方を使用して、いわゆるφ-分位数を計算できます。ここで、0 ≤ φ ≤ 1です。φ-分位数は、N個の観測値の中でφ*N番目にランク付けされる観測値です。φ-分位数の例:0.5-分位数は中央値として知られています。0.95-分位数は95パーセンタイルです。

サマリーとヒストグラムの本質的な違いは、サマリーがクライアント側でストリーミングφ-分位数を計算し、直接公開するのに対し、ヒストグラムはバケット化された観測カウントを公開し、ヒストグラムのバケットからの分位数の計算は、histogram_quantile()関数を使用してサーバー側で行われることです。

この2つのアプローチには、いくつかの異なる意味合いがあります。

ヒストグラム サマリー
必要な設定 予想される観測値の範囲に適したバケットを選択します。 必要なφ-分位数とスライドウィンドウを選択します。他のφ-分位数とスライドウィンドウは後で計算できません。
クライアントのパフォーマンス カウンターを増分するだけで済むため、観測は非常に安価です。 ストリーミング分位数の計算のために、観測は高価です。
サーバーのパフォーマンス サーバーは分位数を計算する必要があります。アドホック計算に時間がかかりすぎる場合(例:大規模なダッシュボード)、レコーディングルールを使用できます。 サーバー側のコストが低い。
タイムシリーズの数(_sumおよび_countシリーズに加えて) 構成されたバケットごとに1つのタイムシリーズ。 構成された分位数ごとに1つのタイムシリーズ。
分位数の誤差(詳細については以下を参照) 関連するバケットの幅によって、観測値の次元で誤差が制限されます。 構成可能な値によって、φの次元で誤差が制限されます。
φ-分位数とスライド時間ウィンドウの指定 Prometheus式でアドホック。 クライアントによって事前構成。
集約 Prometheus式でアドホック。 一般的に集約できません

表の最後の項目の重要性に注意してください。300ms以内に95%のリクエストを提供するというSLOに戻りましょう。今回は、300ms以内に提供されたリクエストの割合を表示するのではなく、95パーセンタイル、つまり95%のリクエストを提供したリクエストの持続時間を表示します。そのためには、0.95-分位数と(たとえば)5分間の減衰時間を持つサマリーを構成するか、300msマークの周りのいくつかのバケット(例:{le="0.1"}{le="0.2"}{le="0.3"}、および{le="0.45"})を持つヒストグラムを構成します。サービスが多数のインスタンスでレプリケートして実行されている場合、各インスタンスからリクエストの持続時間を収集し、その後、すべてを全体の95パーセンタイルに集約します。ただし、サマリーから事前に計算された分位数を平均化することは、めったに意味がありません。この特定のケースでは、分位数を平均化すると、統計的に無意味な値が得られます。

avg(http_request_duration_seconds{quantile="0.95"}) // BAD!

ヒストグラムを使用すると、histogram_quantile()関数を使用して、集約が完全に可能です。

histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) // GOOD.

さらに、SLOが変更され、90パーセンタイルをプロットする必要がある場合、または過去5分間ではなく過去10分間を考慮する必要がある場合、上記の式を調整するだけで済み、クライアントを再構成する必要はありません。

分位数推定の誤差

クライアント側で計算されるかサーバー側で計算されるかにかかわらず、分位数は推定されます。その推定の誤差を理解することが重要です。

上記からのヒストグラムの例を続けると、通常のリクエストの持続時間はほぼすべて220msに非常に近いと想像してください。言い換えれば、「真の」ヒストグラムをプロットできれば、220msで非常に鋭いスパイクが見られます。上記のように構成されたPrometheusヒストグラムメトリックでは、ほぼすべての観測値、したがって95パーセンタイルも、{le="0.3"}というラベルが付いたバケット、つまり200ms〜300msのバケットに分類されます。ヒストグラムの実装は、真の95パーセンタイルが200ms〜300msのどこかに存在することを保証します。単一の値(区間ではなく)を返すために、線形補間が適用され、この場合は295msになります。計算された分位数は、SLOに違反する直前であるという印象を与えますが、実際には、95パーセンタイルは220msをわずかに超えており、SLOに対してかなり余裕があります。

私たちの思考実験の次のステップ:バックエンドルーティングの変更により、すべてのリクエストの持続時間に100msの固定量が追加されます。これで、リクエストの持続時間に320msで鋭いスパイクがあり、ほぼすべての観測値が300ms〜450msのバケットに分類されます。正しい値が320msに近いにもかかわらず、95パーセンタイルは442.5msと計算されます。SLOをわずかに超えているだけですが、計算された95パーセンタイルははるかに悪くなっています。

サマリーを使用した場合、少なくともクライアント側で適切なアルゴリズム(Goクライアントで使用されているアルゴリズムなど)を使用していれば、いずれの場合も正しいパーセンタイル値を計算することに問題はありませんでした。残念ながら、複数のインスタンスからの観測値を集計する必要がある場合は、サマリーを使用できません。

幸いなことに、バケット境界の適切な選択により、観測値の分布に非常に鋭いスパイクがあるというこの無理な例でも、ヒストグラムはSLOの内側か外側かを正しく識別できました。また、分位数の実際の値がSLO(つまり、私たちが最も関心のある値)に近づくほど、計算された値の精度が高くなります。

それでは、実験をもう一度変更してみましょう。新しい設定では、リクエスト継続時間の分布は150msにスパイクがありますが、以前ほど鋭くはなく、観測値の90%のみを占めています。観測値の10%は、150msから450msまでの長い裾に均等に広がっています。この分布では、95パーセンタイルは300msのSLOとちょうど一致します。ヒストグラムを使用すると、95パーセンタイルの値がバケット境界の1つと一致するため、計算値は正確です。わずかに異なる値でも、関連するバケット内の(無理な)均一な分布は、バケット内での線形補間が想定しているものとまさに一致するため、依然として正確です。

サマリーによって報告される分位数の誤差は、ここでより興味深いものになります。サマリーにおける分位数の誤差は、φの次元で設定されます。私たちの場合、0.95±0.01に設定されている可能性があります。つまり、計算された値は94パーセンタイルと96パーセンタイルの間になります。上記の分布における94パーセンタイルは270ms、96パーセンタイルは330msです。サマリーによって報告される95パーセンタイルの計算値は、270msから330msの間のどこかに存在する可能性がありますが、これは残念ながら、SLOのはるか内側か外側かの違いに相当します。

結論として、サマリーを使用する場合は、φの次元で誤差を制御します。ヒストグラムを使用する場合は、観測値の次元で誤差を制御します(適切なバケットレイアウトを選択することで)。広い分布では、φの小さな変化が観測値に大きなずれをもたらします。鋭い分布では、観測値の小さな区間がφの大きな区間をカバーします。

経験則2つ

  1. 集計する必要がある場合は、ヒストグラムを選択してください。

  2. それ以外の場合は、観測される値の範囲と分布に関するアイデアがある場合はヒストグラムを選択してください。値の範囲と分布に関係なく、正確な分位数が必要な場合はサマリーを選択してください。

クライアントライブラリが必要なメトリックタイプをサポートしていない場合の対処法

実装しましょう! コードへの貢献は大歓迎です。一般的に、ヒストグラムはサマリーよりも緊急に必要とされることが予想されます。ヒストグラムはクライアントライブラリにも簡単に実装できるため、迷った場合は最初にヒストグラムを実装することをお勧めします。

このドキュメントはオープンソースです。問題点やプルリクエストを送信して、改善にご協力ください。