- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — 「stable」という言葉が実際に変えたこと
- クラシックヒストグラムが作る時系列の数
- スキーマ — バケット境界が設定ではなく公式である理由
- stableが保証していないもの
- プロトバフスクレイプのコスト — もともとstableの足を引っ張っていた問題
- NHCB — 計装コードを触らない経路
- 正直なトレードオフ — 移行が難しい理由
- それで、あなたはオンにすべきか
- おわりに
- 参考資料
はじめに — 「stable」という言葉が実際に変えたこと
Prometheusのネイティブヒストグラムは長らく待たれてきた機能です。公式仕様書の最初の文がその経緯を要約しています — 2022年11月に実験機能として導入され、これをサポートした最初のサーバーバージョンはv2.40.0で、--enable-feature=native-histogramsフラグでオンにする必要がありました。そして同じ段落にこう書かれています。
Starting with v3.8.0, native histograms are supported as a stable feature.
3.8.0は2025年12月2日にリリースされました(GitHubリリース基準。CHANGELOGのヘッダー表記は11月28日)。3年以上「実験」のラベルを貼られていた機能が、ついにstableになったわけです。
ただ、本稿執筆時点でより重要な出来事が別にあります。3.13が2026年7月1日に出て、これが新しいLTSです。 そして今多くの組織が動かしているであろう3.5 LTSは2026年7月31日にサポートが終了します。 2週間後です。リリースサイクルのドキュメントのLTS表をそのまま転記するとこうなります。
Release Date End of support Status
Prometheus 2.53 2024-06-16 2025-07-31 End of life
Prometheus 3.5 2025-07-14 2026-07-31 Supported
Prometheus 3.13 2026-07-01 2027-07-31 Supported
TBD 2027-06 2028-07-31 Upcoming
この2つの事実を重ねると結論が一つ出ます。3.5 LTS(2025年7月)は、ネイティブヒストグラムがstableになる4か月前に切られたバージョンです。そしてLTSのドキュメントにはサポート範囲から外れる項目が明記されています — API安定性保証でunstableに分類されるもの、実験的機能、そしてOpenBSDサポートです。つまり3.5 LTSに乗っていたなら、ネイティブヒストグラムはそもそもLTSサポート範囲外の実験機能であり、3.13はこの機能がstable状態で入った最初のLTSです。LTSトラックに乗る組織にとって、今が「いつか見よう」ではなく実際の決定の時点である理由がここにあります。
クラシックヒストグラムが作る時系列の数
なぜこの機能が求められたのかから押さえましょう。問題はカーディナリティです。
クラシックヒストグラムは露出フォーマット上はヒストグラムのように見えますが、収集される瞬間に複数のfloat時系列に分解されます。仕様書はこの分解をこう説明します — summaryと(クラシック版の)histogramは収集時にfloat構成要素へ分解され、両タイプともsumとcountの構成要素を持ち、histogramにはさらに多数のbucketサンプルが付く、と。
実際どうなるかは仕様書が挙げる例が的確です。rpc_latency_secondsというヒストグラム一つは、クラシックで収集するとrpc_latency_seconds_sum、rpc_latency_seconds_count、そして異なるleラベルを持つ多数のrpc_latency_seconds_bucket時系列になります。バケットがN個なら時系列はN+2個です。ネイティブヒストグラムで収集すれば、名前どおりrpc_latency_secondsの時系列1つだけです。
ラベルでパーティショニングすると、この差は掛け算になります。仕様書の説明を借りると、すでに重いクラシックヒストグラムにラベルを付けて100個のヒストグラムができれば、コストは100倍になります。ネイティブヒストグラムは事情が違います — パーティショニングすると個々のヒストグラムはそれぞれより少ないバケットしか埋めない傾向があるため(ステータスコード404を処理したリクエストだけを収めるヒストグラムはたいてい分布が狭い)、埋まったバケットの総数はヒストグラム数の倍数よりはるかに小さく増えます。仕様書が「ラベルでパーティショニングすることさえ、もはやずっと現実的だ」と書く根拠がこれです。
ここで数字を一つ作りたい誘惑がありますが、それはしません。Prometheus仕様書とGrafana Mimirのドキュメントは、ネイティブヒストグラムが「クラシックヒストグラムに比べてはるかに低いリソースコストで高い解像度を提供する」と記述していますが、これはベンダー・プロジェクトのドキュメントによる定性的な記述であって、条件が明示されたベンチマーク数値ではありません。節約幅は元々いくつのバケットを使っていたか、分布がどれだけ広いかに完全に左右されます — バケット5個のヒストグラムなら得るものはあまりありません。
スキーマ — バケット境界が設定ではなく公式である理由
ネイティブヒストグラムの核心的な発想は、バケット境界を人が決めるのではなく公式で決めることです。これを決める値がスキーマで、仕様書によればint8整数として現在有効な値は -53、および-4から+8まで です。-53はカスタムバケット境界(NHCB)用で、残りが標準の指数スキーマです。
標準スキーマnにおいてインデックスiの正のバケットの境界は次のように計算されます(仕様書はPython構文で表記しています)。
# 上限(含む): (2**2**-n)**i
# 下限(含まない): (2**2**-n)**(i-1)
ここで重要な性質が出てきます。スキーマnはスキーマn+1の半分の解像度であり、したがってn+1のヒストグラムは隣接バケットを統合してnへ変換できます。これが標準スキーマ同士が相互にマージ可能な理由であり、複数インスタンスのヒストグラムを合算するクエリが成立する根拠です。クラシックヒストグラムで異なるバケット境界を使う2つのサービスを合算できなかった問題が、ここでは消えます。
計装(instrumentation)側ではスキーマを直接選ぶのではなく、成長係数で指定します。GoクライアントのNativeHistogramBucketFactorがそれで、client_golangソースのコメントが正確な値を示しています — コストと精度の一般的に良いトレードオフは1.1で(各バケットは直前のバケットより最大10%広い)、この値は2の累乗区間ごとに8バケットに分かれる結果になります(1と2の間に8個、2と4の間に8個、4と8の間に8個…)。実際の係数は2^(2^-n)で計算され、nは-4から8の整数のうち、指定値以下で最大の係数を与える値が選ばれます。つまり取り得る最小の係数は約1.00271(= 2^(2^-8))です。
ゼロ付近は別扱いです。指数バケットはゼロに無限に近づいていくため、絶対値が閾値以下の観測値はすべて「ゼロバケット」にまとめます。Goクライアントのデフォルト閾値はDefNativeHistogramZeroThreshold = 2.938735877055719e-39です。
そして保存はスパース(sparse)表現です — 空のバケットは(ほぼ)無料です。これが「float64の全範囲をカバーする」という言葉が破綻しない理由です。カバーする範囲が広くても、実際に埋まったバケットだけがコストを払います。
stableが保証していないもの
ここからがこの記事の本論です。stable宣言は、サーバーがこのデータ型を扱う方法がv4まで壊れないという意味であって、「もうただオンにすればいい」という意味ではありません。仕様書自身がstable宣言のすぐあとに但し書きを付けています。
依然として明示的にオンにする必要があります。 スクレイプはscrape_native_histograms設定で有効化する必要があり、Remote-Write送信はsend_native_histogramsで別途オンにする必要があります。3.8では既存のfeature flagがこの設定をtrueにする効果しか残っていませんでしたが、3.9からフラグは完全なno-opになったため、設定を明示する必要があります。仕様書の括弧書きがロードマップを教えてくれます — v4からは両方の設定のデフォルトがtrueになります。つまり今は「stableだがopt-in」、v4は「デフォルト」という中間区間です。
露出はプロトバフでのみ可能です。 これが実務で最もよくつまずく点です。仕様書は、クラシックなPrometheusテキスト形式はネイティブヒストグラム用に拡張されておらず、その計画もないと明言しています。OpenMetricsテキスト形式のサポートには提案文書がありますが、実装状態は依然としてNot implementedです。OpenMetricsのプロトバフ側サポートPR(OpenMetrics#256)は2026年3月19日にマージされずクローズされましたが、理由が興味深いところです — 実質的にはブランディングの整理です。Prometheusのプロトバフ露出形式がすでにネイティブヒストグラムを含む全機能をサポートしているため、そちらを正式なプロトバフ形式とし、OpenMetricsはテキストベースの形式を指すことにする、という決定でした。まとめると2026年7月現在、ネイティブヒストグラムを露出する道はクラシックなPrometheusプロトバフ形式ひとつだけです。
クライアントライブラリはまだ追いついていません。 この部分は自分で確認する価値があります。client_golangの最新リリースはv1.23.2(2025年9月5日)ですが、HistogramOptsのコメントには今もこの文がそのまま残っています。
NOTE: Native Histograms are still an experimental feature. Their behavior might still change without a major version bump. Subsequently, all NativeHistogram... options here might still change their behavior or name (or might completely disappear) without a major version bump.
v1.23.2は3.8.0(2025年12月)より前に出たリリースなので、文言が古びたものだと見ることもできますが、実際mainブランチにも同じ文がまだ残っています。要点はstable宣言の主語がサーバーであることです。サーバーのデータモデルと保存・クエリの挙動はstableですが、計装ライブラリのオプションAPIが同じ保証を文書上で与えているわけではありません。
Pythonはもっと単純です — client_pythonのネイティブヒストグラム実装のissueはまだオープンのままです。テキスト形式の提案書が挙げる動機の一つがまさにこれです。Pythonクライアントのような一部のライブラリは、ネイティブヒストグラムを露出するためだけにプロトバフ依存を追加したくない、というものです。
Kubernetesはアルファです。 Kubernetesのコンポーネントメトリクスにネイティブヒストグラムが入ったのはv1.36(2026年4月22日リリース)からですが、NativeHistogramsのFeature Gateの状態はアルファ、デフォルトfalseです。適用対象はkube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxyで、コンポーネントごとに別々にオンにできます。オンにするとクラシックとネイティブを同時に露出するdual exposition方式になり、どちらを受け取るかはリクエストのAcceptヘッダーが決めます。
プロトバフスクレイプのコスト — もともとstableの足を引っ張っていた問題
ここに興味深い経緯があります。scrape_native_histogramsをオンにするとコンテンツネゴシエーションが変わり、そのスクレイプ設定がプロトバフを優先するようになります。仕様書が示すとおり、デフォルトが指定されていない場合、有効な優先順位は次のように変わります。
[ PrometheusProto, OpenMetricsText1.0.0, OpenMetricsText0.0.1, PrometheusText0.0.4 ]
問題は、これがそのスクレイプ設定のすべてのターゲットに適用される点でした。ネイティブヒストグラムをいくつか受け取るために、スクレイプ全体をプロトバフに切り替える羽目になります。Issue #14668がこの問題を正面から扱っており、本文の診断はこうでした — プロトバフのパースはメモリのチャーンを多く発生させ、大半はGCのCPU使用量増加として現れ、使用パターンによってはCPUが大きく増える可能性があり、実際の事例で2倍の増加を見たことがある、というものです。そして同じ本文にこの文が太字で書かれています。
(Or in other words: This issue needs to get resolved to declare native histograms a stable feature.)
つまりこの性能問題は、stable宣言の前提条件でした。issueは2025年2月13日にcompletedとしてクローズされ、その後2025年12月にstable宣言が出ました。クローズ前の議論でメンテナーは、最適化後の現実的なベンチマークでテキストパーサー比3倍速く、割り当てメモリは10%少ないという結果を報告していました。
ここで正直であるべき点があります。仕様書には今なお、テキストベース形式でスクレイプする方がプロトバフより一般に大幅にリソースを使わない、というnoteが残っており、根拠としてまさにこのissueをリンクしています — ところがそのissueはcompletedでクローズされています。ドキュメントのnoteは最適化前の状態を反映した古い記述に見えます。そのため「プロトバフスクレイプは依然として2倍高コストだ」と言うのも、「もう完全に解決した」と言うのも、現在の資料からすると言い過ぎです。自分の環境でCPUを直接測るしかありません。 オンにする前後でスクレイプ対象プロセスとPrometheus自身のCPUを比較してください。
一方でこのissueが残した実用的な遺産もあります。露出範囲を絞る手段です。scrape_protocolsを直接指定すれば、scrape_native_histogramsがオンになっていても特定のターゲットを非プロトバフ形式に強制できます。そして3.13にはリレーベリングでターゲットごとに上書きできる内部ラベルが追加されました — __scrape_native_histograms__と__always_scrape_classic_histograms__(#18929)、そして__convert_classic_histograms_to_nhcb__(#18840)です。スクレイプ設定を分割しなくても、ネイティブヒストグラムを露出するターゲットにだけ正確にオンにできるということです。
NHCB — 計装コードを触らない経路
すべてのクラシックヒストグラムをネイティブへ計装し直すのは大きな作業です。そこで中間の経路があります。スキーマ-53、つまりカスタムバケットを持つネイティブヒストグラム(NHCB)です。
convert_classic_histograms_to_nhcbスクレイプオプションをオンにすると、Prometheusが収集時にクラシックヒストグラムをNHCBへ変換して取り込みます。計装コードはそのままに、保存効率だけを得る方式です。仕様書の表現ではNHCBの核心的な利点は保存コストが一般に大幅に安いことで、特にバケットを追加する限界コストが低いため、バケットの多いクラシックヒストグラムも現実的なコストで収集できます。
代わりに代償ははっきりしています。NHCBは他との統合(マージ)が根本的に制限されます。仕様書はこう警告します — 異なるバケットレイアウト間の自動調整は境界が正確に一致するものだけを残すため、大半の境界が一致していれば使える結果になりますが、バケットレイアウトが任意に異なるとどの境界も一致せずオーバーフローバケット一つだけが残るヒストグラムになり得ます。そのため仕様書はスキーマ-53を特定の用途に限り、情報に基づいた判断としてのみ使うよう勧めています。サービスごとにバケット境界がまちまちなコードベースでNHCBに全部押し込んで合算クエリを回すと、静かに使い物にならない結果を受け取ることになります。
正直なトレードオフ — 移行が難しい理由
仕様書の移行セクションは問題を3点に整理しています。ここがこの機能導入の実際の難易度です。
第一に、クエリの書き方が違います。 ほとんどの場合、変更は最小限で直感的ですが、厄介なエッジケースがあって信頼できる自動変換は難しい、というのが仕様書の記述です。つまりダッシュボードとアラートルールは手で移す必要があります。
第二に、クラシックとネイティブは互いに集計されません。 これが最も痛い点です。ある時点でクラシックからネイティブへ切り替えると、その切り替え時点をまたぐダッシュボードを作るのが難しくなり、切り替え時点を含むレンジベクトルは必然的に不完全になります — クラシックを選べば前半分のデータだけ、ネイティブを選べば後半分のデータだけが入ります。仕様書が挙げる例は具体的です。
histogram_quantile(0.9, rate(rpc_duration_seconds[1d]))
1日分の90パーセンタイルレイテンシを計算するこのクエリは、ネイティブヒストグラムを最低でも1日以上収集していなければ、それより短い区間しかカバーしません。そのため仕様書の勧告は保守的です — クラシックとネイティブをしばらく並行収集すること。1か月並行収集していれば、1か月より先を見ないダッシュボードは切り替え時点を気にせずそのまま移行できます。並行収集はalways_scrape_classic_histogramsオプションでオンにします。クラシックは接尾辞付きの時系列群として、ネイティブは名前そのままの時系列一つとして入るため、TSDB上で衝突しません。もちろんその間は両方のコストを払います。
第三に、任意のバケット境界を置けません。 これがSLOをやっているチームには決定的になり得ます。クラシックヒストグラムは関心のある地点にちょうど境界を置くようカスタム設計できます — 例えば「300ms以内の応答比率」がSLOなら、境界を300msに打っておけばその比率は推定ではなく正確な値になります。標準スキーマのネイティブヒストグラムは解像度は高いものの、境界を任意の値に置くことはできません。Grafana Mimirのドキュメントも同じ点を欠点として明記しています — SLO定義において特に重要な閾値のような任意のバケット境界を設定する方法がなく、与えられた閾値の上下の観測比率は一般に補間(interpolation)で推定する必要がある、というものです。仕様書はこうしたケースについて「ネイティブヒストグラムでのユーザー体験が実際に悪化しうる」と率直に書いており、解決策としてそのヒストグラムはただ移行せずに残すという選択肢を提示しています。
ここにもう一つ加わります。バケットは最初に埋まるときに動的に生成されるため、想定より広い分布が入ってくると想定より多くのバケットができ、メモリをより多く使います。仕様書は、観測値が外部から操作可能な場合これがメモリ枯渇によるDoSベクターになり得るとまで警告しています。ところがGoクライアントのNativeHistogramMaxBucketNumberのデフォルトは0、つまり無制限です(仕様書もこの点をTODOとして残しています)。よく使われる値は160で、これはOTelの指数ヒストグラムのデフォルト値と同じです。収集側でも防御できます — スクレイプ設定のnative_histogram_bucket_limit(個々のヒストグラムのバケット数上限)とnative_histogram_min_bucket_factor(成長係数の下限)がそれで、どちらも超過時は解像度を下げて合わせます。ただしNHCBがバケット数上限を超えると解像度を下げられないため、スクレイプが失敗します。
それで、あなたはオンにすべきか
今オンにする価値がある場合
- バケットが多い(数十個の)クラシックヒストグラムを使っていて、ラベルのパーティショニングでカーディナリティがすでに痛い場所。節約幅は元のバケット数に比例するため、ここが最も価値を発揮します。
- レイテンシ分布のように、範囲を事前に知るのが難しい観測値。バケット境界を推測して打ち込む作業そのものがなくなることが実質的な利得です。
- Goで計装していて、サーバーを3.13 LTSへ上げる計画がある場所。今のスタックでは経路が最も滑らかです。
- 計装コードを触れないが保存コストを減らしたい場所 — この場合はNHCB変換だけをオンにする選択肢があります。ただしマージの制約を先に理解した上で使ってください。
まだの場合
- SLO閾値にぴったり合わせたバケット境界に依存しているヒストグラム。補間による推定に変わった瞬間、SLO計算の性質が変わります。これはそのままクラシックで残してください。
- 計装がPythonクライアントの場所。まだ未実装なので選択肢がありません。
- バケットが数個しかないヒストグラム。得るものがあまりない一方、移行コストはそのままかかります。
- スクレイプ対象の大半がネイティブヒストグラムを露出していないのに、スクレイプ設定単位で全部プロトバフへ切り替わる構成。3.13のターゲット別リレーベリングで範囲を絞るか、
scrape_protocolsで制御してください。 - Kubernetesのコンポーネントメトリクスが目的の場所。1.36アルファでデフォルト無効なので、本番クラスターでアルファのFeature Gateをオンにする判断を別途下す必要があります。
おわりに
まとめるとこうなります。ネイティブヒストグラムは実際にstableになり(3.8.0、2025年12月)、3.13がこの機能をstableとして含む最初のLTSであり(2026年7月1日)、3.5 LTSは7月31日に終了します。データモデルは本当に良いものです — N+2個の時系列が1個になり、バケット境界を推測しなくてよくなり、標準スキーマ同士が統合できます。
ただし「stable」という言葉の主語を正確に読む必要があります。それはサーバーがこの型を扱う方法がv4まで壊れないという意味であって、エコシステムが準備できているという意味ではありません。露出は今もプロトバフひとつだけで、Goクライアントは自身のオプションを今も実験的と文書化しており、Pythonは未実装で、Kubernetesはアルファです。v4でデフォルトがオンになる方向は明らかですが、その間の区間を今渡る人は各自これらの穴を確認する必要があります。
だから実務的な手順はこうです。3.13へのアップグレードはどのみち7月31日のためにやらねばならないことなので、まずそれをやり、ネイティブヒストグラムは別の決定として扱ってください。そのうえでカーディナリティが最も痛いヒストグラムを数個だけ選んで並行収集から始め、プロトバフ切り替えのCPUへの影響を自分で測り、ダッシュボードを移す時間を確保できるだけ並行期間を長く取ってください。3年かかってstableになった機能です。2週間ですべて移す理由はありません。
参考資料
- Native Histograms — Prometheus公式仕様書 (データモデル、スキーマ、スクレイプ設定、移行時の考慮事項)
- Prometheus CHANGELOG (3.8.0のstable宣言、3.9.0のフラグno-op化、3.13.0のリレーベリングラベル)
- Prometheus Long-term support — LTS一覧とサポート除外範囲
- Release 3.13.0 / 2026-07-01
- prometheus#14668 — プロトバフスクレイプの性能問題とstable宣言の前提条件
- client_golang histogram.go — NativeHistogramBucketFactorなどのオプション定義とコメント
- client_python#918 — ネイティブヒストグラム実装のissue(オープン)
- Native Histograms Text Format 提案書(実装状態: Not implemented)
- OpenMetrics#256 — プロトバフ仕様サポートPR(2026年3月19日、未マージでクローズ)
- Native Histogram Support for Kubernetes Metrics (v1.36, アルファ)
- Send native histograms to Grafana Mimir — メリット・デメリットと移行ガイド