TL

ヒストグラムと分位点推定の内部(Prometheus vs Native)

Prometheusのhistogram_quantileが返すp99はなぜ当てにならないのか。固定バケット補間の誤差源と、ネイティブヒストグラムやDDSketchが相対誤差を保証する原理を押さえ、信頼できる分位点監視を設計できます。

応用Prometheusヒストグラム分位点オブザーバビリティメトリクスSRE最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.古典的Prometheusヒストグラムはバケット境界を事前固定し、histogram_quantileはバケット内を線形補間する。真の分布が線形でない以上、誤差は構造的に避けられず、特に境界の粗い裾で大きくなる。
  • 2.ネイティブ(指数)ヒストグラムとDDSketchはバケット幅を値に比例させ、全域で一定の相対誤差を保証する。バケットを動的に生成するため事前のle設計が不要で、分位点の精度が値域に依存しない。
  • 3.いずれもバケット件数の加算でマージ可能だが、加算可能性が成り立つのは境界スキームが一致するときだけ。古典ヒストでleが揃わない系列は正しく合算できない。

なぜ「ヒストグラムの分位点」が問題になるのか

レイテンシのp99を監視したい。しかし/devops/percentile-latency-statistics/で見たとおり、分位点は順序統計量なので加算も平均もできず、生データを全件保持するのも非現実的です。そこで各サーバーで分布を ヒストグラム に畳み込み、件数だけを集めて合算してから分位点を引きます。問題は、この「ヒストグラムから分位点を読む」工程に構造的な誤差が潜む ことです。本記事はその誤差の正体と、ネイティブヒストグラムやDDSketchがどう誤差を抑えるかを、Prometheusを軸に解説します。

古典的Prometheusヒストグラムの仕組み

古典的(classic)なPrometheusヒストグラムは、メトリクスを複数の 累積バケット に展開します。http_request_duration_seconds_bucket{le="0.1"} のように le(less than or equal、以下)ラベル付きで、「境界値 le 以下に収まった観測数」をカウンタとして公開します。重要なのは 累積(cumulative) である点です。le="0.1" のバケットは0.1秒以下の全件、le="0.5" は0.5秒以下の全件を含みます。最大の le="+Inf" は総件数(_count)に一致します。

le="0.1"  : 12000   ← 0.1s以下の累積件数
le="0.25" : 18500
le="0.5"  : 21000
le="1.0"  : 21800
le="+Inf" : 22000   ← 総件数(_count と一致)

le バケット間の 件数の差 が、その区間に落ちた観測数になります。例えば (0.25, 0.5] の区間には 21000 - 18500 = 2500 件が入ったと分かります。バケット境界は 収集時にあらかじめ固定 され、計測中に変えられません。これが後述の誤差の根源です。

累積バケットの利点と代償

累積形式の利点は、分位点計算で「下から何件目か」を求めるとき、対象の le バケットの値をそのまま読めば累積件数になっている点です。一方の代償は、1つの観測で全ての上位バケットがインクリメントされるため、バケット数 b に比例して時系列が増えること。1メトリクスが b+2 本(各 le とsumとcount)の系列を生み、これが/devops/metrics-cardinality/で扱う基数爆発の一因になります。

histogram_quantile の線形補間と誤差

histogram_quantile(0.99, ...) は次の手順で分位点を推定します。まず総件数に分位点を掛けて目標順位(rank)を出す。次に累積バケットを下から走査し、目標順位を初めて超えるバケットを特定する。そして そのバケットの下側境界と上側境界の間を線形補間 して値を返します。

ここに本質的な近似があります。バケット内のデータが一様(均等)に分布していると仮定して直線で内挿する のです。現実の分布はバケット内で一様ではありません。レイテンシは右に裾を引くため、1つのバケットの中でも下側に密集して上側が疎、ということが普通に起きます。それでも補間は直線を引くので、真の分位点とずれます。

目標が le="0.5" バケット(下端0.25, 上端0.5)に入るとき:
  下端での累積 = 18500, 上端での累積 = 21000
  目標順位     = 21780(22000 × 0.99)
  → 線形補間: 0.25 + (0.5 - 0.25) × (21780 - 18500)/(21000 - 18500)
            = 0.25 + 0.25 × (3280/2500) ... 区間をはみ出すので上端付近にクランプ

誤差を支配するのは バケット幅 です。目標分位点が落ちるバケットが広いほど、補間の自由度が大きく、推定が荒れます。そして固定バケットでは 裾ほどバケットが粗くなりがち です。le=[..., 1, 2.5, 5, 10] のように上限側を間引いて設計するのが通例で、するとp99やp999が (5, 10] のような広いバケットに落ち、補間誤差が最大化します。さらに最後の +Inf バケットには 上側境界が存在しない ため、分位点がそこに落ちると値を内挿できず、Prometheusは直前バケットの上端を返します。つまり 最大の le を超える分位点は原理的に推定不能 です。

le設計が分位点の精度を決める

古典ヒストの精度は、計測対象のレイテンシ域に le 境界をどれだけ密に配置できたかで決まります。狙うSLOがp99=300msなら、200〜400ms付近に細かく境界を置かないと補間が荒れます。逆に言えば、観測前にレイテンシの分布をある程度知っていないと適切な le を引けません。「とりあえずデフォルトのバケット」では、肝心の裾で誤差が大きくなります。SLO境界の設計は/devops/sre-slo/を参照してください。

加算可能性とマージの条件

ヒストグラムの最大の武器は マージ可能性 です。同じ le 境界を持つ2つのヒストグラムは、対応するバケットの件数を足すだけで合算でき、合算後に分位点を引けば分散環境でも正しい全体のpXが得られます。これが分位点の加算不能問題を回避する正攻法です。

ただし成立条件があります。バケット境界スキームが完全に一致していること です。サービスAが le=[0.1, 0.5, 1]、サービスBが le=[0.2, 0.4, 0.8] のように異なる境界を使うと、対応づけができず単純加算できません。PromQLの sum by (le) (rate(..._bucket[5m])) で系列をまたいで合算する操作も、le が揃っていて初めて意味を持ちます。異なるバケット構成のヒストグラムを正しく統合するには、共通の粗い境界へ再ビニングするか、そもそも境界を統一しておく必要があります。古典ヒストの運用がデプロイ横断で le を固定したがるのはこのためです。

ネイティブ(指数)ヒストグラムの原理

古典ヒストの根本問題は「バケット境界を事前に固定する」点でした。ネイティブヒストグラム(Prometheusのexponential histogram、OpenTelemetryのexponential histogramに対応)は、境界を 指数的に自動生成 することでこれを解決します。

核心は、バケット境界を base^i の形で定義することです。base = 2^(2^-schema) と置き、各バケットの上側境界を base^i(i は整数のインデックス)とします。schema パラメータが解像度を決め、schema を1上げるとバケット数が倍になり相対精度が倍に細かくなります。観測値 v が来たら、i = ceil(log(v) / log(base)) でバケットインデックスを直接計算し、そのバケットのみをインクリメントします。

schema=2 のとき base = 2^(2^-2) = 2^0.25 ≒ 1.1892
バケット上側境界: ..., 1.000, 1.189, 1.414, 1.682, 2.000, ...
  各バケットの相対幅 = base - 1 ≒ 0.189(全域で一定)
観測 v=1.5 → i = ceil(log(1.5)/log(1.189)) → (1.414, 1.682] のバケットへ

この設計の効果は決定的です。バケット幅が値に比例するため、全域でバケットの相対幅が一定 になります。小さい値は狭いバケットで細かく、大きい値は広いバケットで粗く刻まれ、「値に対する相対誤差」が値域によらず一定に保たれます。古典ヒストのように裾で誤差が膨らむことがありません。しかもバケットは 観測された範囲だけ動的に生成 されるので、事前の le 設計が不要で、想定外に大きい値が来ても自動でバケットが伸びます。

ネイティブヒストは系列数も劇的に減らす

古典ヒストは le ごとに別系列でしたが、ネイティブヒストは1つのサンプル内に全バケット(スパース表現で空でないものだけ)を格納します。これにより時系列の本数が激減し、基数問題が大幅に緩和されます。同じ解像度の古典ヒストが数百系列を要する場面でも、ネイティブヒストは概念上1系列で済みます。記録・転送効率は/devops/observability-pillars-internals/で扱う収集パイプライン全体の負荷にも効きます。

DDSketch:相対誤差保証付き分位点

ネイティブヒストグラムと同じ「指数バケット」の発想を、分位点の相対誤差を理論保証 する形で定式化したのがDDSketch(Distributed Distribution Sketch)です。相対精度パラメータ alpha を与えると、推定分位点 q-hat は真値 q に対し |q-hat - q| <= alpha × q を満たすことが保証されます。例えば alpha=0.01 なら、どの分位点でも誤差は真値の1%以内です。

仕組みは指数バケットそのものです。gamma = (1 + alpha) / (1 - alpha) と置き、バケットインデックスを i = ceil(log(v) / log(gamma)) で定めます。各バケットの相対幅が 2 × alpha 程度に保たれるため、どのバケットから読んだ分位点も真値に対して相対 alpha 以内に収まります。保証が「相対」である点が肝 で、p50だろうとp999だろうと、また値が1msだろうと10秒だろうと、同じ相対精度が約束されます。古典ヒストが特定の値域でしか高精度を出せなかったのと対照的です。

観点古典Prometheusヒストネイティブ/指数ヒストDDSketch
バケット境界事前固定のle(手動設計)base^i で自動生成gamma^i で自動生成
誤差の性質バケット内を線形補間・裾で増大相対幅が全域一定相対誤差 alpha を理論保証
値域の前提観測前にレンジを設計不要・動的に伸縮不要・動的に伸縮
系列数(基数)le ごとに1系列で増えやすいスパースで激減実装依存だが省メモリ
マージle 一致時のみ加算可能schema 一致時に加算可能gamma 一致時に加算可能
分位点の精度保証なし(補間近似)相対精度(schema依存)相対 alpha を厳密保証

DDSketchもバケット件数の加算でマージできますが、条件は古典ヒストと同型です。相対精度パラメータ(gamma、すなわち alpha)が一致するスケッチ同士でないと正しく合算できません。alpha の異なるスケッチを混ぜると、粗い側に合わせた再量子化が要ります。

どの方式でも「マージ前にpXを引く」のは禁則

ネイティブヒストもDDSketchも、誤差を抑えるのはあくまで「分布表現を保ったまま合算し、最後に分位点を引く」運用が前提です。各サーバーでp99を先に計算してから平均・最大を取れば、相対誤差保証は崩壊し、加算不能問題が再発します。保証されるのは「正しくマージしてから読む」ときの誤差だけで、計算順序を誤れば最新のスケッチでも無意味な数字になります。

運用への落とし込み

  • 古典ヒストはleを裾に密に: 監視したいSLO境界(例p99の想定域)の周辺に le を細かく置く。+Inf 直下が広いと裾の分位点が壊れる。
  • 裾を測るならネイティブヒスト/DDSketchへ: 相対誤差が全域一定なので、p999のような極端な分位点でも値域に依存せず安定する。事前のレンジ設計も不要。
  • マージ条件を揃える: 古典なら le、ネイティブなら schema、DDSketchなら alpha を系列間で統一する。揃っていない分布表現は合算できない。
  • 計算順序を守る: 必ず「合算 → 分位点」。avg(histogram_quantile(...)) のような「分位点の平均」を作らない。
  • +Inf超えに注意: 分位点が最大バケットを超えると推定不能。総件数と最終バケットの乖離を監視し、レンジ設計の不備を検知する。

まとめ

  • 古典的Prometheusヒストグラムは 事前固定の累積バケット で、histogram_quantileバケット内を線形補間 する。真の分布が非線形である以上、誤差は構造的で、特に 裾の粗いバケット+Inf 超え で破綻する。
  • 誤差を支配するのは バケット幅le設計。観測前に分布を知っていないと適切な境界を引けず、裾の精度が出ない。
  • ネイティブ(指数)ヒストグラムとDDSketch は境界を base^igamma^i で自動生成し、全域で相対誤差を一定 に保つ。DDSketchは相対誤差 alpha を理論保証し、値域や分位点位置に依存しない。
  • どの方式も マージ可能だが、境界スキーム(le/schema/alpha)の一致が前提。そして「合算してから分位点を引く」順序を守って初めて誤差保証が意味を持つ。

DevOps/インフラ Article

ヒストグラムと分位点推定の内部(Prometheus vs Native)を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

Prometheus

比較で見る軸

難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6

導入後に効く点

ネイティブ(指数)ヒストグラムとDDSketchはバケット幅を値に比例させ、全域で一定の相対誤差を保証する。バケットを動的に生成するため事前のle設計が不要で、分位点の精度が値域に依存しない。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
DevOps/インフラ
タグ数
6

判断チェックリスト

  • 自社の用途が「Prometheus / ヒストグラム」に近いか確認する。
  • 強みである「古典的Prometheusヒストグラムはバケット境界を事前固定し、histogram_quantileはバケット内を線形補間する。真の分布が線形でない以上、誤差は構造的に避けられず、特に境界の粗い裾で大きくなる。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

Prometheusヒストグラム分位点オブザーバビリティメトリクスPrometheusヒストグラム分位点