パーセンタイル統計とレイテンシ分布の罠
平均レイテンシで監視すると遅いユーザーを見落とす。なぜ平均が嘘をつくのか、p99はなぜ加算も平均もできないのか、HdrHistogramやt-digestがどう正しく集計するのかを原理から押さえます。
- 1.レイテンシ分布は右に裾を引くため平均は外れ値に引っ張られ、中央値より大きく出る。多数のユーザーの体感は平均より良く、少数の体感は平均よりはるかに悪い。
- 2.パーセンタイルは順序統計量なので加算も算術平均もできない。サーバーごとのp99を平均した値は全体のp99と一致せず、しばしば過小評価になる。
- 3.正しい集計にはヒストグラム(HdrHistogram)やスケッチ(t-digest)を使い、生データではなくマージ可能な分布表現を集約してからパーセンタイルを引く。
平均レイテンシはなぜ嘘をつくのか
「平均応答時間は50ms、健全です」——このダッシュボードは、しばしばユーザーの怒りを覆い隠します。レイテンシ分布を平均という1つの数字に潰した瞬間、分布の形が持つ最も重要な情報、すなわち「裾(テイル)」が消える からです。
レイテンシは本質的に 右に長い裾を引く非対称分布 になります。下限は物理的に存在する(処理が0msより速くなることはない)一方、上限はGCポーズ、ロック競合、リトライ、ネットワークの揺らぎなどでいくらでも伸びます。この 右歪(みぎゆがみ) のため、平均は少数の遅い値に引っ張られて中央値より大きく出ます。逆に言えば、過半数のリクエストは平均より速く、少数のリクエストは平均よりはるかに遅い。平均値そのものを体感するユーザーはほとんどいません。
左右対称な正規分布なら平均=中央値=最頻値で一致します。しかし右に裾を引く分布では 最頻値 < 中央値 < 平均 の順に並びます。レイテンシで平均が中央値より明確に大きいなら、それは裾が効いている証拠です。平均だけを見ることは、分布が対称だという誤った前提を暗黙に置くことに等しいのです。
さらに平均には致命的な弱点があります。外れ値1つで簡単に汚染される ことです。1000件のうち1件が10秒かかれば、それだけで全体の平均に10msが上乗せされます。SLO違反の原因がどこにあるかを平均は教えてくれません。
p50 / p99 / p999 が表すもの
パーセンタイル(百分位数)は、値を小さい順に並べたときの順位 で分布を切る指標です。pX は「X%のリクエストがこの値以下に収まる」境界を意味します。
- p50(中央値): 半数のリクエストはこれ以下。典型的な体感に近い。
- p99: 99%がこれ以下、裏返せば 100回に1回 はこれより遅い。
- p999(p99.9): 1000回に1回より遅い領域。
重要なのは、どのパーセンタイルが「効く」かはトラフィック構造で決まる ことです。1ページが内部で多数のAPIを呼ぶ場合、ユーザーが1回の操作で100回のリクエストを発生させるなら、各リクエストの p99に当たる確率の積み重ね で、ページ全体ではほぼ毎回どこかが遅い裾に当たります。0.99^100 ≒ 0.366 なので、約63%の操作が少なくとも1つのp99遅延を踏むことになります。だからこそ高トラフィックなサービスでは p99 や p999 が体感を支配します。この合成によるテイルの悪化は/devops/queueing-theory-tail-latency/で詳しく扱います。
SLOは「99%のリクエストを300ms以内に返す」のようにパーセンタイルで定義するのが定石です。平均ではなくパーセンタイルを使うのは、ユーザー体験が裾で決まるからです。SLI/SLOの設計は/devops/sre-slo/を参照してください。試験でも「平均ではなくパーセンタイルでレイテンシ目標を立てる理由」は頻出です。
最大の罠:パーセンタイルは加算も平均もできない
ここが本記事の核心です。パーセンタイルは 順序統計量 であり、平均(加法的な量)とは数学的な性質が根本的に異なります。
平均には 加法性 があります。2つのグループの平均は、件数で重み付けすれば正しく合成できます。ところがパーセンタイルにはこの性質がありません。部分集合のパーセンタイルから全体のパーセンタイルを復元することは、原理的にできない のです。
具体例で見ます。3台のサーバーがあり、それぞれの p99 が 100ms, 100ms, 100ms だったとします。ここで「全体の p99 は?」と問われて (100+100+100)/3 = 100ms と答えるのは誤りです。なぜなら、各サーバーで100msを超える1%のリクエストが どんな値だったか の情報が、p99という1点に潰された時点で失われているからです。あるサーバーの上位1%が実は全て5秒だったとしても、p99=100msという表示は同じになります。
| やりたい集計 | 平均(加法的) | パーセンタイル(順序統計量) |
|---|---|---|
| 複数グループの統合 | 件数で重み付けして合算でき正確 | 部分のpXから全体のpXは復元不能 |
| 時間窓のロールアップ | 分の平均から時の平均を算出可能 | 分のp99を平均/最大しても時のp99と不一致 |
| 集計の方向 | 事前集約してよい | 原則として生分布を保ったまま合算が必要 |
| 典型的な誤り | ほぼ起きない | p99の平均・p99の最大で代用してしまう |
この罠は監視基盤で日常的に踏まれます。「各インスタンスのp99を集めて平均する」「1分ごとのp99を保存し、1時間のp99はそれらの平均(または最大)で代用する」——どちらも統計的に無意味です。前者は過小評価(個々のサーバーで薄まる)、後者の平均は過小評価、最大は過大評価になりがちで、いずれも真のp99とは一致しません。パーセンタイルの平均は、ほぼ常に間違った数字 だと覚えてください。
時系列データベースに「5分ごとのp99」だけを保存していると、後から「1日のp99」を正しく出せません。一度パーセンタイルに潰した値は元の分布に戻せないからです。正しくは、生のレイテンシ分布をマージ可能な形(ヒストグラムやスケッチ)で保存し、必要なときに合算してからパーセンタイルを引きます。これは/devops/metrics-cardinality/で扱う基数の問題とも絡み、保存形式の設計が集計の正しさを左右します。
ヒストグラムによる近似集計:HdrHistogram
生のレイテンシを全件保存すれば正確なパーセンタイルが出せますが、毎秒数万件を全件保持するのは非現実的です。そこで使うのが マージ可能で省メモリな分布表現 です。
最も基本的なのが ヒストグラム です。値の範囲を多数のバケット(区間)に分け、各バケットに入った件数だけを数えます。生データは捨て、(バケット境界, 件数) の組 だけを保持します。パーセンタイルは、件数を端から累積し、目標の順位に達したバケットの境界値を答えとして読みます。
ヒストグラムの決定的な利点は 加算性 です。バケット構成が同じ2つのヒストグラムは、対応するバケットの件数を足すだけでマージできます。マージしてからパーセンタイルを引けば、サーバーをまたいでも時間窓をまたいでも正しい全体のpXが得られる のです。これがパーセンタイル加算問題への正攻法です。
バケット例(境界, 件数):
[0,1)ms: 12000
[1,2)ms: 8000
[2,4)ms: 3000
[4,8)ms: 900 ← ここまでの累積で全体の99%に到達
[8,16)ms: 90 ← p999(上位0.1%の境目)はこのあたり
[16,32)ms: 10
合計: 24000件 → p99は[4,8)バケットの上側境界で近似
HdrHistogram(High Dynamic Range Histogram)は、この発想を高ダイナミックレンジ向けに洗練したものです。1マイクロ秒から数時間まで桁の異なる値を、指定した相対精度(例:有効数字3桁)を全域で保ちながら 一定メモリで記録します。鍵は 対数的なバケット配置 です。バケット幅を値の大きさに比例して広げるため、小さい値は細かく、大きい値は粗く刻まれ、全域で「値に対する相対誤差」が一定に保たれます。線形バケットなら裾の領域で必要なバケット数が爆発しますが、HdrHistogramは対数配置でこれを抑えます。記録はロックフリーで定数時間、計測がアプリ自身のレイテンシを汚さない点も実務上重要です。
スケッチによる近似集計:t-digest
ヒストグラムは事前にバケット境界を決める必要があり、どこに精度を寄せるか を設計時に固定します。これに対し、データの分布に適応し、特に裾の精度を高く保つ よう設計されたのが t-digest です。
t-digestは、データを 重み付きのクラスタ(セントロイド) の集合として近似します。核心は 可変サイズのクラスタ にあります。中央付近(p50近辺)のクラスタは大きく(多くの点をまとめて粗く)、両端の裾(p1やp99、p999近辺)のクラスタは小さく(少ない点で細かく)保ちます。この大小は、各点の「分布上の位置」を表す累積分布関数(CDF)上の位置に応じたスケール関数で制御されます。
なぜ裾を細かくするのか。相対的な順位精度は端で重要だから です。p50が0.49か0.51かは実害が小さい一方、p999が0.998か0.9995かは桁違いの遅延を意味し、ここでの粗さは致命的です。t-digestはメモリを裾に重点配分することで、少ないクラスタ数で極端なパーセンタイル(p999、p9999)を高精度に近似 します。
| 観点 | HdrHistogram | t-digest |
|---|---|---|
| 内部表現 | 固定境界の対数バケット | 可変サイズのセントロイド |
| 精度の保証 | 全域で相対誤差を一定に保つ | 裾ほど高精度(中央は粗め) |
| 値域の前提 | 事前に最大値の桁を指定 | 事前指定不要・分布に適応 |
| 得意領域 | 境界既知・定数時間記録 | 極端なパーセンタイルの近似 |
| マージ | バケット件数の加算で可能 | セントロイド統合で可能 |
両者に共通する最重要の性質が マージ可能性 です。各サーバーがローカルでHdrHistogramやt-digestを構築し、中央でそれらを マージしてから パーセンタイルを計算する——この流れだけが、分散環境で統計的に正しいp99を与えます。生データを全部集めずに済み、かつパーセンタイル加算問題も回避できる、一石二鳥の解です。
HdrHistogramもt-digestも近似です。読み出されるパーセンタイルにはバケット幅やクラスタ粒度に由来する誤差が含まれます。SLO判定のように境界がシビアな用途では、設定した相対精度(HdrHistogramなら有効桁数、t-digestなら圧縮パラメータ)が要求を満たすかを必ず確認してください。「ヒストグラムだから正確」ではなく「設定した精度の範囲で正確」です。精度を上げればメモリと引き換えになります。
運用への落とし込み
- 平均を主指標にしない: ダッシュボードの一等地はp50・p99・p999に。平均は分布の歪みを隠す。
- 生分布をマージ可能な形で保存する: 事前集約したパーセンタイルではなく、ヒストグラムやスケッチを保存し、合算してからpXを引く。
- パーセンタイルの平均・最大を作らない: 集計パイプラインで「p99の平均」が生成されていないか点検する。これは無意味な数字。
- 裾に精度を寄せる: SLOがp999で効くなら、その領域の近似精度を確保する設定を選ぶ。
- 可視化はヒートマップで: 1本の折れ線でなく、バケット件数を時間軸で色分けすれば分布の変化(二峰化や裾の伸び)が見える。集計基盤の全体像は/devops/observability/を参照。
まとめ
- レイテンシ分布は 右に裾を引く ため平均は外れ値に汚染され、
中央値 < 平均となる。平均はユーザー体験を代表しない。 - p99・p999は順序で分布を切る指標。高ファンアウトなサービスほど裾が体感を支配する。
- パーセンタイルは加算も平均もできない。部分のpXから全体のpXは復元不能で、「p99の平均」は統計的に無意味な数字。
- 正しい集計は マージ可能なヒストグラム(HdrHistogram)やスケッチ(t-digest) を合算してからパーセンタイルを引く。HdrHistogramは対数バケットで全域一定精度、t-digestは可変クラスタで裾を高精度に近似する。
- 近似である以上、設定した精度の範囲で正確。SLO判定の境界がシビアなら誤差の方向と大きさを把握しておく。
DevOps/インフラ Article
パーセンタイル統計とレイテンシ分布の罠を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
パーセンタイル
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
パーセンタイルは順序統計量なので加算も算術平均もできない。サーバーごとのp99を平均した値は全体のp99と一致せず、しばしば過小評価になる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「パーセンタイル / レイテンシ」に近いか確認する。
- 強みである「レイテンシ分布は右に裾を引くため平均は外れ値に引っ張られ、中央値より大きく出る。多数のユーザーの体感は平均より良く、少数の体感は平均よりはるかに悪い。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。