p95は平均できない|サーバー別のパーセンタイルを平均したダッシュボードが実態とずれる理由
レスポンス時間の監視では「平均ではなくp95を見よう」とよく言われます。平均は一部の遅いリクエストを薄めてしまうので、遅い側の実態を見たいならパーセンタイルのほうがよい。ここまでは多くの人が知っています。
では、サーバーが3台あるとき、ダッシュボードの「全体のp95」はどう計算されているでしょうか。各サーバーのp95を出して、その3つを平均している。そう作られていても、画面上では違和感なく「p95」と表示されます。
この記事では、サーバーごとのp95を平均しても全体のp95にはならないことを、Prometheus(監視ツールの1つ)の公式ドキュメントと、筆者が作った単純な数値例で確認します。そのうえで、自分のダッシュボードがどう集計しているかを確かめる手順を示します。
30秒で要点
- p95は「小さい順に並べて95%の位置にある値」。サーバーごとのp95を平均しても、全体のp95にはならない
- Prometheus公式ドキュメントは、分位数は集約できないと明記し、分位数の平均を「統計的に無意味」としている
- 平均は、合計(sum)と件数(count)を持っておけば全体の値を求め直せる。分位数にはこの方法がない
- 全体の分位数は、ヒストグラム(値の範囲ごとの件数)を集約してから推定する。誤差はバケット幅で決まる
- 自分のダッシュボードで、p95がどの段階で計算されているかを1つ確認するところから始められる
| 用語 | 意味 |
|---|---|
| パーセンタイル(分位数) | データを小さい順に並べたとき、指定した割合の位置にある値。p95は95%の位置の値 |
| SLO | サービスが満たすべき目標水準を、数値で決めたもの。例: 「p95を300ms以下に保つ」 |
| summary | Prometheusの計測方式の1つ。計測するプログラムの側で分位数を計算してから送る |
| ヒストグラム | 値の範囲(バケット)ごとに件数を数えて送る方式。分位数は後から推定する |
| バケット | ヒストグラムで件数を数える区切り。例: 200〜300ms |
| ネイティブヒストグラム | Prometheusのヒストグラムのうち、古典的なもの(あらかじめ決めた区切りで数える形式)とは別の形式。後で紹介する公式ドキュメントの例では、古典的なものより狭い範囲で値を推定している |
| PromQL | Prometheusでデータを集計するための問い合わせ言語 |
公式ドキュメントは何と書いているか
Prometheusの公式ドキュメント「Histograms and summaries」は、2つの計測方式の違いを説明する文書です。summary(プログラム側で分位数を事前計算する方式)について、次の趣旨をはっきり書いています。
- 後から別の時間幅や別のパーセンタイルで計算し直すことはできない。そして最も重要なこととして、分位数は集約できない。例として、複数の同じ役割のサーバーで動くサービス全体の90パーセンタイルを求める場合が挙げられている
avg(http_request_duration_seconds{quantile="0.95"})という集計式には// BAD!と付けられ、事前計算された分位数を平均すると統計的に無意味な値になると説明されている- ヒストグラムなら、
histogram_quantile()関数で集約できる
同じ文書の比較表でも、集約(Aggregation)の行は、summaryが「Not aggregatable(集約不可)」、ヒストグラムがPromQLで随時集約可能(古典的なヒストグラムではバケットの境界が変わらない限り)となっています。
公式ドキュメントが「BAD!」とまで書いている集計は、書き方としては普通の平均に見えます。ここが、この問題の気づきにくさです。
平均すると、どちら向きにもずれる
公式ドキュメントの主張を、筆者が作った単純な仮想の数値で確かめてみます。ここからの数値例は説明のためのもので、実測データではありません。
例1: 遅いサーバーが薄められる
- サーバーA: 1,000件、すべて100ms → p95は100ms
- サーバーB: 100件、すべて1,000ms → p95は1,000ms
- 2つのp95の平均: (100 + 1,000) ÷ 2 = 550ms
全体の1,100件を小さい順に並べると、1〜1,000番目が100ms、1,001〜1,100番目が1,000msです。95%の位置は1,100 × 0.95 = 1,045番目なので、全体のp95は1,000msになります。平均した550msは、全体のp95の約半分です。
例2: 速いのに遅く見える
- サーバーC: 1,000件、うち940件が100ms、60件が900ms → p95(950番目)は900ms
- サーバーD: 1,000件、すべて100ms → p95は100ms
- 2つのp95の平均: (900 + 100) ÷ 2 = 500ms
全体の2,000件では、1〜1,940番目が100ms、1,941〜2,000番目が900msです。95%の位置は1,900番目なので、全体のp95は100msです。平均した500msは、実態の5倍に見えています。
パーセンタイルの計算方法には細かな流儀の違いがありますが、この2つの例ではどの流儀でも結論は変わりません。
最初は「平均すると、遅い側が薄まって楽観的になる」とだけ考えていました。しかし例2のように、悲観的にずれることもあります。ずれる向きは、各サーバーの件数と分布の形しだいです。どちらにずれるか分からないので、「少し甘めの目安」として使うこともできません。
平均は求め直せて、分位数は求め直せない
ここで、1つ気になることがあります。例1で各サーバーの平均値を単純に平均しても、やはりずれます。
- サーバーAの平均100ms、サーバーBの平均1,000msを単純に平均すると550ms
- 全体の平均は、(1,000件 × 100ms + 100件 × 1,000ms) ÷ 1,100件 ≈ 181.8ms
では、平均も分位数も同じ問題を抱えているのでしょうか。違いは、元に戻せるかどうかにあります。
平均は「合計 ÷ 件数」です。各サーバーが合計時間(sum)と件数(count)を別々に記録していれば、全サーバーの合計を足し、全サーバーの件数を足して、割り直せば正確な全体平均が出ます。Prometheus公式ドキュメントも、summaryでもヒストグラムでも観測値の件数と合計を記録し、それを使って平均を計算する集計式を示しています。
分位数には、この「合計と件数」にあたる部品がありません。p95という1つの値からは、その下に何件が、どう分布していたのかが分かりません。一度p95にしてしまった値からは、全体のp95を組み立て直せないのです。
ここまでの整理を、判断の軸として書き直すと次のようになります。
| 指標 | 集約してよいか | 正しい集約のしかた |
|---|---|---|
| 件数・合計 | よい | 足し合わせる |
| 平均 | 部品があればよい | 合計の総和 ÷ 件数の総和 |
| パーセンタイル | 値のままでは不可 | 元の観測値かヒストグラムを集約してから計算する |
ヒストグラムで集約しても、バケット幅が誤差を決める
では、ヒストグラムを使えば問題は解決するのか。ここにも注意点があります。ヒストグラムからの分位数は推定値で、その誤差はバケットの幅で決まります。
公式ドキュメントは、SLOを「95パーセンタイル300ms」とした仮想例で、これを説明しています。
- 真の値が220ms付近に集中している分布では、ネイティブヒストグラムの推定は228ms(真の値は210〜229msの範囲)。一方、200〜300msという古典的なバケットでは、推定は295msになる
- 全体が100ms遅くなり真の値が約320msになると、ネイティブヒストグラムの推定は323ms。一方、300〜450msのバケットでは443msと推定される
220ms付近で余裕があるのに、推定は295msで目標の300msぎりぎりに見える。320msで少し超えているだけなのに、推定は443msで大きく超えているように見える。バケットの区切りが粗いと、目標値の近くほど判断を誤らせるということです。
summaryでも誤差はあります。同じ文書は、誤差を0.95±0.01と設定した例で、94パーセンタイルが270ms、96パーセンタイルが330msの分布なら、報告される値は270〜330msのどこにでもなりうると説明しています。
なお、この文書は冒頭で、可能ならネイティブヒストグラムを使い、古典的なヒストグラムやsummaryより優先することを最も重要な教訓として挙げています。
なぜ「p95の平均」に違和感を持ちにくいのか
この取り違えが起こりやすい理由は、3つあると考えています。
1つ目に、平均という操作に慣れすぎている。複数のものをまとめて1つの数字にするとき、平均は最初に思い浮かぶ操作です。売上や件数のように足し算で扱える数字を日常的に扱っていると、パーセンタイルも同じように扱えるように感じます。
2つ目に、結果がもっともらしく見える。例1の550msも例2の500msも、ミリ秒単位の、それらしい値です。明らかにおかしい値(マイナスの時間など)は出てこないので、画面を見ているだけでは気づけません。
3つ目に、集計の段階が画面に出てこない。ダッシュボードには「p95」とだけ表示され、それが元データから計算されたものか、サーバーごとのp95を平均したものかは、集計の設定を開かないと分かりません。ラベルが同じなら、中身も同じだと受け取ってしまいます。
確かなことと、まだ確かではないこと
確かなこと(Prometheus公式ドキュメントの記載による):
- summaryで事前計算された分位数は集約できず、平均すると統計的に無意味な値になる
- ヒストグラムは、
histogram_quantile()で集約できる(古典的なヒストグラムはバケット境界が変わらない限り) - 平均は、観測値の件数と合計から計算できる
- ヒストグラムからの分位数は推定値で、誤差はバケット幅で決まる
この記事では確かめていないこと:
- Prometheus以外の監視製品が、パーセンタイルをどう集約しているか。製品ごとに仕様を確認する必要があります
- 実際の現場で、p95を平均する集計がどの程度使われているか。この記事は頻度を主張していません
- 自分の環境のバケット設定が、目標値の付近で十分に細かいかどうか。これは各環境で確かめるしかありません
自分のダッシュボードで確かめる最小の手順
p95を見て判断しているダッシュボードを1つ選び、次を確認してください。
- p95がどの段階で計算されているかを見る。元の観測値やヒストグラムを全体で集約してから計算しているか、それともサーバーやコンテナごとに計算した値を後から平均しているか。Prometheusなら、
quantile="0.95"のような分位数の系列にavg(をかけていないかを探します - 1日分だけ、全体のp95を別に計算して比べる。アクセスログなど元の観測値が残っていれば、全件を並べてp95を出し、ダッシュボードの値と比べます
- ヒストグラムなら、目標値の前後のバケット境界を確認する。目標が300msなのに、200〜300msや300〜450msのような粗い区切りしかないなら、目標付近の推定は大きくずれえます
2で値が大きく違ったなら、そのダッシュボードの「p95」は、全体の遅さを表していなかったことになります。これまでその数字で「問題なし」「対応が必要」と判断していたものを、見直すきっかけになります。
外れ値の扱いについては外れ値は消していいのか|IQR法・Zスコア・修正Zスコアの使い分けと判断基準で、同じ現象なのに数字が食い違って見える問題は「39.8%減」と「2ポイント差」が同じ現象の話だったときで扱っています。レスポンス時間の測り方そのものはWebパフォーマンス測定とモニタリング完全ガイド、表示速度の改善提案をどこまで信じるかはCore Web Vitals改善の提案、事業インパクトはどこまで信じていいかで整理しました。
この記事で扱ったような判断を、自社の状況に照らして整理したい場合は、無料診断ツールで状況を言語化するところから始めることができます。
状況を整理する(15分)