ネイティブヒストグラム

ネイティブヒストグラムは、2022年11月に実験的な機能として導入されました。これらはPrometheusスタックのほぼすべての部分に影響する概念です。ネイティブヒストグラムをサポートするPrometheusサーバーの最初のバージョンはv2.40.0でした。このサポートは、機能フラグ `--enable-feature=native-histograms` を介して有効にする必要がありました。(TODO: これは現在のリリースv2.55およびv3.00でも同様です。安定版がリリースされたら、このセクションを更新してください。)

ネイティブヒストグラムに関連する変更の広範な性質のため、それらの変更のドキュメントと基本概念の説明は、様々なチャネル(影響を受けるPrometheusコンポーネントのドキュメント、ソースコード内のドキュメントコメント、時にはソースコード自体、設計ドキュメント、カンファレンストークなど)に広く分散しています。このドキュメントは、これらすべての情報を集約し、統一された文脈で簡潔に提示することを目的としています。このドキュメントは、既存の詳細なドキュメントを再記述するよりもリンクすることを推奨しますが、他の情報源を参照しなくても理解できる十分な情報を含んでいます。とはいえ、このドキュメントは初心者向けの入門書としては適しておらず、開発者のニーズに焦点を当てているわけでもないことに注意してください。前者については、ヒストグラムとサマリーに関するベストプラクティス記事の更新版を提供する予定です。(TODO: そしてブログ投稿、あるいは一連の投稿も。) 後者については、Carrie Edward氏のPrometheusネイティブヒストグラム開発者ガイドがあります。

公式な仕様はそれぞれの文脈(例:OpenMetricsの変更は一般的なOpenMetrics仕様で規定される)で行われるべきですが、このドキュメントの一部は仕様の形式を取っています。これらの部分では、キーワード「MUST」(しなければならない)、「MUST NOT」(してはならない)、「REQUIRED」(必須)、「SHALL」(するものとする)、「SHALL NOT」(してはならない)、「SHOULD」(すべきである)、「SHOULD NOT」(すべきではない)、「RECOMMENDED」(推奨される)、「MAY」(してもよい)、「OPTIONAL」(任意)は、RFC 2119で記述されている通りに使用されます。

このドキュメントには、まだ多くのTODOが含まれています。ほとんどの場合、これらはこのドキュメントの不完全さを指すだけでなく、より重要なこととして、実装の不完全さや未解決の質問を指しています。現在のところ、これは本質的に生きているドキュメントであり、実装と仕様が追いつくにつれて更新される予定です。

はじめに

ネイティブヒストグラムの核となる考え方は、ヒストグラムをPrometheusデータモデルにおける第一級市民として扱うことです。ヒストグラムを「ネイティブな」サンプルタイプに昇格させることは、以下に挙げる主要な特性の基本的な前提条件であり、ネイティブヒストグラムという名前の選択を説明しています。

ネイティブヒストグラムが導入される前は、すべてのPrometheusサンプル値は64ビット浮動小数点値(短くはfloat64または単にfloat)でした。これらのfloat値は、ゲージまたはカウンターを直接表現できます。Prometheusメトリックタイプであるサマリーと(従来のバージョンの)ヒストグラムは、エクスポジションフォーマットで存在しますが、取り込み時にfloatコンポーネントに分解されます。これら両方のタイプに対して合計カウントコンポーネント、サマリーに対して多数のパーセンタイルサンプル、そして(従来の)ヒストグラムに対して多数のバケットサンプルです。

ネイティブヒストグラムでは、新しい構造化されたサンプルタイプが導入されます。単一のサンプルは、以前に知られていた合計カウントに加え、動的なバケットセットを表します。これは取り込みに限定されず、PromQL式も以前はfloatサンプルしか返せなかった場所で、新しいサンプルタイプを返すことができます。

ネイティブヒストグラムには、以下の主要な特性があります

  1. スパースバケット表現により、空のバケットに対する(ほぼ)ゼロのコストを実現。
  2. float64値の全範囲をカバー。
  3. 計測中のバケット境界の構成不要。
  4. シンプルな設定パラメータに従って動的な解像度を選択。
  5. 高度な指数バケット化スキーマにより、それらのスキーマを使用するすべてのヒストグラム間でマージ可能性を確保。
  6. エクスポジションとストレージの両方で効率的なデータ表現。

これらの主要な特性は、標準のバケット化スキーマで完全に実現されています。これらの特性の一部しか備えていない、異なるトレードオフを持つ他のスキーマも存在します。詳細は以下のスキーマセクションをご覧ください。

既存の「従来の」ヒストグラムと比較して、ネイティブヒストグラム(標準のバケット化スキーマを使用)は、観測値の任意の範囲にわたってより高いバケット解像度を、より低いストレージおよびクエリコストで、ほとんど設定なしで実現します。ラベルによるヒストグラムのパーティショニングも、はるかに手頃になりました。

疎な表現(上記のリストの特性1)がネイティブヒストグラムの他の多くの利点にとって非常に重要であるため、設計プロセスの初期段階ではスパースヒストグラムネイティブヒストグラムの一般的な名称でした。しかし、指数バケット化スキーマやバケットの動的な性質などの他の主要な特性も非常に重要ですが、スパースヒストグラムという用語では全く捉えられていません。

設計ドキュメント

これらは、ネイティブヒストグラムの開発を導いた設計ドキュメントです。一部の詳細は現在廃止されていますが、根底にある概念とそれらがどのように発展したかをかなりよく説明しています。

カンファレンストーク

ネイティブヒストグラムについて学ぶためのよりわかりやすい方法は、以下のカンファレンストークを視聴することです。導入として、これらのトークを視聴し、その後このドキュメントに戻ってすべての詳細と専門的な側面を学ぶのがよいでしょう。

用語集

  • ネイティブヒストグラムは、このドキュメントで説明する完全なヒストグラムを表す新しい複合サンプルタイプのインスタンスです。文脈が十分に明確な場合、以下では単にヒストグラムと呼ばれることがよくあります。
  • 従来のヒストグラムは、固定バケットを持つヒストグラムを表す古いサンプルタイプのインスタンスであり、以前は単にヒストグラムと呼ばれていました。エクスポジションフォーマットではそのように存在しますが、Prometheusへの取り込み時に多数のfloatサンプルに分解されます。
  • スパースヒストグラムは、ネイティブヒストグラムの古い、現在は非推奨の名称です。この名称は、古いドキュメントで時折見られるかもしれません。スパースバケットは、ネイティブヒストグラムのバケットにとって意味のある用語です。

データモデル

このセクションでは、ネイティブヒストグラムの一般的なデータモデルについて説明します。可能な限り実装の詳細を避けます。これには用語も含まれます。例えば、このセクションで説明されているリストは、protobuf実装ではrepeated messageに、Go実装では(おそらく)sliceになります。

一般的な構造

従来のヒストグラムと同様に、ネイティブヒストグラムには観測のカウントのフィールドと観測の合計のフィールドがあります。さらに、以下のコンポーネントが含まれており、これらは以下の専用セクションで詳しく説明されています

  • インデックスiを持つ任意のバケットの境界を決定する方法を識別するためのスキーマ
  • 正および負の観測値に対してミラーリングされた、インデックス付きバケットのスパース表現。
  • ゼロに近い観測値をカウントするためのゼロバケット
  • (おそらく空の)カスタム値のリスト。
  • エグゼンプラー.

フレーバー

どのネイティブヒストグラムも、2つの独立した次元に沿って特定のフレーバーを持ちます

  1. カウンター対ゲージ:通常、ヒストグラムは「カウンター的」、つまり各バケットが観測値のカウンターとして機能します。しかし、「ゲージ的」ヒストグラムも存在し、そこでは各バケットがゲージであり、ある時点での任意の分布を表します。ゲージヒストグラムの概念は、以前にOpenMetricsによって従来のヒストグラムのために導入されました。
  2. 整数対浮動小数点(略してfloat):ヒストグラムの明らかなユースケースは観測値をカウントすることであり、各バケット内(ゼロバケットを含む)および観測値の合計カウントにおいて、0以上の整数値(略してuint64)として表されます。しかし、「重み付けされた」または「スケールされた」ヒストグラムにつながる特定のユースケースでは、これらの値のすべてが64ビット浮動小数点数(略してfloat64)として表現されます。いずれの場合でも、観測値の合計はfloat64であることに注意してください。

floatヒストグラムは、例えば、観測値がヒストグラムの異なるバケットに該当した秒数をカウントするなど、「重み付けされた」観測値の直接計測で時折使用されます。しかし、floatヒストグラムのより一般的なユースケースはPromQL内です。PromQLは通常float値のみを処理するため、PromQLエンジンはTSDBから取得したすべてのヒストグラムを最初にfloatヒストグラムに変換し、記録ルールを介してTSDBに保存されるすべてのヒストグラムはfloatヒストグラムになります。そのようなヒストグラムが実質的に整数ヒストグラムである場合(すべての非sumフィールドの値がuint64として正確に表現できるため)、TSDB実装はストレージ効率を向上させるためにそれらを整数ヒストグラムに変換してもよい(MAY)。(Prometheus v3.00現在、Prometheus内のTSDB実装はこのオプションを利用していません。) ただし、カウンターヒストグラムに適用される最も一般的なPromQL関数は`rate`であり、これは通常、非整数値を生成するため、記録ルールの結果は、いずれにせよ非整数値のfloatヒストグラムとなることが多いことに注意してください。

ネイティブヒストグラムを明示的に整数ヒストグラムとfloatヒストグラムとして扱うことは、簡潔さのためにスタック全体で常にfloatとして扱われる従来の単純な数値サンプルとは顕著な逸脱です。

ヒストグラムをより複雑に扱う主な理由は、protobufベースのエクスポジションフォーマットにおける容易な効率向上です。Protobufは整数にvarintエンコーディングを使用しており、これにより追加の圧縮レイヤーを必要とせずに小さな整数値のデータサイズを削減します。この利点は、一般的に小さな整数値をもたらす整数バケットのデルタエンコーディングによって増幅されます。対照的に、floatはprotobufでは常に8バイトを必要とします。実際には、整数ヒストグラム内の多くの整数は1バイトに収まり、ほとんどは2バイトに収まるため、protobufエクスポジションフォーマットにおける整数ヒストグラムの明示的な存在は、多くのバケットを持つヒストグラムに対して最大8倍のデータサイズ削減に直接つながります。これは、計測されたターゲットによって公開されるヒストグラムの圧倒的多数が整数ヒストグラムであるため、特に重要です。

同様の理由により、RAMおよびディスク上の整数ヒストグラムの表現は、一般的にfloatヒストグラムよりも効率的です。ただし、これはエクスポジションフォーマットの利点よりも重要ではありません。Prometheusはfloatに対してGorillaスタイルのXORエンコーディングを使用しており、これによりサイズが削減されますが、整数に使用されるdouble-deltaエンコーディングほどではありません。さらに重要なことは、実装は、実質的に整数値であるヒストグラムフィールドに対して内部的に整数表現を使用することを常に決定できることです(上記参照)。(歴史的注記: Prometheus v1は、floatサンプルの圧縮を改善するためにまさにこのアプローチを使用しており、Prometheus v3は将来的にこのアプローチを再び採用する可能性が十分にあります。)

カウンターヒストグラムでは、観測値の合計カウントと各バケット内のカウントは、個別にPrometheusのカウンターのように振る舞います。つまり、カウンタリセット時にのみ減少します。しかし、観測値が負の値である結果として、観測値の合計が減少する可能性があります。PromQLの実装は、ヒストグラム全体に基づいてカウンタリセットを検出しなければならない(MUST)(詳細は以下のカウンタリセットに関する考慮事項セクションを参照)。(これは従来のヒストグラムやサマリーの合計コンポーネントについても常に問題でした。これまでのアプローチは、そのような場合に合計のカウンタリセット検出が黙って機能しなくなることを受け入れることでした。幸い、負の観測値はPrometheusのヒストグラムやサマリーにとって非常にまれなユースケースです。)

スキーマ

スキーマは、8ビットサイズの符号付き整数値(略してint8)です。これは、バケット境界の計算方法を定義します。現在有効な値は-53、および-4から+8までの範囲(両端を含む)です。将来、さらに多くのスキーマが追加される可能性があります。-53は、いわゆるカスタムバケット境界、または略してカスタムバケットのスキーマであり、他のスキーマ番号は異なる標準指数スキーマ(略して標準スキーマ)を表します。

標準スキーマは互いにマージ可能であり、一般的なユースケースには推奨される(RECOMMENDED)。スキーマ番号が大きいほど、解像度が高くなります。スキーマnはスキーマn+1の半分の解像度を持ちます。これは、スキーマn+1のヒストグラムを、隣接するバケットをマージすることでスキーマnのヒストグラムに変換できることを意味します。

任意の標準スキーマnの場合、インデックスiを持つバケットの境界は以下のように計算されます(Python構文を使用)

  • 正のバケットの上限(inclusive):`(2**2**-n)**i`
  • 正のバケットの下限(exclusive):`(2**2**-n)**(i-1)`
  • 負のバケットの下限(inclusive):`-((2**2**-n)**i)`
  • 負のバケットの上限(exclusive):`-((2**2**-n)**(i-1))`

iは負の値でもよい整数です。

上記のルールには、float64として表現可能な最大および最小の有限値(以下では`MaxFloat64`および`MinFloat64`と呼ぶ)、ならびに正および負の無限大値(`+Inf`および`-Inf`)に関する例外があります。

  • `MaxFloat64`を含む正のバケット(上記の境界式による)は、上限値(inclusive)が`MaxFloat64`となります(上記の式で計算される、float64をオーバーフローさせるはずの制限ではなく)。
  • 次の正のバケット(前の項目からのバケットに対するインデックスi+1)は、下限値(exclusive)が`MaxFloat64`で、上限値(inclusive)が`+Inf`となります。(これは正のオーバーフローバケットと呼ぶこともできます。)
  • `MinFloat64`を含む負のバケット(上記の境界式による)は、下限値(inclusive)が`MinFloat64`となります(上記の式で計算される、float64をアンダーフローさせるはずの制限ではなく)。
  • 次の負のバケット(前の項目からのバケットに対するインデックスi+1)は、上限値(exclusive)が`MinFloat64`で、下限値(inclusive)が`-Inf`となります。(これは負のオーバーフローバケットと呼ぶこともできます。)
  • 上記で説明した`+Inf`および`-Inf`バケットを超えるバケットは使用してはならない(MUST NOT)。

ゼロに近い値にはさらに例外があります。詳細は以下のゼロバケットセクションをご覧ください。

最低解像度の-4と最高解像度の8という現在の制限は、実用的な有用性に基づいて選択されています。さらに低いまたは高い解像度が必要になった場合、範囲の拡張が検討されます。ただし、52を超えるスキーマは、あるバケットから次のバケットへの成長因子が表現可能なfloat64数値間の差よりも小さくなるため、意味がありません。同様に、-9より小さいスキーマも、成長因子がfloat64として表現可能な最大のfloat値を超えるため、意味がありません。したがって、-9から+52までのスキーマ番号(両端を含む)は、将来の標準スキーマ(上記のバケット境界の式に従うもの)のために予約されており、他のいかなるスキーマにも使用してはならない(MUST NOT)。

スキーマ-53の場合、バケット境界は以下のカスタム値セクションで詳しく説明されているカスタム値によって明示的に設定されます。これにより、カスタムバケット境界を持つネイティブヒストグラム(または略してカスタムバケット、さらに略してNHCB)が生成されます。このようなヒストグラムは、従来のヒストグラムをネイティブヒストグラムとして表現するために使用できます。また、標準スキーマが特徴とする指数バケット化が、ヒストグラムで表現される分布にうまく合わない場合にも使用できます。異なるカスタムバケット境界を持つヒストグラムは、一般的に互いにマージできません。したがって、スキーマ-53は特定のユースケースにおいて情報に基づいた決定としてのみ使用すべきである(SHOULD)。

バケット

標準スキーマの場合、バケットは正のバケット用と負のバケット用の2つのリストとして表現されます。カスタムバケット(スキーマ-53)の場合、正のバケットリストのみが使用されますが、すべてのバケットのために再利用されます。

データが格納されていないバケットは、リストから除外してもよい(MAY)。(これが、バケットがしばしばスパースバケットと呼ばれる理由です。)

floatヒストグラムの場合、リストの要素はfloat64であり、バケットの母集団を直接表します。

整数ヒストグラムの場合、リストの要素は符号付き64ビット整数(略してint64)であり、各要素はリスト内の前のバケットに対するデルタとしてバケット母集団を表します。各リストの最初のバケットには絶対的な母集団が含まれます(これはゼロに対するデルタとしても見なすことができます)。

リスト内のバケットを前のセクションで定義されたインデックスにマッピングするために、いわゆるスパンの2つのリストがあります。1つは正のバケット用、もう1つは負のバケット用です。

各スパンは、符号付き32ビット整数(略してint32)であるオフセットと、符号なし32ビット整数(略してuint32)である長さのペアで構成されます。各リストの最初のスパンのみが負のオフセットを持つことができます。それは、対応するバケットリスト内の最初のバケットのインデックスを定義します。(NHCBの場合、インデックスは常に正であることに注意してください。詳細は以下のカスタム値セクションを参照。)長さは、バケットリストが始まる連続するバケットの数を定義します。続くスパンのオフセットは、除外された(したがってデータが格納されていない)バケットの数を定義します。長さは、除外されたバケットに続くリスト内の連続するバケットの数を定義します。

各スパンリスト内のすべての長さ値の合計は、対応するバケットリストの長さと等しくなければならない(MUST)。

空のスパン(長さゼロ)は有効であり、使用してもよい(MAY)ですが、一般的には有用ではなく、それらのオフセットを次のスパンのオフセットに加算することで排除すべきである(SHOULD)。同様に、リストの最初のスパンではないスパンはオフセットがゼロであってもよい(MAY)ですが、それらのオフセットは前のスパンに長さを加算することで排除すべきである(SHOULD)。両方のケースが許可されているのは、ネイティブヒストグラムの生成者がその時点で最適なリソーストレードオフを持つ表現を選択できるようにするためです。例えば、ヒストグラムが様々なステージを経て処理される場合、最後の処理ステージの後で冗長なスパンのみを排除するのが最も効率的かもしれません。

同様の趣旨で、バケットリストからすべてのデータが格納されていないバケットを除外するのが最も効率的な状況もあれば、少数のデータが格納されていないバケットを明示的に表現することでスパンの数を減らす方が良い状況もあります。

将来の高解像度スキーマでは、int32で表現するには大きすぎるオフセットが必要になる可能性があることに注意してください。その場合、データモデルの拡張が必要になります。(現在の最高解像度の標準スキーマはスキーマ8であり、`MaxFloat64`を含むバケットのインデックスは262144、したがって`+Inf`オーバーフローバケットのインデックスは262145ですが、int32で表現可能な最大の数値は2147483647です。int32オフセットで依然として機能する最高の標準スキーマはスキーマ20であり、これはバケットからバケットへの成長因子が約1.000000661に相当します。)

整数ヒストグラムには、以下の正のバケットがあります(インデックス→母集団)

-2→3, -1→5, 0→0, 1→0, 2→1, 3→0, 4→3, 5→2

これらは次のように表現できます

  • 正のバケットリスト: `[3, 2, -4, 2, -1]`
  • 正のスパンリスト: `[[-2, 2], [2,1], [1,2]]`

インデックス3の単一のデータが格納されていないバケットを明示的に表現すれば、2番目と3番目のスパンを1つにマージでき、次の結果になります

  • 正のバケットリスト: `[3, 2, -4, -1, 3, -1]`
  • 正のスパンリスト: `[[-2, 2], [2,4]]`

または、上記のすべてのデータが格納されていないバケットを明示的に表現して、すべてのスパンを1つにマージすることもできます

  • 正のバケットリスト: `[3, 2, -5, 0, 1, -1, 3, -1]`
  • 正のスパンリスト: `[[-2, 8]]`

ゼロバケット

厳密にゼロの観測値は、上記の標準スキーマで定義されたどのバケットにも収まりません。これらは、ゼロバケットと呼ばれる専用のバケットでカウントされます。

ゼロバケット内の観測値の数は、単一のuint64(整数ヒストグラムの場合)またはfloat64(floatヒストグラムの場合)によって追跡されます。

ゼロバケットには、ゼロしきい値と呼ばれる追加のパラメータがあり、これは0以上のfloat64です。しきい値がゼロに設定されている場合、厳密にゼロの観測値のみがゼロバケットに入り、これは上記で説明したケースです。しきい値が正の値を持つ場合、閉区間[-しきい値, +しきい値]内のすべての観測値は、通常のバケットではなくゼロバケットに入ります。これには2つのユースケースがあります

  • ゼロに近いノイズの多い観測値は、多数のバケットにデータが格納される傾向があります。これらの観測値は、数値の不正確さや、観測値のソースが実際の物理測定値である場合に発生する可能性があります。比較的低いしきい値を持つゼロバケットは、これらの観測値を単一のバケットにリダイレクトします。
  • ユーザーが分布のロングテール、つまりゼロから遠く離れた部分に興味がある場合、ゼロバケットのしきい値を比較的大きくすることで、関心のない範囲に多くの高解像度バケットが生成されるのを防ぐのに役立ちます。

ゼロバケットのしきい値は、通常のバケットの境界と一致すべきである(SHOULD)。これにより、ゼロバケットが通常のバケットの一部と重なるという複雑さを避けることができます。ただし、そのような重なりが発生している場合、ゼロバケットと重なる通常のバケットでカウントされる観測値は、[-しきい値, +しきい値]の範囲外でなければならない(MUST)。

同じゼロしきい値を持つヒストグラムをマージするには、2つのゼロバケットを単純に加算します。ただし、ソースヒストグラムのゼロしきい値が異なる場合、いずれかのソースヒストグラムで最大のしきい値が選択されます。そのしきい値が他のソースヒストグラム内のいずれかのデータが格納されているバケット内にある場合、以下のいずれかが各ソースヒストグラムで真になるまでしきい値が増加されます

  • 新しいしきい値が、データが格納されているバケットの境界と一致する。
  • 新しいしきい値が、どのデータが格納されているバケット内にもない。

その後、ソースのゼロバケットと、新しいしきい値内にあるすべてのソースバケットが合計され、新しいゼロバケットの母集団が得られます。

スキーマが-53(カスタムバケット)の場合、ゼロバケットは使用されません。

カスタム値

カスタム値のリストは、標準スキーマでは使用されません。追加のデータを格納する必要がある場合、非標準スキーマによってカスタムな方法で使用されます。

現在、カスタム値が使用される唯一の定義済みスキーマは-53(カスタムバケット)です。このセクションの残りの部分では、この特定のケースにおけるカスタム値の使用法についてさらに詳しく説明します。

カスタム値は、カスタムバケットの上限値(inclusive)を表します。これらは昇順にソートされます。カスタムバケット自体は、正のバケットリストと正のスパンリストを使用して格納されますが、カスタム値によって決定される境界は負になることもあります。これらの「正の」バケットそれぞれのインデックスは、カスタム値リスト内での上限値のゼロベース位置を定義します。

下限値(exclusive)は、上限値の前のカスタム値によって定義されます。リストのゼロ番目の位置にある最初のカスタム値の場合、先行する値がないため、下限値は`-Inf`と見なされます。したがって、インデックスゼロのカスタムバケットは、`-Inf`から最初のカスタム値までのすべての観測値をカウントします。正の観測値のみが期待される一般的なケースでは、インデックスゼロのカスタムバケットの上限値はゼロであるべきである(SHOULD)。これにより、ゼロ以下で観測値があった場合に明確にマークされます。(実際に正の観測値のみがある場合、インデックスゼロのカスタムバケットはデータが格納されず、したがって明示的に表現されることはありません。唯一のコストは、カスタム値リストの先頭に追加されるゼロ要素だけです。)

最後のカスタム値は`+Inf`であってはならない(MUST NOT)。最後のカスタム値より大きい観測値は、上限値が`+Inf`であるオーバーフローバケットに入ります。このオーバーフローバケットは、カスタム値リストの長さと等しいインデックスで追加されます。

エグゼンプラー

ネイティブヒストグラムのサンプルは、ゼロ、1つ、または複数のエグゼンプラーを持つことができます。これらは従来のエグゼンプラーと同じように機能しますが、リスト(複数存在するため)として整理されており、タイムスタンプを持たなければならない(MUST)。

従来のヒストグラムの一部として公開されるエグゼンプラーは、タイムスタンプを持つ場合、ネイティブヒストグラムによって使用されてもよい(MAY)。

観測値の特殊なケース

計装されたコードは、ヒストグラムのコンテキストでは意味が限られているため、NaNおよび±Infの値を観測することを避けるべきです。ただし、これらの値は、以下に説明するように、依然として適切に処理されなければなりません。

観測値の合計は、通常の浮動小数点演算に従って、観測値を観測値の合計に加算することにより、通常どおり計算されます。(例えば、NaNの観測値は合計をNaNに設定します。+Infの観測値は、合計がすでにNaNまたは-Infでない限り、合計を+Infに設定し、その場合は合計がNaNに設定されます。)

NaNの観測値はどのバケットにも入りませんが、観測値のカウントを増やします。これは、観測値のカウントがすべてのバケット(負、正、ゼロバケット)の合計よりも大きくなる可能性があり、その差がNaN観測値の数であることを意味します。(NaN観測値を含まない整数ヒストグラムの場合、すべてのバケットの合計は観測値のカウントと等しくなります。通常の浮動小数点精度制限内では、NaN観測値を含まない浮動小数点ヒストグラムについても同様です。)

+Infまたは-Infの観測値は、観測値のカウントを増やし、以下の方法で選択されたバケットを増やします: - 標準スキーマの場合、+Infの観測値は、上記で説明した正のオーバーフローバケットを増やします。 - 標準スキーマの場合、-Infの観測値は、上記で説明した負のオーバーフローバケットを増やします。 - スキーマ -53(カスタムバケット)の場合、+Infの観測値は、カスタム値リストの長さと等しいインデックスを持つバケットを増やします。 - スキーマ -53(カスタムバケット)の場合、-Infの観測値は、インデックスゼロのバケットを増やします。

OpenTelemetryの相互運用性

標準スキーマを持つPrometheus(Prom)ネイティブヒストグラムは、OpenTelemetry(OTel)指数ヒストグラムに容易にマッピングでき、その逆も可能です。詳細は以下に詳述します。

PromのスキーマはOTelのスケールと等しいですが、OTelは-4よりも低い値と+8よりも高い値を許可するという制限があります。上記で説明したように、Promは実際に必要になった場合にその範囲を拡張するために、より多くのスキーマ番号を予約しています。

インデックスは1だけオフセットされます。つまり、インデックスnを持つPromバケットは、OTelではインデックスn-1を持ちます。

OTelは、疎な表現ではなく、密なバケット表現を使用します。OTelは「単一のスパンを持つProm」と見なすこともできます。

Promのゼロバケットは、OTelではゼロカウントと呼ばれます。(Promも、ゼロバケット内の観測値のカウントを格納するフィールドにゼロカウントという名前を使用します)。両者とも、ゼロ閾値の存在を含め、同じように機能します。OTelは、閾値が指定されていない場合、ゼロの閾値を意味することに注意してください。

(TODO: OTelの仕様には、「zero_thresholdが設定されていないか0の場合、このバケットには標準指数式を使用して表現できない値、およびゼロに丸められた値が格納される」とあります。これが本当に同じ動作を生み出すか二重確認してください。ゼロに近い問題がある場合、Promの仕様をより正確にすることができます。OTelがゼロバケットでNaNをカウントする場合、ここに注記を追加する必要があります。)

OTel指数ヒストグラムは、標準的な指数バケットスキーマのみをサポートしています(その名前が示すように)。したがって、NHCB(または他の将来のバケットスキーマを持つネイティブヒストグラム)は、OTel指数ヒストグラムにきれいに変換することはできません。ただし、固定バケットを持つ従来のOTelヒストグラムへの変換は依然として可能です。

OTelのあらゆる種類のヒストグラムには、ヒストグラムで観測された最小値と最大値のオプションフィールドがあります。これらのフィールドはPrometheusには同等の概念がありません。これは、カウンターヒストグラムが長期間かつ予測不可能な期間にわたってデータを蓄積し、いつでもスクレイピングできるため、最小値と最大値を追跡することは実現不可能であるか、用途が限られているためです。ただし、ネイティブヒストグラムは、任意の期間における最大および最小観測値をかなり正確に推定できることに注意してください。詳細については、PromQLセクションを参照してください。

公開フォーマット

従来のPrometheusのユースケースにおけるメトリクス公開は、文字列が支配的です。これは、すべてのメトリクス名、ラベル名、およびラベル値が、float64サンプル値よりもはるかに多くのスペースを占めるためであり、後者がより冗長なテキスト形式で表現されたとしても同様です。これは、過去にprotobufベースの公開を放棄することが有利に見えた理由の1つでした。

対照的に、上記で説明したデータモデルに従うネイティブヒストグラムは、はるかに多くの数値データで構成されます。これにより、protobufベースのフォーマットの利点が強調されます。したがって、以前に放棄されたprotobufベースの公開が、ネイティブヒストグラムを効率的に公開およびスクレイピングするために復活しました。

従来のPrometheusフォーマット

ネイティブヒストグラムが考案された当時、OpenMetricsの採用はまだ不足しており、特にOpenMetricsprotobufバージョンには全く既知のアプリケーションがありませんでした。そのため、最初の取り組みは、従来のPrometheus protobufフォーマットを拡張してネイティブヒストグラムをサポートすることでした。(追加の実践的な考慮事項として、Go計装ライブラリが依然として従来のprotobuf仕様を内部データモデルとして使用しており、初期開発を簡素化しました。)

従来のPrometheusテキスト形式はネイティブヒストグラム用に拡張されておらず、そのような拡張は計画されていません。(下のOpenMetricsセクションも参照してください。)

protobuf仕様にはproto2proto3バージョンがあり、どちらも同じワイヤーフォーマットを作成します

これらのファイルには包括的なコメントがあり、proto仕様から上記で説明したデータモデルへの容易なマッピングを可能にするはずです。

proto3ファイルの関連部分を以下に示します

// [...]

message Histogram {
  uint64 sample_count       = 1;
  double sample_count_float = 4; // Overrides sample_count if > 0.
  double sample_sum         = 2;
  // Buckets for the classic histogram.
  repeated Bucket bucket = 3 [(gogoproto.nullable) = false]; // Ordered in increasing order of upper_bound, +Inf bucket is optional.

  google.protobuf.Timestamp created_timestamp = 15;

  // Everything below here is for native histograms (also known as sparse histograms).
  // Native histograms are an experimental feature without stability guarantees.

  // schema defines the bucket schema. Currently, valid numbers are -4 <= n <= 8.
  // They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and
  // then each power of two is divided into 2^n logarithmic buckets.
  // Or in other words, each bucket boundary is the previous boundary times 2^(2^-n).
  // In the future, more bucket schemas may be added using numbers < -4 or > 8.
  sint32 schema           = 5;
  double zero_threshold   = 6; // Breadth of the zero bucket.
  uint64 zero_count       = 7; // Count in zero bucket.
  double zero_count_float = 8; // Overrides sb_zero_count if > 0.

  // Negative buckets for the native histogram.
  repeated BucketSpan negative_span = 9 [(gogoproto.nullable) = false];
  // Use either "negative_delta" or "negative_count", the former for
  // regular histograms with integer counts, the latter for float
  // histograms.
  repeated sint64 negative_delta = 10; // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
  repeated double negative_count = 11; // Absolute count of each bucket.

  // Positive buckets for the native histogram.
  // Use a no-op span (offset 0, length 0) for a native histogram without any
  // observations yet and with a zero_threshold of 0. Otherwise, it would be
  // indistinguishable from a classic histogram.
  repeated BucketSpan positive_span = 12 [(gogoproto.nullable) = false];
  // Use either "positive_delta" or "positive_count", the former for
  // regular histograms with integer counts, the latter for float
  // histograms.
  repeated sint64 positive_delta = 13; // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
  repeated double positive_count = 14; // Absolute count of each bucket.

  // Only used for native histograms. These exemplars MUST have a timestamp.
  repeated Exemplar exemplars = 16;
}

message Bucket {
  uint64   cumulative_count       = 1; // Cumulative in increasing order.
  double   cumulative_count_float = 4; // Overrides cumulative_count if > 0.
  double   upper_bound            = 2; // Inclusive.
  Exemplar exemplar               = 3;
}

// A BucketSpan defines a number of consecutive buckets in a native
// histogram with their offset. Logically, it would be more
// straightforward to include the bucket counts in the Span. However,
// the protobuf representation is more compact in the way the data is
// structured here (with all the buckets in a single array separate
// from the Spans).
message BucketSpan {
  sint32 offset = 1; // Gap to previous span, or starting point for 1st span (which can be negative).
  uint32 length = 2; // Length of consecutive buckets.
}


// A BucketSpan defines a number of consecutive buckets in a native
// histogram with their offset. Logically, it would be more
// straightforward to include the bucket counts in the Span. However,
// the protobuf representation is more compact in the way the data is
// structured here (with all the buckets in a single array separate
// from the Spans).
message BucketSpan {
  sint32 offset = 1; // Gap to previous span, or starting point for 1st span (which can be negative).
  uint32 length = 2; // Length of consecutive buckets.
}

// [...]

(TODO: 上記はまだNHCBに必要なカスタム値を含んでいません。現在、NHCBは従来のヒストグラムをスクレイピングすることで取り込むことができるため、直ちに必要ではありません。ただし、最終的に公開フォーマットにカスタムバケットを含めることは、例えばフェデレーションのためや、将来カスタム値を利用する可能性のあるスキーマのためにも、依然として有用であるかもしれません。)

以下に注意してください

  • ネイティブヒストグラムと従来のヒストグラムは両方とも同じHistogramプロトメッセージによってエンコードされます。つまり、既存のHistogramメッセージはネイティブヒストグラム用のフィールドで拡張されました。
  • 観測値の合計とカウント、およびcreated_timestampのフィールドは、従来のヒストグラムとネイティブヒストグラムの間で共有され、両方で同じように機能し続けます。
  • このフォーマットは元々、従来の浮動小数点ヒストグラムをサポートしていませんでした。ネイティブヒストグラムのフォーマットを拡張する際に、従来の浮動小数点ヒストグラムのサポートが副産物として追加されました(フィールドsample_count_floatcumulative_count_floatを参照)。
  • BucketフィールドとBucketメッセージは、従来のヒストグラムのバケットに使用されます。同じヒストグラムの従来版とネイティブ版の両方を表すHistogramメッセージを作成することは完全に可能です。パーサーは、いずれかまたは両方のバージョンを選択する自由があります(スクレイプ設定セクションも参照)。
  • バケットの母集団は、浮動小数点ヒストグラムの場合は絶対数として、整数ヒストグラムの場合は前のバケット(または最初のバケットの場合はゼロ)からのデルタとしてエンコードされます。後者はより小さな数につながり、protobufsint64型にvarintエンコーディングを使用するため、メッセージサイズが小さくなります。
  • まだ観測値を受け取っていないネイティブヒストグラムと、バケットが設定されていない従来のヒストグラムは、protobufメッセージとして全く同じに見えます。したがって、ネイティブヒストグラムとしてパースされることを意図したHistogramメッセージは、繰り返し出現するpositive_spanフィールドに、「no-op span」、つまりoffsetlengthが0に設定されたBucketSpanを含まなければなりません。
  • ネイティブヒストグラムの任意の数のエグザンプラーは、Histogramメッセージの繰り返し出現するExemplarフィールドに追加されることがあります(MAY)が、それぞれにタイムスタンプが必要です(MUST)。この方法でエグザンプラーが提供されない場合、パーサーは、従来のバケットに提供されたタイムスタンプ付きエグザンプラー(BucketメッセージのExemplarフィールド内のバケットごとに最大1つのエグザンプラーとして)を使用することがあります(MAY)。
  • ネイティブヒストグラムのエグザンプラーの数と分布は、現在のユースケースに適合するべきです(SHOULD)。一般的に、エグザンプラーのペイロードはHistogramメッセージの残りの部分よりも大幅に大きくないべきであり(SHOULD NOT)、エグザンプラーは異なるバケットに分類され、バケットの全範囲をほぼ均等にカバーするべきです(SHOULD)。(これは、観測値の分布を比例的に表すエグザンプラー分布よりも一般的に好ましいです。後者は、分布のロングテールからのエグザンプラーをほとんど生成せず、それらは多くの場合、最も興味深いエグザンプラーであるためです。)

OpenMetrics

現在(2024-11-03)、OpenMetricsはネイティブヒストグラムをサポートしていません。

OpenMetricsprotobufバージョンへのサポート追加は、従来のPrometheus protobufフォーマットとの類似性により、比較的簡単です。PR形式の提案が現在レビュー中です。

OpenMetricsのテキストバージョンへのサポート追加はより困難ですが、protobufの生成が実現不可能な状況が多いため、非常に望ましいものでもあります。テキストフォーマットは、人間の可読性と機械による効率的な処理(エンコーディング、トランスポート、デコーディング)の間でトレードオフを行う必要があります。現在、作業が進行中です。詳細については、設計ドキュメントを参照してください。

(TODO: 進捗があり次第、セクションを更新してください。)

計装ライブラリ

protobuf仕様は、protobufコンパイラによって作成された言語固有のバインディングを使用して、ネイティブヒストグラムを含むメトリクス公開の低レベルな作成を可能にします。ただし、直接的なコード計装には、計装ライブラリが必要です。

現在(2024-11-03)、ネイティブヒストグラムをサポートする公式Prometheus計装ライブラリが2つあります

他の計装ライブラリにネイティブヒストグラムのサポートを追加することは、そのライブラリがすでにprotobuf公開をサポートしている場合、比較的簡単です。純粋にテキストベースのライブラリの場合、テキストベースの公開フォーマットの完成が前提条件となります。(TODO: 必要に応じて更新してください。)

このセクションでは、個々の計装ライブラリの使用方法の詳細については触れません(それについては上記のドキュメントを参照してください)が、共通の使用パターンに焦点を当て、計装ライブラリの一部としてネイティブヒストグラムサポートを実装するための一般的なガイドラインも提供します。既存のGo実装が例として使用されています。データモデル公開フォーマットに関するセクションは、計装ライブラリの実装に非常に重要です(ただし、このセクションでは繰り返しません)。

ヒストグラムの実際の計装APIは、ネイティブヒストグラムでは変更されません。従来のヒストグラムとネイティブヒストグラムはどちらも同じ方法で観測値を受け取ります(エグザンプラーに関する微妙な違いは次の段落を参照)。計装ライブラリは、同じヒストグラムの従来版とネイティブ版の両方を維持し、それらを並行して公開して、スクレイパーがどちらのバージョンを取り込むかを選択できるようにすることも可能です(詳細については公開フォーマットに関するセクションを参照)。ユーザーは、設定を介して従来および/またはネイティブヒストグラムを公開するかどうかを選択します。

従来のヒストグラムのエグザンプラーは、通常、各バケットの最新のエグザンプラーを保存および公開することで追跡されます。従来のバケットが定義されている限り、計装ライブラリは、各エグザンプラーにタイムスタンプがある場合、同じヒストグラムのネイティブバージョンに対して同じエグザンプラーを公開することがあります(MAY)。(実際、スクレイパーは、ヒストグラムの従来バージョンに提供されたエグザンプラーを使用することがあります。たとえネイティブバージョンのみを取り込んでいる場合でもです。詳細については公開フォーマットセクションを参照してください)。ただし、ネイティブヒストグラムには任意の数のエグザンプラーを割り当てることができ、計装ライブラリは、公開フォーマットセクションで説明されているエグザンプラーのベストプラクティスを満たすために、この自由を使用するべきです(SHOULD)。

計装ライブラリは、標準スキーマに従うネイティブヒストグラムに対して、以下の設定パラメータを提供するべきです(SHOULD)。名前はGoライブラリからの例です。他の言語の慣用的なスタイルに調整する必要があります。括弧内の値は、ライブラリが提供するべき(SHOULD)デフォルト値です。

  • NativeHistogramBucketFactor (1.1): 初期解像度を決定するための1より大きい浮動小数点数です。ライブラリは、バケット幅が次のバケットへの成長率が提供された値を超えないような開始スキーマを選択します。例の値については以下の表を参照してください。
  • NativeHistogramZeroThreshold (2-128): ゼロバケットの初期閾値を設定するためのゼロまたはそれより大きい浮動小数点数です。

解像度は、スキーマを直接提供するのではなく、成長率を介して設定されます。これは、ほとんどのユーザーがスキーマ番号の背後にある数学を知らないためです。バケット間の成長率の上限という概念は、ネイティブヒストグラムの内部動作を知らなくても理解できます。以下の表は、各有効なスキーマの例となる係数をリストしています。

NativeHistogramBucketFactor 結果のスキーマ
65536 -4
256 -3
16 -2
4 -1
2 0
1.5 1
1.2 2
1.1 3
1.05 4
1.03 5
1.02 6
1.01 7
1.005 8

バケット数の制限

ネイティブヒストグラムのバケットは、最初にデータが投入されるときに動的に作成されます。観測値の予期せぬ広い分布は、予期せぬ数のバケットにつながり、予想よりも多くのメモリを必要とする可能性があります。観測値の分布が外部から操作できる場合、これはプログラムに利用可能なすべてのメモリを使い果たすことにより、DoS攻撃ベクトルとして使用される可能性さえあります。したがって、計装ライブラリはバケット制限戦略を提供するべきです(SHOULD)。ライブラリが使用される典型的なユースケースに応じて、デフォルトで設定することがあります(MAY)。(TODO: 戦略はデフォルトで設定されるべきであると言うべきかもしれません。Goライブラリは現在デフォルトではバケットを制限しておらず、これまでのところ問題は報告されていません。)

以下では、Go計装ライブラリによって実装されているバケット制限戦略について説明します。他のライブラリはこの例に倣うことがあります(MAY)が、ライブラリの典型的な使用パターンによっては、他の戦略も実行可能かもしれません。

この戦略は3つのパラメータによって定義されます。符号なし整数NativeHistogramMaxBucketNumber、期間NativeHistogramMinResetDuration、および浮動小数点数NativeHistogramMaxZeroThresholdです。NativeHistogramMaxBucketNumberがゼロ(デフォルト値)の場合、バケットは全く制限されず、他の2つのパラメータは無視されます。NativeHistogramMaxBucketNumberが正の値に設定されている場合、ライブラリは各ヒストグラムのバケットカウントを指定された値に保とうとします。制限の典型的な値は160で、これはOTel指数ヒストグラムで類似の戦略で使われているデフォルト値でもあります。(ラベルによるパーティショニングは多数のヒストグラムを作成することに注意してください。この制限は、それらすべてを合計するのではなく、個々のヒストグラムに適用されます。) 制限を超えそうな場合、バケット数が再び制限内になるまで、いくつかの対策が順番に適用されます。

  1. ヒストグラムの最後のリセット(ヒストグラムの作成を含む)から少なくともNativeHistogramMinResetDurationが経過した場合、ヒストグラム全体がリセットされます。つまり、すべてのバケットが削除され、観測値の合計とカウント、およびゼロバケットはゼロに設定されます。Prometheusはこれを通常のカウンタリセットとして扱います。これは、一部の観測値がスクレイプ間で失われることを意味するため、リセットはスクレイピング間隔と比較して稀にしか発生しないべきです。さらに、頻繁なカウンタリセットは、TSDBでのストレージ効率の低下につながる可能性があります(詳細についてはTSDBセクションを参照)。1時間のNativeHistogramMinResetDurationは、ほとんどの状況でうまく機能する値です。
  2. 最後のリセットから十分な時間が経過していない場合(またはNativeHistogramMinResetDurationがゼロに設定されている場合、これはデフォルト値です)、リセットは実行されません。代わりに、ゼロ閾値が増加され、ゼロに近いバケットがゼロバケットにマージされ、その方法でバケットの数が減少します。閾値の増加はNativeHistogramMaxZeroThresholdによって制限されます。この値がすでに到達している場合(またはゼロに設定されている場合、これはデフォルトです)、このステップでは何も起こりません。
  3. バケット数がまだ制限を超えている場合、ヒストグラムの解像度は、次に低いスキーマに変換することで削減されます。つまり、隣接するバケットをマージすることで、バケットの幅を2倍にします。これは、バケット数が設定された制限内になるか、スキーマ-4に到達するまで繰り返されます。

ステップ2または3がヒストグラムを変更した場合、最後のリセットからNativeHistogramMinResetDurationが経過すると、バケットを削除するだけでなく、ゼロ閾値とバケット解像度の初期値に戻すためにリセットが実行されます。これは、いわゆる作成タイムスタンプの更新を含め、あらゆる点で他の理由によるリセットと同様に扱われることに注意してください。

非常に低いNativeHistogramBucketFactor(例えば1.005)と合理的なNativeHistogramMaxBucketNumber(例えば160)を組み合わせて設定したくなるかもしれません。このようにすると、各ヒストグラムは常に、与えられたバケット数の「予算」内で許容される最高の解像度を持ちます。(これはOTel指数ヒストグラムで使われているデフォルトの戦略です。OTelは、現在Prometheusネイティブヒストグラムでは利用できないさらに高いスキーマ(20)から開始します。) しかし、この戦略はPrometheusのユースケースでは一般的に推奨されません。観測値が入り込むにつれて、作成後および各リセット後に解像度が頻繁に低下します。これは、計装されたプログラムとTSDBの両方でチャーン(変更の頻発)を引き起こし、後者にとって特に問題となります。ヒストグラムに関わる一般的なクエリでは、多くのヒストグラムがマージされる必要があり、その際に最も低い共通の解像度が使用されるため、結局ユーザーは低い解像度で終わることになります。そのため、この努力のほとんどは無駄になります。TSDBは取り込み時に解像度を制限することでチャーンから保護できます(以下を参照)が、いずれにせよ取り込み時に合理的に低い解像度が強制されるのであれば、計装時にこの解像度を設定する方が直接的です。ただし、この戦略は、計装時に合理的な解像度を想定できない特定のケースで、計装されたプログラム内のリソースオーバーヘッドに見合う価値があるかもしれません。また、スクレイパーはスクレイプ時に希望する解像度を選択する柔軟性を持つべきです。

ラベルによるパーティショニング

多くのバケットを持つ従来のヒストグラムをラベルでパーティショニングすることは慎重に行う必要がありますが、ネイティブヒストグラムでは状況はより緩和されます。ネイティブヒストグラムをパーティショニングしても、多数の個別のヒストグラムが作成されます。ただし、結果として得られるパーティション化されたヒストグラムは、元のパーティション化されていないヒストグラムよりも、個々が埋めるバケットの数が少なくなることがよくあります。(例えば、HTTPリクエストの期間を追跡するヒストグラムをHTTPステータスコードでパーティショニングする場合、ステータスコード404で応答されたリクエストを追跡する個別のヒストグラムは、未知のパスを特定するのにかかる典型的な期間を中心に非常にシャープなバケット分布を持ち、少数のバケットしか埋めないかもしれません。) すべてのパーティション化されたヒストグラムで埋められたバケットの総数は依然として増加しますが、パーティション化されたヒストグラムの数よりも小さい係数で増加します。(例えば、すでにかなり重い従来のヒストグラムにラベルを追加すると、100個のラベル付きヒストグラムが生成され、総コストは100倍になります。ネイティブヒストグラムの場合、従来のヒストグラムが高い解像度を特徴としていた場合、単一のヒストグラムのコストはすでに低くなっている可能性があります。パーティショニング後、ラベル付きネイティブヒストグラムで埋められたバケットの総数は、元のネイティブヒストグラムのバケット数の100倍よりも著しく少なくなります。)

NHCB

現在(2024-11-03)、計装ライブラリはカスタムバケット境界(NHCB)を持つネイティブヒストグラムを直接構成する方法を提供していません。NHCBのユースケースは、ネイティブヒストグラム対応のスクレイパーが、取り込み時に従来のヒストグラムをNHCBに変換できるようにすることです(次のセクションを参照)。ただし、計装時に直接カスタムバケットが望ましい有効なユースケースも存在します。これらの場合、現在の方法は、従来のヒストグラムで計装し、スクレイパーが取り込み時にそれをNHCBに変換するように構成することです。しかし、将来的には計装ライブラリでNHCBをより直接的に扱うことが行われるかもしれません。

スクレイプ設定

Prometheusサーバーがネイティブヒストグラムをスクレイピングできるようにするには、機能フラグ--enable-feature=native-histogramsが必要です。このフラグはまた、コンテンツネゴシエーションを変更し、OpenMetricsテキスト形式よりも従来のprotobufベースの公開形式を優先させます。(TODO: ネイティブヒストグラムが安定した機能になったら、この動作は変更されます。)

コンテンツネゴシエーションの微調整

Prometheus v2.49以降では、scrape_protocols設定を使用して、スクレイププロトコルネゴシエーションをグローバルまたはスクレイプ設定ごとに微調整することが可能です。これは、コンテンツネゴシエーションの優先順位を定義するリストです。そのデフォルト値は、--enable-feature=native-histogramsフラグに依存します。このフラグが設定されている場合、[ PrometheusProto, OpenMetricsText1.0.0, OpenMetricsText0.0.1, PrometheusText0.0.4 ]となりますが、設定されていない場合、最初の要素であるPrometheusProtoがリストから削除され、結果として[ OpenMetricsText1.0.0, OpenMetricsText0.0.1, PrometheusText0.0.4 ]となります。これらのデフォルト値は上記の動作、つまり--enable-feature=native-histogramsフラグがない場合はprotobufが使用されず、フラグが設定されている場合は最初の優先順位になるという動作につながります。

この設定は、ネイティブヒストグラムを取り込まずにprotobufスクレイプを構成したり、--enable-feature=native-histogramsフラグが設定されている場合でも特定のターゲットに対して非protobuf形式を強制したりするために使用できます。設定されたリスト内の従来のPrometheus protobuf形式(PrometheusProto)がネイティブヒストグラムをサポートする唯一の形式である限り、実際にネイティブヒストグラムを取り込むためには、機能フラグとprotobufのネゴシエーションの両方が必要です。

(TODO: ネイティブヒストグラムが安定した機能になるか、他の形式でネイティブヒストグラムがサポートされたら、このセクションを更新してください。)

注記: 使用する公開フォーマットをテキストベースとprotobufベースで切り替えることには、いくつかの自明ではない影響があります。最も重要なのは、特定の実装詳細により、テキストベースのフォーマットでのスクレイピングがprotobufベースのフォーマットでのスクレイピングよりも一般的にリソース消費がはるかに少ないという直感に反する効果が生じることです(詳細についてはトラッキングイシューを参照)。さらに微妙なのは、quantileラベル(サマリーで使用)およびleラベル(従来のヒストグラムで使用)のラベル値のフォーマットへの影響です。この問題はPrometheusサーバーのv2のみに影響し(v3はあらゆる状況で一貫したフォーマットを持っています)、ネイティブヒストグラムに直接関連するわけではありませんが、ネイティブヒストグラムを有効にするにはprotobuf公開形式が必要となるため、同じコンテキストで現れる可能性があります。v2.55のnative-histograms機能フラグのドキュメントで詳細を参照してください。

バケット数と解像度の制限

計装ライブラリはネイティブヒストグラムの解像度とバケット数を制限する設定オプションを提供するべきですが(SHOULD)、取り込み時にそれらの制限を強制する必要が依然としてあります。ユーザーは、特定のプログラムの計装を変更できない場合や、プログラムが意図的に高解像度ヒストグラムで計装され、異なるスクレイパーが適切に解像度を減らすオプションを持てるようにしている場合があります。

Prometheusのスクレイプ設定は、この必要性に対応するための2つの設定を提供します

  1. native_histogram_bucket_limitは、個々のヒストグラムにおけるバケット数の上限(包含)を設定します。制限を超えた場合、標準スキーマを持つヒストグラムの解像度は、制限に達するまで繰り返し削減されます(バケットの幅を2倍にする、つまりスキーマを減少させることによって)。NHCBが制限を超える場合、またはスキーマ-4でも制限が満たされない稀なケースでは、スクレイプは失敗します。
  2. native_histogram_min_bucket_factorは、バケット間の成長率の下限(包含)を設定します。この設定は標準スキーマにのみ関連し、NHCBには影響しません。ここでも、制限を超えた場合、ヒストグラムの解像度は、制限に達するまで繰り返し削減されます(バケットの幅を2倍にする、つまりスキーマを減少させることによって)。ただし、スキーマ-4に達すると、より高い成長率が指定されていたとしても、スクレイプは成功します。

両方の設定は、有効な値としてゼロを受け入れ、これは「制限なし」を意味します。バケット制限の場合、これはバケットの数が全くチェックされないことを意味します。バケット係数の場合、Prometheusは標準スキーマが使用されるストレージバックエンドの能力を超えないことを引き続き保証します。(TODO: これは現在、スキーマが最大+8であることを意味し、これは公開フォーマットで許容される制限でもあります。OTelはより高い指数スキーマを許可しており、Prometheusも取り込みパスでそれらを許可するかもしれませんが、取り込み時にスキーマを+8、または現在の実装が必要とする制限に減らすでしょう。最終的な明確化についてはhttps://github.com/prometheus/prometheus/issues/14168を参照してください。)

両方の設定が非ゼロ値を持つ場合、両方の制限を満たすようにスキーマは十分に減少されます。

インスツルメンテーション中に設定されるバケット係数は上限(公開されるバケット増加係数 ≤ 設定値)であるのに対し、スクレイプ設定で設定されるバケット係数は下限(取り込まれるバケット増加係数 ≥ 設定値)であることに注意してください。そのため、特定の制限から生じるスキーマはわずかに異なります。いくつか例を挙げます

native_histogram_min_bucket_factor 結果の最大スキーマ
65536 -4
256 -3
16 -2
4 -1
2 0
1.4 1
1.1 2
1.09 3
1.04 4
1.02 5
1.01 6
1.005 7
1.002 8

制限設定に関する一般的な考慮事項:native_histogram_bucket_limitは、個々のヒストグラムのコストに厳密な制限を設定するのに適しています。native_histogram_min_bucket_factorでは同じことはできません。これは、観測値の分布が十分に広範な場合、ヒストグラムは低い解像度でも多くのバケットを持つことができるためです。native_histogram_min_bucket_factorは、不要な全体的なリソースコストを回避するのに適しています。例えば、現在のユースケースが特定の解像度しか必要としない場合、すべてのヒストグラムに対応するnative_histogram_min_bucket_factorを設定することで、観測値の広い分布を持つ少数のヒストグラムにおいて、非常に高いバケット数を受け入れるのに十分なリソースを解放できるかもしれません。別の例は、何らかの理由で一部のヒストグラムが低い解像度を持つ場合(おそらく計装側ですでに)です。集計が定期的にこれらの低解像度ヒストグラムを含む場合、結果も同じ低解像度になります(PromQLの詳細を以下で参照)。低解像度ヒストグラムと定期的に集計される他のヒストグラムを高解像度で保存しても、あまり意味がないかもしれません。

従来のヒストグラムとネイティブヒストグラムの両方のスクレイピング

上記で説明したように、計装されたプログラムによって公開されるヒストグラムは、従来のヒストグラムとネイティブヒストグラムの両方を含み、一部の要素(観測値のカウントと合計など)は共有されることさえあります。このセクションでは、Prometheusによってどの部分がスクレイピングされるか、およびその動作を制御する方法について説明します。

--enable-feature=native-histogramsフラグがない場合、Prometheusはスクレイピング中にネイティブヒストグラムの部分を完全に無視します。(TODO: 機能フラグがno-opになったら更新してください。) このフラグが設定されている場合、Prometheusは、同じヒストグラムに対して両方が公開されていても、従来のヒストグラムの部分よりもネイティブヒストグラムの部分を優先します。Prometheusは、ネイティブヒストグラムデータがないヒストグラムに対しても、従来のヒストグラムの部分をスクレイピングし続けます。

移行シナリオのような状況では、計装されたプログラムによって両方のバージョンが公開されている場合、同じヒストグラムに対して従来とネイティブの両方のバージョンをスクレイピングすることが望ましいかもしれません。この動作を有効にするには、スクレイプ設定にブール値設定always_scrape_classic_histogramsがあります。これはデフォルトでfalseですが、trueに設定すると、少なくとも1つの従来のバケットと少なくとも1つのネイティブバケットスパン(no-opスパンの場合もあります)がある場合に限り、各ヒストグラムの両方のバージョンがスクレイピングされて取り込まれます。これはTSDBで競合を引き起こしません。なぜなら、従来のヒストグラムは接尾辞付きの複数のシリーズとして取り込まれるのに対し、ネイティブヒストグラムは変更されていない名前を持つ1つのシリーズとして取り込まれるためです。(例: rpc_latency_secondsというヒストグラムは、rpc_latency_secondsという名前のネイティブヒストグラムシリーズと、従来のパートの複数のシリーズ、すなわちrpc_latency_seconds_sumrpc_latency_seconds_count、および異なるleラベルを持つ複数のrpc_latency_seconds_bucketシリーズをもたらします。)

従来のヒストグラムをNHCBとしてスクレイピングする

前述のNHCBは、従来のヒストグラムをネイティブヒストグラムとしてモデル化することができます。ブール値のスクレイプ設定オプションconvert_classic_histograms_to_nhcbを介して、Prometheusは従来のヒストグラムをNHCBとして取り込むように構成できます。

NHCBは従来のヒストグラムと同様にマージ可能性に制限があるという問題を抱えていますが、一般的に保存コストははるかに低いです。

TSDB

注記: このセクションでは、TSDBへのネイティブヒストグラムの保存に関するハイレベルな概要を提供し、見落としがちな重要な個々の側面も説明します。実装の詳細を説明したり、オンディスク形式を定義したり、コードベースを案内したりすることを意図したものではありません。さまざまなストレージ形式の詳細なドキュメントと、もちろん通常の生成されたGoDocがあり、tsdbパッケージstorageパッケージが適切な出発点となります。役立つリソースとして、前述のDeveloper’s Guide to Prometheus Native Histogramsも挙げられます。

整数ヒストグラム対浮動小数点ヒストグラム

TSDBは整数ヒストグラムと浮動小数点ヒストグラムを異なる方法で保存します。一般的に、整数ヒストグラムの方が圧縮率が高いと予想されるため、TSDB実装は、すべてのバケットカウントと観測値のカウントがint64範囲内の整数値を持つ場合、浮動小数点ヒストグラムを整数ヒストグラムとして保存することがあります(MAY)。これにより、整数ヒストグラムへの変換が元の浮動小数点ヒストグラムの数値的に正確な表現を作成します。(Prometheus TSDBはまだこのオプションを利用していません。)

エンコーディング

ネイティブヒストグラムは、TSDBで2つの新しいチャンクエンコーディング(Goの型chunkenc.Encoding)を必要とします。整数ヒストグラム用のchunkenc.EncHistogram(文字列表現histogram、数値2)と、浮動小数点ヒストグラム用のchunkenc.EncFloatHistogram(文字列表現floathistogram、数値3)です。

同様に、WALとインメモリのスナップショット(Goの型record.Type)には2つの新しいレコードタイプがあります。整数ヒストグラム用のrecord.HistogramSamples(文字列表現histogram_samples、数値9)と、浮動小数点ヒストグラム用のrecord.FloatHistogramSamples(文字列表現float_histogram_samples、数値10)です。下位互換性の理由から、さらに2つのヒストグラムレコードタイプがあります。record.HistogramSamplesLegacyhistogram_samples_legacy、7)とrecord.FloatHistogramSamplesLegacyfloat_histogram_samples_legacy、8)です。これらは、NHCBに必要なカスタム値の導入前に使用されていました。古いWALの読み取りが引き続き可能になるようにサポートされています。

Prometheusは時系列をそのラベルだけで識別します。系列内のサンプルが浮動小数点型(したがってカウンターまたはゲージ)であるか、またはヒストグラム型(どのような種類であっても)であるかは、系列の識別に寄与しません。そのため、系列には異なる型や種類のサンプルが混在する可能性があります。時系列内のサンプル型の変更は、実際には非常に稀であると予想されます。これらは通常、ターゲットのインスツルメンテーションの変更後(例えば、変更前はゲージ浮動小数点型に、変更後はカウンターヒストグラム型に同じメトリック名が使用される稀なケース)、または記録ルールの変更後(例えば、ルールの古いバージョンがゲージ浮動小数点型を作成し、新しいバージョンが名前を保持したままゲージヒストグラム型を作成する場合)に発生します。サンプル型が頻繁に変わることは、通常、設定ミス(例えば、同じ系列に異なるサンプル型を供給する2つの異なる記録ルール)の結果です。したがって、TSDBの実装はサンプル型の変更を処理しなければなりませんが、比較的非効率な方法で行うことも可能です。Prometheus TSDBは、現在使用中のチャンクに書き込めないサンプル型に遭遇した場合、そのチャンクを閉じ、適切なエンコーディングで新しいチャンクを開始します。(各サンプルでサンプル型が頻繁に切り替わる時系列は、各サンプルごとに新しいチャンクを生成することになり、これは非常に非効率です。)

ヒストグラムチャンクは、数値に対していくつかのカスタムエンコーディングを使用し、一般的な値をあまり一般的でない値よりも少ないビット数でエンコードすることでデータサイズを削減します。各カスタムエンコーディングの詳細は、低レベルチャンク形式ドキュメント(そして最終的にはそこからリンクされているコード)に記載されています。以下の3つのエンコーディングは多くの異なるフィールドで使用されるため、後で参照できるようここに名前を付けます

  • varbit-intは、符号付き整数用の可変ビット幅エンコーディングです。1ビットから9バイトの範囲を使用します。ゼロに近い数値はより少ないビットを必要とします。これは浮動小数点サンプル用チャンクのタイムスタンプエンコーディングに似ていますが、様々なビット長のバケット分けが異なり、ネイティブヒストグラムで一般的に遭遇する値の分布に最適化されています。
  • varbit-uintは同様のエンコーディングですが、符号なし整数用です。
  • varbit-xorは、浮動小数点数値のシーケンス用の可変ビット幅エンコーディングです。これは、シーケンス内の現在の浮動小数点数値と前の浮動小数点数値のXORに基づいています。浮動小数点数値あたり1ビットから77ビットを使用します。これはTSDBが浮動小数点サンプルにすでに使用しているエンコーディングとまったく同じです。

ヒストグラムチャンクは、通常通りチャンク内のサンプル数(uint16)から始まり、その後にヒストグラムがゲージヒストグラムであるかカウンターヒストグラムであるかを記述し、後者についてはカウンターリセット情報を提供する1バイトが続きます。詳細については、以下の対応するセクションを参照してください。これに続いて、いわゆるチャンクレイアウトがあり、これにはチャンク内のすべてのヒストグラムで共有される以下の情報が含まれます

  • ゼロバケットのしきい値。これは一般的な値(ゼロまたは特定の2のべき乗)をわずか1バイトでエンコードするカスタムエンコーディングを使用しますが、任意の値には9バイトを必要とします。
  • スキーマ。varbit-intとしてエンコードされます。
  • 正のスパン。スパンの数(varbit-uint)としてエンコードされ、その後、繰り返しシーケンス内で各スパンの長さ(varbit-uint)とオフセット(varbit-int)が続きます。
  • 負のスパンも同様です。
  • スキーマ-53 (NHCB) の場合にのみ、カスタム値。カスタム値の数(varbit-uint)としてエンコードされ、その後にカスタムエンコーディングを使用して繰り返しシーケンスでカスタム値が続きます。

チャンクレイアウトの後には、サンプルデータの繰り返しシーケンスが続きます。サンプルデータは整数ヒストグラムと浮動小数点ヒストグラムで異なります。整数ヒストグラムの場合、各サンプルのデータには以下が含まれます

  • タイムスタンプ。varbit-intとしてエンコードされ、1番目のサンプルは絶対値、2番目のサンプルは1番目と2番目のサンプルの間の差分、それ以降のサンプルは「差分の差分」(つまり、従来の浮動小数点チャンクのタイムスタンプに使用されるのと同じ「ダブルデルタ」エンコーディングですが、varbit-intエンコーディングのためにビットバケットが異なります)としてエンコードされます。
  • 観測数。1番目のサンプルはvarbit-uintとして、それ以降のサンプルはvarbit-intとしてエンコードされ、タイムスタンプと同じ「差分の差分」アプローチを使用します。
  • ゼロバケットの母集団。1番目のサンプルはvarbit-uintとして、それ以降のサンプルはvarbit-intとしてエンコードされ、タイムスタンプと同じ「差分の差分」アプローチを使用します。
  • 観測の合計。1番目のサンプルはfloat64として、それ以降のサンプルはvarbit-xorとしてエンコードされます(現在および前のサンプルの間でXOR演算を実行)。
  • 正のバケットの母集団。それぞれ前のバケットとの差分として(または1番目のバケットでは絶対的な母集団として)varbit-intとしてエンコードされ、タイムスタンプと同じ「差分の差分」アプローチを使用します。(言い換えれば、「ダブルデルタ」エンコーディングは、それ自体がすでに差分である値に適用されるため、これが「トリプルデルタ」エンコーディングと呼ばれることもあります。)
  • 負のバケットの母集団も同様です。

浮動小数点ヒストグラムのサンプルデータには以下の違いがあります

  • 観測数とゼロバケットの母集団は現在浮動小数点型であり、そのため観測の合計と同じ方法でエンコードされます(1番目のサンプルはfloat64、それ以降のサンプルはvarbit-xor)。
  • バケットの母集団は現在浮動小数点型であるだけでなく、バケット間の差分ではなく絶対的な母集団カウントです。1番目のサンプルでは、すべてのバケット母集団はプレーンなfloat64として表現されますが、それ以降のすべてのサンプルでは、現在および前のサンプルからの対応するバケットをXOR演算してvarbit-xorとしてエンコードされます。

以下のイベントは(括弧内に記載された理由により)新しいチャンクの切り出しをトリガーします

  • 整数ヒストグラムと浮動小数点ヒストグラムの間でのサンプル型の変更(どちらも全く異なるチャンクエンコーディングを必要とするため)。
  • ゲージヒストグラムとカウンターヒストグラムの間でのサンプル型の変更(先頭バイトが異なる型を示す必要があるため)。
  • カウンターヒストグラムのカウンターリセット(詳細については以下を参照。カウンターリセット情報として先頭バイトに格納されるため)。
  • スキーマの変更(新しいチャンクレイアウトが必要となり、チャンクは1つのチャンクレイアウトしか持てないため)。
  • ゼロしきい値の変更(チャンクレイアウトが変更されるため、上記参照)。
  • カスタム値の変更(チャンクレイアウトが変更されるため、上記参照)。
  • 陳腐化マーカーの後に通常のサンプルが続く場合(厳密には新しいチャンクを必要としないが、ほとんどのヒストグラムは一度消滅して再出現する際に大きく変化するため、新しいチャンクを切り出すのが最善の選択肢であると想定される)。
  • チャンクサイズ制限を超過した場合(詳細は以下を参照)。

スパンの違いもチャンクレイアウトを変更しますが、チャンク内のすべてのヒストグラムが同じスパン構造を共有するように、必要に応じて(明示的に表現された)未投入のバケットを追加することで調整されます。バケットが消失した場合、これは簡単です。なぜなら、消失したバケットは、ヒストグラムがチャンクに追加される際に、未投入のバケットとして新しいヒストグラムに単純に追加されるためです。しかし、以前に投入されていたバケットの消失はカウンターリセットを構成するため(以下参照)、このケースはゲージヒストグラム(カウンターリセットを特徴としない)でのみ発生します。はるかに一般的なケースは、新しく追加されたヒストグラムに、以前に追加されたヒストグラムには存在しなかったバケットが存在することです。この場合、これらのバケットは、以前に追加されたすべてのヒストグラムに明示的に未投入のバケットとして追加される必要があります。これにより、チャンク全体の完全な再エンコーディングが必要になります。(影響を受ける部分のみを再エンコードする最適化の可能性がありますが、これを実装するのはかなり複雑でしょう。これまでのところ、完全な再エンコーディングによるパフォーマンスへの影響は問題として際立っていません。)

陳腐化マーカー

注:以下のセクションを理解するためには、TSDBにおける陳腐化マーカーの仕組みを思い出すことが重要です。浮動小数点系列の陳腐化マーカーは、NaN値を表現するために使用できる多くのビットパターンの中から、特定の1つのビットパターンによって表現されます。この非常に特殊な浮動小数点値は、以下のセクションで「特別な陳腐化NaN値」と呼ばれます。これは通常の算術浮動小数点演算によって返されることは(ほぼ確実に)なく、したがって観測値の特殊なケースで議論されているものを含む「自然発生的な」NaN値とは異なります。実際、特別な陳腐化NaN値はTSDBをクエリする際に直接返されることはありませんが、呼び出し元に到達する前に内部で処理されます。

ヒストグラム系列に陳腐化をマークするためには、通常の特別な陳腐化NaN値を使用できます。しかし、これには系列を陳腐化としてマークする目的のためだけに新しいチャンクを切り出す必要があります。なぜなら、ヒストグラム値に続く浮動小数点値は異なるチャンクに格納されなければならないからです(上記参照)。そのため、観測の合計のフィールドが特別な陳腐化NaN値に設定される、陳腐化マーカーのヒストグラムバージョンも存在します。この場合、他のすべてのフィールドは無視され、効率的なストレージに適した値に設定できます(陳腐化マーカーのヒストグラムバージョンは本質的にストレージ最適化に過ぎないため)。これは浮動小数点ヒストグラムと整数ヒストグラムの両方で機能し(合計フィールドは整数ヒストグラムでも浮動小数点値であるため)、適切なバージョンを使用して新しいチャンクを切り出すことを避けることができます。陳腐化マーカーのすべてのバージョン(浮動小数点、整数ヒストグラム、浮動小数点ヒストグラム)は、TSDBによって同等に扱われなければなりません。

チャンクサイズ制限

浮動小数点チャンクのサイズは1024バイトに制限されています。同様のサイズ制限は通常、ヒストグラムチャンクにも適用されます。しかし、個々のヒストグラムは多くのバケットを持つ場合、非常に大きくなる可能性があるため、サイズ制限を盲目的に適用すると、チャンクあたりのヒストグラムが非常に少なくなる可能性があります。(最も極端な場合、単一のヒストグラムが1024バイトを超えることさえあり、サイズ制限を全く適用できないことになります。)チャンクあたりのヒストグラムが非常に少ないと、圧縮率が悪化します。そのため、1024バイトのサイズ制限が適用される前に、チャンクあたり最低10個のヒストグラムに達する必要があります。これは、ヒストグラムチャンクが1024バイトよりもはるかに大きくなる可能性があることを意味します。

チャンクあたり最低10個のヒストグラムを要求することは、チャンクサイズと圧縮率の間でより良いトレードオフを見つけるために、将来的には改善される可能性のある初期の非常に単純なアプローチです。

カウンターリセットに関する考慮事項

一般的に、Prometheusはカウンターの値が次のサンプルに比べて減少した場合にリセットされたと見なします(ただし、作成タイムスタンプに関する次のセクションも参照してください)。2つのヒストグラムサンプル間でカウンターリセットを検出する場合、状況はより複雑になります。

まず、ゲージヒストグラムとカウンターヒストグラムは明示的に異なります(Prometheusは通常、取り込み後にすべての浮動小数点サンプルを、ゲージとして取り込まれたかカウンターメトリックとして取り込まれたかに関わらず、同等に扱います)。カウンターリセットはゲージヒストグラムには適用されません。

時系列においてゲージヒストグラムの後にカウンターヒストグラムが続く場合、カウンターリセットが発生したと仮定されます。なぜなら、ゲージからカウンターへの変更は、ゲージが削除され、カウンターがゼロから新たに作成されたのと同等と見なされるためです。

最も一般的なケースは、カウンターヒストグラムの後に別のカウンターヒストグラムが続く場合です。この場合、可能性のあるカウンターリセットは以下の手順で検出されます

2つのヒストグラムがスキーマまたはゼロバケットの幅で異なる場合、これらの変更は互換性のある解像度削減の一部である可能性があります(これはヒストグラムのバケット数を減らすために定期的に発生します)。互換性のある解像度削減の場合、以下の両方が真です

  • スキーマが変更された場合、その番号は1つの標準指数スキーマから別の標準スキーマへと減少しています。
  • ゼロバケットの幅が変更された場合、最初のヒストグラム内の投入された通常のバケットは、2番目のヒストグラムのゼロバケットに完全に含まれるか、まったく含まれないかのいずれかです(つまり、古い通常のバケットと新しいゼロバケットの部分的な重複はありません)。

いずれかの条件が満たされない場合、その変更は互換性のある解像度削減ではありません。そのような変更はヒストグラムのリセットまたは新規作成によってのみ可能であるため、カウンターリセットと見なされ、検出手順は終了します。

両方の条件が満たされた場合、最初のヒストグラムはそのスキーマとゼロバケットの幅が2番目のヒストグラムと一致するように変換されなければなりません。これは以前に説明されたのと同じ方法で行われます。つまり、隣接するバケットはスキーマを減らすためにマージされ、通常のバケットはゼロバケットとマージされてゼロバケットの幅が広げられます。

この手順の時点で、両方のヒストグラムは同じスキーマとゼロバケット幅を持ちます。これは最初からそうであったか、最初のヒストグラムがそれに応じて変換されたかのいずれかです。(NHCBはゼロバケットを使用しないことに注意してください。この手順のために、それらのゼロバケット幅と母集団カウントは等しいと見なされます。)この状況では、以下のいずれかがカウンターリセットを構成します

  • 観測数の減少(ただし、観測の合計の減少は含まれません)。
  • ゼロバケットを含む、いずれかのバケットの母集団カウントの減少。これには、投入されたバケットが消失するケースも含まれます。なぜなら、表現されていないバケットは、母集団がゼロのバケットと同等であるためです。
  • カスタム値の任意の変更。これはカスタム値を使用するスキーマ(現在スキーマ-53、すなわちNHCB)にのみ適用されます。(TODO: 原則として、NHCBでも互換性のあるバケット変更の概念が存在しうるが、そのような概念はまだ実装されていない。)

上記のいずれにも該当しない場合、カウンターリセットはありません。

この一連の手順は比較的複雑であるため、カウンターリセットの検出は取り込み中に一度行うことが望ましく、その結果は後で使用するために永続化されます。カウンターリセットは新しいチャンクを切り出すトリガーの一つであるため、取り込み中のカウンターリセット検出は anyhow 発生しなければなりません。

カウンターリセット後に新しいチャンクを切り出す目的は、圧縮率を向上させることです。カウンターリセットはすべてのバケット母集団をゼロに設定するため、表現すべきバケットは少なくなります。しかし、チャンクはチャンク内のすべてのヒストグラムのすべてのバケットのスーパーセットを表現しなければならないため、新しいチャンクを切り出すことで、新しいチャンクのためのより単純なバケットセットが可能になります。

これは、チャンク内の最初のサンプル以降にカウンターリセットが発生することはない、ということを意味します。したがって、永続化する必要があるカウンターリセット情報は、チャンク内の1番目のヒストグラムの情報のみです。これは、チャンク内のサンプル数の直後に格納される1バイトである、いわゆるヒストグラムフラグで行われます。このバイトは現在、カウンターリセット情報にのみ使用されていますが、将来的には他のフラグにも使用される可能性があります。カウンターリセット情報は最初の2ビットを使用します。4つの可能なビットパターンは、chunkencパッケージのCounterResetHeader型のGo定数として表現されます。それらの名前と意味は以下の通りです

  • GaugeType(ビットパターン11):チャンクにはゲージヒストグラムが含まれています。カウンターリセットはゲージヒストグラムには関係ありません。
  • CounterReset(ビットパターン10):前のチャンクの最後のヒストグラムとこのチャンクの1番目のヒストグラムの間でカウンターリセットが発生しました。(新しいチャンクが切り出されたのは、実際にはカウンターリセットが原因である可能性が高いです。)
  • NotCounterReset(ビットパターン01):前のチャンクの最後のヒストグラムとこのチャンクの1番目のヒストグラムの間でカウンターリセットは発生しませんでした。(これは、前のチャンクがサイズ制限に達したために新しいチャンクが切り出された場合によく発生します。)
  • UnknownCounterReset(ビットパターン00):前のチャンクの最後のヒストグラムとこのチャンクの1番目のヒストグラムの間でカウンターリセットがあったかどうかは不明です。

UnknownCounterResetは常に安全な選択です。これはカウンターリセット検出を妨げるものではなく、カウンターリセット情報が必要になるたびにカウンターリセット検出手順を(再度)実行する必要があることを意味するだけです。

カウンターリセット情報は、TSDBをクエリする際に呼び出し元に伝播されます(Goコードでは、Go型HistogramFloatHistogram内のCounterResetHint型のフィールドとして、上記のビットパターン定数と同じ名前を持つ列挙定数を使用して)。

ゲージヒストグラムの場合、CounterResetHintは常にGaugeTypeです。その他のCounterResetHint値は、当該ヒストグラムがカウンターヒストグラムであることを意味します。このようにして、クエリ実行者(PromQLエンジンを含む。以下参照)は、ヒストグラムがゲージであるかカウンターであるかの情報を取得します(これは浮動小数点サンプルとは著しく異なります)。

カウンターヒストグラムが単一のチャンクから順序通りに返される限り、チャンク内の2番目以降のヒストグラムのCounterResetHintNotCounterResetに設定されます。(重複するブロックや順不同の取り込みは、複数のチャンクからヒストグラムシーケンスが生じる可能性があり、特別な処理が必要となります。以下を参照。)

カウンターヒストグラムチャンクから1番目のヒストグラムを返す際、TSDB実装が、以前に返されたヒストグラムが取り込み時にカウンターリセットを検出するための先行ヒストグラムとして使用されたものと本当に同じヒストグラムであることを保証できる場合を除きCounterResetHintUnknownCounterResetに設定されなければなりません。後者の場合にのみ、チャンクからのカウンターリセット情報を、返されるヒストグラムのCounterResetHintとして直接使用することができます。

この予防策が必要なのは、チャンクが削除または挿入される様々な方法があるためです(例:墓標による削除や、バックフィル用のブロックの追加)。カウンターリセットは、1つのサンプルに起因するとされる一方で、実際にはマークされたサンプルと先行するサンプルので発生しています。先行するサンプルを削除したり、2つのサンプルの間に別のサンプルを挿入したりすると、以前に行われたカウンターリセット検出が無効になります。

TODO: 現在、Prometheus TSDBは、先行するチャンクが取り込み時と同じチャンクであることを保証する手段を持っていません。そのため、Prometheusは現在、カウンターヒストグラムチャンクのすべての最初のヒストグラムに対してUnknownCounterResetを返します。これに変更を加えるための取り組みについては、トラッキングイシューを参照してください。

すでに上記で示唆されているように、CounterResetHintUnknownCounterResetに設定されている場合、クエリ実行者はカウンターリセット検出手順を(再度)実行しなければなりません。

重複するブロックや順不同のサンプルを処理する際(クエリ時またはコンパクション時)には、特別な注意が必要です。以下の例が示すように、これらのケースではカウンターリセットの過剰検出と過少検出の両方が発生する可能性があります

  • 過少検出の例: あるチャンクにはカウンターリセットなしのサンプルABCが含まれています。別のチャンクには、同様にカウンターリセットなしのサンプルDEFが含まれています。これらのチャンクは重複しており、同じ系列を参照しています。これらを一緒にクエリすると、サンプルの時間順序はADBECFとなることが判明しました。これらのサンプルの一部またはすべてにおいて、カウンターリセットが発生している可能性が十分にあります。これは、2つのサンプルが実際には無関係な系列に由来し、誤って同じ系列にマージされた場合に特に可能性が高いです。しかし、このような偶発的なマージであっても、TSDBによって正しく処理されなければなりません。重複するチャンクが新しいチャンクにコンパクションされる場合、新しいカウンターリセット検出が行われ、新たなカウンターリセットが捕捉される必要があります。重複するチャンクを直接クエリする場合(事前のコンパクションなしで)、以前に返されたサンプルとは異なるチャンクから来る各サンプルにはUnknownCounterResetCounterResetHintを設定する必要があり、これはクエリ実行者によるカウンターリセット検出を義務付けます(上記の安全なフォールバックを利用して)。
  • 過剰検出の例: サンプルABCDのシーケンスがあり、BとCの間でカウンターリセットが発生しています。しかし、最初の取り込みではBとCが欠落していたため、AとDのみが取り込まれ、AとDの間でカウンターリセットが検出されました。その後、BとCが取り込まれ(順不同の取り込み、または後にTSDBに別ブロックとして追加された独立したチャンクを介して)、BとCの間でカウンターリセットが検出されました。この場合、各サンプルは独自のチャンクに入り、すべてのチャンクを組み立てても、それらは重複しません。しかし、上記のルールに従ってカウンターリセットヒントを返すと、現在CとDの間にはカウンターリセットがないにもかかわらず、CとDの両方がCounterResetCounterResetHintとともにクエリ実行者に返されます。前の例の状況と同様に、AとBの間で新たなカウンターリセット検出、そしてCとDの間で別の検出を実行する必要があります。あるいは、BとDの両方をUnknownCounterResetCounterResetHintとともに返す必要があります。

要約すると、TSDBが2つのサンプル間でのカウンターリセット検出が取り込み時に安全に発生したことを確立できない場合、それは別のカウンターリセット検出を実行するか、2番目のサンプルに対してUnknownCounterResetCounterResetHintを返さなければなりません。

上記の手順では検出されないカウンターリセットの可能性も存在することに注意してください。それは、リセットされたヒストグラムのカウントが十分に早く増加し、カウンターリセット後の最初のサンプルが、カウンターリセット前の最後のサンプルと比較して減少したカウントを持たない場合です。(これは浮動小数点カウンターでも問題となり、実際にはより発生しやすいです。)上記で説明したメカニズムを使用すれば、この場合でもカウンターリセットを保存することが可能です。ただし、カウンターリセットが他の手段で検出された場合に限ります。しかし、チャンクの挿入と削除、順不同サンプル、重複ブロック(上記で説明)によって引き起こされる複雑さのため、2回目のカウンターリセット検出が必要な場合、この情報が失われる可能性があります。(TODO: 現在、この情報は確実に失われます。上記のTODOを参照してください。)カウンターリセットを安全にマークするより良い方法は、作成タイムスタンプを介することです(次セクション参照)。

作成タイムスタンプの処理

OpenMetricsは、カウンター、サマリー、および従来のカウンターヒストグラムのために、いわゆる作成タイムスタンプを導入しました。(この用語は恐らく「created-at timestamp」の略です。より適切な用語は「creation timestamp」または「reset timestamp」だったかもしれませんが、「created timestamp」という用語は現在しっかりと確立されています。)

作成タイムスタンプは、メトリックが作成またはリセットされた最新の時刻を提供します。Prometheusが作成タイムスタンプをどのように扱うかは、設計ドキュメントに記載されています。

作成タイムスタンプはネイティブヒストグラムにも役立ちます。浮動小数点カウンターに合成ゼロサンプルが挿入されるのと同じように、カウンターヒストグラムにはヒストグラムサンプルのゼロ値が挿入されます。ヒストグラムのゼロ値は投入されたバケットを持たず、観測の合計、観測の数、およびゼロバケットの母集団はすべてゼロです。スキーマ、ゼロバケットの幅、カスタム値、およびヒストグラムの浮動小数点と整数の種類は、合成ゼロサンプルの直後に続くサンプルと一致するべきです(誤ったカウンターリセットの検出をトリガーしないため)。

合成ゼロサンプルのカウンターリセット情報は常にCounterResetに設定されます。(TODO: 現在、Prometheusは系列の最初のサンプルに対してUnknownCounterResetに設定している可能性が高いですが、これは間違いではありませんが、CounterResetに設定する方がより理にかなっていると思います。)

Exemplars

ネイティブヒストグラムのエクセンプラーは、個々のバケットではなく、ヒストグラムサンプル全体に添付されます。(公開フォーマットセクションも参照。)そのため、単一のネイティブヒストグラムサンプルに複数のエクセンプラーが添付されることが許可されています(そして実際、それが一般的なケースです)。

Exemplarsは、あるスクレイプから次のスクレイプへと変化する可能性がありますが、変化しない場合もあります。スクレイパーは、多くの重複するExemplarsを保存しないように、変更されていないExemplarsを検出するべきです。ただし、単一のサンプルには多くのExemplarsが含まれる可能性があり、その一部が前回のスクレイプからの重複Exemplarsである可能性があるため、重複検出は潜在的にコストがかかる場合があります。TSDBは、新しいExemplarが以前に公開されたどのExemplarよりも新しいタイムスタンプを持つという仮定に依存してもよいです。(ネイティブヒストグラムのExemplarsはタイムスタンプを持たなければならないことを忘れないでください。)これにより、効率的な方法で重複検出が可能になります

  1. 新しく取り込まれたネイティブヒストグラムのエクセンプラーは、以下のフィールドでソートされます:まずタイムスタンプ、次に値、そしてラベル。
  2. エクセンプラーは、ソートされた順序でエクセンプラー貯蔵に追記されます。
  3. 最後に正常に追記されたエクセンプラー(同じメトリックに対する以前のスクレイプからのものである可能性あり)よりも前にソートされる、または等しいエクセンプラーの場合、追記は失敗します。
  4. 最後に正常に追記されたエクセンプラーよりも後にソートされるエクセンプラーの場合、追記は成功します。

Exemplarsは、取り込まれたヒストグラムのすべてのExemplarsが最後に正常に追記されたExemplarよりも前にソートされる場合にのみ順不同としてカウントされます。これは、新しいExemplarsや最後に正常に追記されたExemplarの重複と混在している順不同のExemplarsを検出するものではありませんが、これは許容範囲と見なされます。

PromQL

このセクションでは、PromQLがネイティブヒストグラムをどのように処理するかについて説明します。個々の操作のすべての詳細ではなく、一般的な概念に焦点を当てています。後者については、オペレーター関数に関するPromQLドキュメントを参照してください。

アノテーション

ネイティブヒストグラムの導入により、PromQL式が予期せぬ結果を返す特定の状況、最も一般的には出力ベクトルの要素の一部またはすべてが予期せず欠落するケースが生じます。ユーザーがこれらの状況を検出し、理解するのを助けるため、ネイティブヒストグラムを操作する際にはアノテーションがよく使用されます。アノテーションには警告レベルと情報レベルがあり、評価中に発生する可能性のある問題を記述します。警告レベルは、ユーザーが対処する必要がある実際の問題である可能性が最も高い状況をマークするために使用されます。情報レベルは、意図的なものである可能性もあるが、それでもフラグを立てるには十分に異常な状況に使用されます。

整数ヒストグラム vs 浮動小数点ヒストグラム

PromQLは常に浮動小数点ヒストグラムに対して動作します。整数ヒストグラムとして格納されているネイティブヒストグラムは、TSDBから取得される際に自動的に浮動小数点ヒストグラムに変換されます。

ヒストグラム間の互換性

オペレーターまたは関数が2つ以上のネイティブヒストグラムに対して動作する場合、関連するヒストグラムは同じスキーマとゼロバケット幅を持つ必要があります。特定の制限内で、ヒストグラムはこれらの互換性基準を満たすためにオンザフライで変換できます

  • NHCB(スキーマ -53)は、常に他のNHCBと互換性があり、それらも正確に同じカスタム値を持っていなければなりません。(原則として、調整可能なカスタム値の相違が存在する可能性はありますが、PromQLはまだそれらを考慮していません。)
  • 標準スキーマを持つヒストグラムは、より大きなスキーマ(つまり高解像度)を持つヒストグラムの解像度を下げることにより、常に最小(つまり最低解像度)の共通スキーマに変換できます。これは、隣接するバケットをより小さなスキーマの大きなバケットにマージする通常の方法で行われます。
  • 異なるゼロバケット幅は、より小さなゼロバケットを拡張し、投入された通常のバケットを適切に拡張されたゼロバケットにマージすることで処理されます。最大の共通幅が、投入されたバケットの途中で終わる場合、そのバケットの境界と一致するようにさらに拡張されます。(詳細は上記のゼロバケットセクションを参照してください。)

互換性の問題により操作が妨げられた場合、結果に警告レベルのアノテーションが追加されます。

カウンタリセット

カウンタリセットは、上記で説明されているように定義されます。TSDB から返されるカウンタリセットのヒントは、明示的なカウンタリセットの検出を避け、通常のプロシージャでは検出できないカウンタリセットを正しく処理するために考慮される**場合があります**。(これは、これらのカウンタリセットがベストエフォートベースでのみ考慮されることを意味します。ただし、TSDB 自体についても同様です。上記を参照してください。)従来のヒストグラムおよびサマリーのカウンタリセット処理との顕著な違いは、観測値の合計が減少しても、それ自体がカウンタリセットを構成するわけではないことです。(例えば、ネイティブヒストグラムのレートを計算する場合、ヒストグラムが負の値を観測していたとしても、依然として正しく機能します。)

サブクエリによって返されるカウンターヒストグラムのカウンタリセットヒントは、PromQL エンジンがサブクエリから返される連続したカウンターヒストグラムがTSDB内でも連続していることを安全に検出できる場合を除き、明示的なカウンタリセット検出を避けるために考慮されてはなりません

ゲージヒストグラムとカウンターヒストグラム

TSDB から返されるカウンタリセットヒントにより、PromQL はネイティブヒストグラムがゲージヒストグラムかカウンターヒストグラムかを認識します。PromQL の浮動小数点サンプルに対する扱い(浮動小数点カウンターとゲージを確実に区別できない)を反映するため、カウンターに作用する関数は、ゲージヒストグラムも処理し、その逆も同様ですが、結果と共に警告レベルのアノテーションが返されます。この場合、ゲージヒストグラムに対して明示的なカウンタリセット検出を実行し、あたかもカウンターヒストグラムであるかのように扱う必要があることに注意してください。

バケット内の補間

クォンタイルまたは割合を推定する場合、PromQL はバケット内で補間を適用する必要があります。従来のヒストグラムでは、この補間は線形で行われます。これは、観測値がバケット内に均等に分布しているという仮定に基づいています。実際には、この仮定は大きく外れている可能性があります。(例えば、APIエンドポイントがほとんどすべてのリクエストに110msのレイテンシで応答する場合、中央値レイテンシ、そしておそらく90パーセンタイルレイテンシも110msに近くなるでしょう。従来のヒストグラムが100msと200msにバケット境界を持っている場合、その範囲でほとんどの観測値を見て、中央値を150ms、90パーセンタイルを190msと推定するでしょう。)最悪のケースは、実際の値がバケットの反対側にあるのに、バケットの一端で推定を行うことです。したがって、最大可能な誤差はバケット全体の幅です。補間をまったく行わず、バケット内の固定の中間点(例えば算術平均、あるいは調和平均)を使用すると、最大可能な誤差は最小化されますが(算術平均の場合、バケット幅の半分になります)、実際には、線形補間は平均してより低い誤差をもたらします。補間は長年の従来のヒストグラムの使用でうまく機能してきたため、ネイティブヒストグラムにも補間が適用されます。

NHCB の場合、PromQL は結果の一貫性を保つために、従来のヒストグラムと同じ補間方法を適用します。(NHCB の主なユースケースは、従来のヒストグラムのドロップイン代替です。)ただし、標準的な指数スキーマの場合、線形補間は不適切と見なされる可能性があります。指数スキーマは、主にクォンタイル推定の相対誤差を最小化することを目的としていますが、少なくとも観測値の特定の範囲では、バケットのバランスの取れた使用からも恩恵を受けます。基本的な仮定は、ほとんどの実用的な分布において、観測値の密度は小さい観測値ほど高くなる傾向があるということです。したがって、PromQL は標準スキーマに対して指数補外を使用します。これは、スキーマ番号を1増やす(つまり、解像度を2倍にする)ときにバケットを2つに分割すると、平均して両方の新しいバケットで同様の母集団が見られるという仮定をモデル化しています。補間方法を実装する PR は こちら でより詳細な説明が確認できます。

特殊なケースとして、ゼロバケット内の補間があります。ゼロバケットは指数バケット化スキーマを破綻させます。したがって、ゼロバケット内では線形補間が適用されます。さらに、ヒストグラムのすべての有効な通常バケットが正である場合、ゼロバケット内のすべての観測値も正であると仮定されます。つまり、補間はゼロとゼロバケットの上限の間で行われます。ヒストグラムのすべての有効な通常バケットが負である場合、状況は逆転し、ゼロバケット内の補間はゼロバケットの下限とゼロの間で行われます。

混合系列

上記ですでに議論したように、ネイティブヒストグラムのサンプルタイプもフレーバーも系列のアイデンティティの一部ではありません。したがって、同じ系列が異なるサンプルタイプとフレーバーの混合を含む可能性があります。

カウンターヒストグラムとゲージヒストグラムの混合は、PromQL の操作を妨げませんが、入力サンプルの一部が不適切なフレーバーを持っている場合、警告レベルのアノテーションが結果と共に返されます(上記参照)。

浮動小数点サンプルとヒストグラムサンプルの混合は、より問題があります。範囲ベクトルを操作する多くの関数は、入力要素に浮動小数点とヒストグラムの混合が含まれる場合、結果から要素を削除します。この場合、警告レベルのアノテーションが結果に追加されます。具体的な例は 下記 で確認できます。

単項マイナスと負のヒストグラム

ネイティブヒストグラムに単項マイナスを使用できます。これにより、すべてのバケットの個体数、観測値のカウント、および合計の符号が反転したヒストグラムが返されます。カウンタリセットヒントを含む他のすべては同じままです。ただし、明示的なカウンタリセット検出は、反転した符号によって妨げられることに注意してください。(TODO: すべての負のヒストグラムをゲージとしてマークすべきでしょうか?)負のヒストグラムはそれ自体ではあまり意味がなく、他の式の中間結果としてのみ機能することを意図しています。

二項演算子

ほとんどの二項演算子は、2つのヒストグラム間、またはヒストグラムと浮動小数点数間、またはヒストグラムとスカラー間では機能しません。演算子がそのような不可能な組み合わせを処理する場合、対応する要素は出力ベクトルから削除され、情報レベルのアノテーションが結果に追加されます。(この状況は、サンプルタイプがラベルに似た役割を果たすラベルマッチングといくらか似ています。したがって、このような不一致は既知であり意図的なものである可能性があり、これがアノテーションのレベルが情報のみである理由です。)

以下に、実際に機能するすべての操作を説明します。

加算(+)と減算(-)は、2つの互換性のあるヒストグラム間で機能します。これらの演算子は、一致するすべてのバケットの個体数と、観測値のカウントおよび合計を加算または減算します。欠落しているバケットは空であると見なされ、それに応じて処理されます。減算は負のヒストグラムになる場合があります。詳細は上記の注記を参照してください。一般的に、両方のオペランドはゲージであるべきです。カウンターヒストグラムの加算と減算には注意が必要ですが、PromQL はこれを許可します。ゲージヒストグラムとカウンターヒストグラムを加算すると、ゲージヒストグラムになります。矛盾するカウンタリセットヒントを持つ2つのカウンターヒストグラムを加算すると、警告レベルのアノテーションがトリガーされます。(TODO: 後者はまだ実装されていません。また、減算はまだカウンタリセットヒントをチェック/変更していません。これはPromQLのドキュメントで詳細に記述されるべきです。)

乗算(*)は、浮動小数点サンプルまたはスカラーとヒストグラムの間で、順不同で機能します。これは、すべてのバケットの個体数、観測値のカウント、および合計を浮動小数点数(サンプルまたはスカラー)で乗算します。これにより、「スケーリングされた」ヒストグラム、場合によっては負のヒストグラムが生成されますが、これらは通常、他の式の中間結果としてのみ有用です(上記の注記も参照)。乗算はカウンターヒストグラムとゲージヒストグラムの両方で機能し、そのフレーバーは操作によって変更されません。

除算(/)は、左側のヒストグラムと右側の浮動小数点サンプルまたはスカラーの間で機能します。これは、浮動小数点数(サンプルまたはスカラー)の逆数による乗算と同等です。ゼロによる除算は、通常のバケットがなく、ゼロバケットの個体数、観測値のカウント、および合計がすべて、入力ヒストグラム内の値(それぞれ正、負、またはゼロ/NaN)に応じて、+Inf-Inf、またはNaN に設定されたヒストグラムを生成します。

等価(==)と不等価(!=)は、2つのヒストグラム間で機能し、フィルタリングバージョンと bool 修飾子の両方で動作します。これらは、スキーマ、カスタム値、ゼロ閾値、すべてのバケットの個体数、および観測値の合計とカウントを比較します。ヒストグラムがカウンターフレーバーかゲージフレーバーかは比較に無関係です。(カウンターヒストグラムはゲージヒストグラムと等しい場合があります。)

論理/セット二項演算子(andorunless)は、ヒストグラムサンプルが関与する場合でも期待通りに機能します。これらはベクトル要素の存在のみをチェックし、要素のサンプルタイプやフレーバー(浮動小数点またはヒストグラム、カウンターまたはゲージ)に応じて動作を変更しません。

「トリム」演算子 >/ および </ は、ネイティブヒストグラム専用に導入されました。これらは左側のヒストグラムと右側の浮動小数点サンプルまたはスカラーに対してのみ機能します。(両側が浮動小数点サンプルまたはスカラーである場合には機能しません。この場合、情報レベルのアノテーションが返されます。)これらの演算子は、ヒストグラムから右側の浮動小数点値よりも大きいか小さい観測値をそれぞれ削除し、結果のヒストグラムを返します。削除は、閾値がバケット境界と一致する場合にのみ正確です。そうでない場合は、上記で説明されているように、影響を受けるバケット内で補間を使用する必要があります。ヒストグラムのカウンターとゲージのフレーバーは保持されます。(TODO: これらの演算子はまだ実装されておらず、詳細が変更される可能性もあります。追跡課題についてはこちらを参照してください。)

集約演算子

以下の集約演算子は、浮動小数点サンプルとヒストグラムサンプルで同じように機能します(括弧内に示された理由による)。

  • group (この集約の結果はサンプル値に依存しません。)
  • count (この集約の結果はサンプル値に依存しません。)
  • count_values (Go の FloatHistogram.String メソッドによって生成されるテキスト表現が、ヒストグラムの値として使用されます。)
  • limitk (サンプリングされた要素は変更されずに返されます。)
  • limit_ratio (サンプリングされた要素は変更されずに返されます。)

sum 集約演算子は、集約されるヒストグラムを合計することで(上記の + 演算子で説明したのと同じ方法で)ネイティブヒストグラムで機能します。avg 集約演算子も同様に機能しますが、合計を集約されたヒストグラムの数で割ります(上記の / 演算子で説明したのと同じ方法で)。どちらの集約演算子も、浮動小数点サンプルとヒストグラムサンプルの集約を必要とする要素を出力ベクトルから削除します。このような削除は警告レベルのアノテーションによってフラグが立てられます。

他のすべての集約演算子は、ネイティブヒストグラムでは機能しません。入力ベクトル内のヒストグラムは単純に無視され、無視されたヒストグラムごとに情報レベルのアノテーションが追加されます。

関数

以下の関数は、ネイティブヒストグラムの範囲ベクトルに対して、一致するバケット(ゼロバケットを含む)と観測値の合計およびカウントに個別に通常の浮動小数点演算を適用することで機能し、新しいネイティブヒストグラムを生成します。

  • delta() (ゲージヒストグラム用。)
  • increase() (カウンターヒストグラム用。)
  • rate() (カウンターヒストグラム用。)
  • idelta() (ゲージヒストグラム用。)
  • irate() (カウンターヒストグラム用。)

これらの関数は、上記で述べたように、ゲージヒストグラムまたはカウンターヒストグラムのいずれかに適用されるべきです。ただし、これらは両方のフレーバーで機能しますが、範囲ベクトルに不適切なフレーバーのヒストグラムが少なくとも1つ含まれている場合、警告レベルのアノテーションが結果に追加されます。

delta()increase()、および rate() は、範囲内に浮動小数点サンプルとヒストグラムサンプルの混合を含む系列に対しては結果を返しません。idelta() および irate() は、範囲内の最後の2つのサンプルが浮動小数点サンプルとヒストグラムサンプルの混合である系列に対しては結果を返しません。いずれの場合も、これらの理由で欠落した出力要素ごとに警告レベルのアノテーションが追加されます。

これらすべての関数は、結果としてゲージヒストグラムを返します。

通常通り、これらの関数は、可能な限りスキーマを共通のものに変換することで、異なるスキーマを調整しようとします。ただし、カウンターに適用される関数(increase()rate()irate())は、1番目のサンプルと2番目のサンプルの間にカウンタリセットがある場合、1番目のサンプルに対してこの変換を実行しません。この場合、1番目のサンプルは計算に含まれないため、1番目のサンプルと他のサンプル間の互換性のないバケットレイアウトは単に無視されます。

TODO: ネイティブヒストグラムの場合、ゼロ以下への外挿の防止は現在まだ実装されていません(そして実際には意味がないかもしれません)。これは、従来のヒストグラムと同等の NHCB を比較した場合に、わずかに異なる結果をもたらす可能性があります。

avg_over_time() および sum_over_time() は、それぞれの集約演算子に対応する方法でネイティブヒストグラムで機能します。特に、系列が範囲内に浮動小数点サンプルとヒストグラムサンプルの混合を含む場合、対応する結果は出力ベクトルから完全に削除されます。このような削除は警告レベルのアノテーションによってフラグが立てられます。

changes() 関数と resets() 関数は、浮動小数点サンプルと同様に、ネイティブヒストグラムサンプルでも機能します。これらは、同じ系列内で浮動小数点サンプルとヒストグラムサンプルの混合が存在する場合でも機能します。この場合、浮動小数点サンプルからヒストグラムサンプルへの変更、およびその逆の変更は、changes() では変更として、resets() ではリセットとしてカウントされます。カウンターヒストグラムからゲージヒストグラムへのフレーバーの変更、およびその逆は、changes() では変更としてカウントされません。resets() はカウンター浮動小数点とカウンターヒストグラムにのみ適用されるべきですが、この関数はゲージヒストグラムでも機能し、その場合は明示的なカウンタリセット検出を適用します。さらに、カウンターヒストグラムからゲージヒストグラムへの変更、およびその逆はリセットとしてカウントされます。

histogram_quantile() 関数は、特定の「魔法の」ラベル、すなわち従来のヒストグラムで使用される le ラベルを特別に扱う唯一の関数であるため、非常に特殊な役割を担っています。histogram_quantile() はネイティブヒストグラムに対しても同様に機能しますが、le ラベルの特殊な役割はありません。この関数は、浮動小数点サンプルについては既知の方法で処理し続け、ネイティブヒストグラムサンプルについては新しい「ネイティブ」な方法を使用します。

従来のヒストグラムの典型的なクエリの例(rate と集約を含む)

histogram_quantile(0.9, sum by (job, le) (rate(http_request_duration_seconds_bucket[10m])))

これはネイティブヒストグラムに対応するクエリです: histogram_quantile(0.9, sum by (job) (rate(http_request_duration_seconds[10m])))

従来のヒストグラムと同様に、histogram_quantile の最初のパラメータとしてそれぞれ 1 と 0 を使用することで、ヒストグラム内の最大および最小の観測値を推定できます。しかし、標準スキーマを持つネイティブヒストグラムは、通常ネイティブヒストグラムの解像度が高いだけでなく、標準スキーマを持つネイティブヒストグラムが float64 数値の全範囲で同じ解像度を維持するため、はるかに有用な結果を可能にします。従来のヒストグラムでは、最大観測値が +Inf バケットにある可能性が高く、その場合、推定値は単に +Inf バケットの前の最後のバケットの上限を返します。同様に、最小観測値は最低のバケットにあることが多いでしょう。

histogram_quantile は、値 NaN の観測値(上記参照、発生すべきではありません)を事実上 +Inf の観測値として扱います。これは、NaNhistogram_quantile が返すどの値よりも小さくなることはなく、従来のヒストグラムが通常 NaN 観測値を扱う方法(ほとんどの実装で +Inf バケットに収まる)と一貫しているという根拠に基づいています。(TODO: この動作の正しい実装は、まだテストによって検証される必要があります。)

以下の関数は、ネイティブヒストグラム専用に導入されました。

  • histogram_avg()
  • histogram_count()
  • histogram_fraction()
  • histogram_sum()
  • histogram_stddev()
  • histogram_stdvar()

これらすべての関数は、浮動小数点サンプルを入力として受け取った場合、黙って無視します。各関数は浮動小数点サンプルのベクトルを返します。

histogram_count()histogram_sum() は、それぞれネイティブヒストグラムに含まれる観測値のカウントまたは観測値の合計を返します。これらは通常の関数であるため、その結果は範囲セレクタでは使用できません。サブクエリを使用する代わりに、観測値のカウントまたは合計のレートを計算する推奨される方法は、まずヒストグラムのレートを計算し、次にその結果に histogram_count() または histogram_sum() を適用することです。例えば、以下のクエリは、ネイティブヒストグラムから観測値のレート(この場合は「1秒あたりのリクエスト数」に相当)を計算します: histogram_count(rate(http_request_duration_seconds[10m]))

histogram_sum() の結果に対してサブクエリを使用する場合、ネイティブヒストグラムの特別なカウンタリセット検出は適用されないことに注意してください。つまり、負の観測値が誤ったカウンタリセットを引き起こす可能性があります。

histogram_avg() は、ネイティブヒストグラム内の観測値の算術平均を返します。(これは、複数のネイティブヒストグラムに avg 集約演算子を適用することとは大きく異なります。後者は平均化されたヒストグラムを返します。)

同様に、histogram_stddev()histogram_stdvar() は、それぞれネイティブヒストグラム内の観測値の推定標準偏差または標準分散を返します。この推定では、バケット内のすべての観測値がバケット境界の平均値を持つと仮定されます。ゼロバケットおよびカスタム境界を持つバケットの場合、算術平均が使用されます。標準的な指数バケットの場合、幾何平均が使用されます。

histogram_fraction(lower, upper, histogram) は、指定された境界であるスカラー値 lowerupper の間の histogram 内の観測値の推定割合を返します。推定の誤差は、基になるネイティブヒストグラムの解像度と、指定された境界がヒストグラム内のバケット境界とどれだけ密接に一致しているかに依存します。+Inf-Inf は有効な境界値であり、特定の値を上回るまたは下回るすべての観測値の割合を推定するのに有用です。ただし、値 NaN の観測値は、常に指定された境界外と見なされます(+Inf および -Inf であっても)。(TODO: この動作の正しい実装をテストで検証する必要がある。)提供された境界が包括的(inclusive)か排他的(exclusive)かは、提供された境界が基になるネイティブヒストグラムのバケット境界と正確に一致する場合にのみ関連します。この場合、動作はヒストグラムのスキーマの正確な定義に依存します。

以下の関数は、サンプル値と直接やり取りしないため、浮動小数点サンプルと同様にネイティブヒストグラムサンプルでも機能します。

  • absent()
  • absent_over_time()
  • count_over_time()
  • info()
  • label_join()
  • label_replace()
  • last_over_time()
  • present_over_time()
  • sort_by_label()
  • sort_by_label_desc()
  • timestamp()

このセクションで言及されていない残りのすべての関数は、ネイティブヒストグラムでは機能しません。入力ベクトル内のヒストグラム要素は黙って無視されます。deriv()double_exponential_smoothing()predict_linear()、およびこれまでに言及されていないすべての <aggregation>_over_time() 関数については、ネイティブヒストグラムサンプルは入力範囲ベクトルから削除されます。いずれかの系列が範囲内に浮動小数点サンプルとヒストグラムサンプルの混合を含む場合、ヒストグラムの削除は情報レベルのアノテーションによってフラグが立てられます。

レコーディングルール

レコーディングルールは、ネイティブヒストグラム値を生成する**場合があります**。それらは、ヒストグラムがゲージヒストグラムであるかカウンターヒストグラムであるかを含め、通常の取り込み時と同様にTSDBに保存されます。後者の場合、カウンタリセットヒントによって明示的にマークされたカウンタリセットも保存され、そうでない場合は取り込み中に新しいカウンタリセット検出が開始されます。

TSDB 実装は、レコーディングルールによって作成された浮動小数点ヒストグラムを、この変換が元のヒストグラム内のすべての浮動小数点値を正確に表現できる場合、整数ヒストグラムに変換する**場合があります**。

アラートルール

アラートはネイティブヒストグラムで通常通り機能します。ただし、アラートの出力値としてネイティブヒストグラムを使用することは**推奨されません**。テンプレートでネイティブヒストグラムサンプルが使用される場合、それらは単純なテキスト形式(Go の FloatHistogram.String メソッドによって生成される形式)でレンダリングされ、人間が読むのは困難です。

テストフレームワーク

PromQL テストフレームワークは拡張され、PromQL ユニットテストと promtool を介したルールユニットテストの両方でネイティブヒストグラムを含めることができるようになりました。ヒストグラムサンプルの表記法は複雑で、ルールユニットテストのドキュメントで説明されています。

ユニットテストフレームワークには、load_with_nhcb と呼ばれる代替の load コマンドがあります。これは、従来のヒストグラムを NHCB に変換し、従来のヒストグラムの浮動小数点系列と変換によって生成される NHCB 系列の両方をロードします。

ネイティブヒストグラムに特有ではありませんが、その文脈で非常に有用なのが、情報レベルと警告レベルのアノテーションに関する期待値を定義できるユニットテストフレームワークの expect キーワードです。

最適化

通常通り、PromQL の実装は、動作が同じである限り、適切と判断されるあらゆる最適化を適用**する場合があります**。ネイティブヒストグラムのデコードは、潜在的に多くのバケットがあるため、非常にコストがかかる可能性があります。同様に、PromQL エンジン内でヒストグラムサンプルをディープコピーすることは、単純な浮動小数点サンプルをコピーするよりもはるかにコストがかかります。これにより、常にすべてをデコードし、常にすべてをコピーするという素朴なアプローチと比較して、最適化の大きな可能性が生まれます。

Prometheus は現在、不要なコピーを避けようとしており(TODO: しかし、よりクリーンでバグが少ないため、適切な CoW のようなアプローチをまだ実装する必要があります)、観測値の合計とカウントのみが必要な特殊なケースでは、バケットのデコードをスキップします。

Prometheus クエリ API

クエリ API ドキュメントには、ネイティブヒストグラムのサポートが含まれています。このセクションでは、ネイティブヒストグラムに関連する部分に焦点を当て、API ドキュメントの一部ではないコンテキストをいくつか提供します。

インスタントクエリと範囲クエリ

インスタント(query エンドポイント)および範囲(query_range エンドポイント)クエリの JSON レスポンスでネイティブヒストグラムを返すには、vectormatrix の両方の結果タイプに新しいキーによる拡張が必要です。

vector 結果タイプには、既存の value キーと同じレベルに新しいキー histogram が追加されます。これら2つのキーは相互に排他的であり、つまり vector 内の各要素は、value キー(浮動小数点結果用)または histogram キー(ヒストグラム結果用)のいずれか一方を持ちます。histogram キーの値は、value キーの値(2要素配列)と同様の構造ですが、浮動小数点サンプル値を表す文字列が、以下で説明する特定のヒストグラムオブジェクトに置き換えられる点が異なります。

matrix 結果タイプには、既存の values キーと同じレベルに新しいキー histograms が追加されます。これらのキーは相互に排他的ではありません。系列は浮動小数点値とヒストグラム値の両方を含むことができますが、特定のタイムスタンプでは、浮動小数点またはヒストグラムのいずれか1つのサンプルのみが存在する必要があります。histograms キーの値は、values キーの値(n個の2要素配列の配列)と同様の構造ですが、浮動小数点サンプル値を表す文字列が、以下で説明する特定のヒストグラムオブジェクトに置き換えられる点が異なります。

浮動小数点値とヒストグラム値の両方が値であるため、キーのより良い命名は float/histogram および floats/histograms であることに注意してください。現在の命名には歴史的な理由があります。(過去には、値のタイプは浮動小数点数のみであったため、キーを単に valuevalues と呼ぶのが自明の選択でした。)ここでの意図は、ネイティブヒストグラムを知らない既存のコンシューマを壊さないことです。

上記で述べたヒストグラムオブジェクトは、以下の構造を持ちます。

{
  "count": "<count_of_observations>",
  "sum": "<sum_of_observations>",
  "buckets": [ [ <boundary_rule>, "<left_boundary>", "<right_boundary>", "<count_in_bucket>" ], ... ]
}

countsum は、ヒストグラムの同名のフィールドに直接対応します。各バケットは、境界とカウント(ゼロバケットを含む)とともに明示的に表現されます。したがって、スパンとスキーマはレスポンスの一部ではなく、ヒストグラムオブジェクトの構造は使用されるスキーマに依存しません。

<boundary_rule> プレースホルダーは、0から3までの整数で、以下の意味を持ちます。

  • 0:「左開」(左境界は排他的、右境界は包括的)
  • 1:「右開」(左境界は包括的、右境界は排他的)
  • 2:「両開」(両方の境界が排他的)
  • 3:「両閉」(両方の境界が包括的)

標準スキーマの場合、正のバケットは「左開」、負のバケットは「右開」、そしてゼロバケット(負の左境界と正の右境界を持つ)は「両閉」です。NHCB の場合、すべてのバケットは「左開」です(従来のヒストグラムの動作を反映)。将来のスキーマは異なる境界規則を利用する可能性があります。

メタデータ

series エンドポイントの場合、ネイティブヒストグラムを含む系列は、浮動小数点のみを含む従来の系列と同じ方法で含まれます。このエンドポイントは、どのサンプルタイプが含まれているかの情報を提供しません(実際、任意の系列は、どちらか一方または両方のサンプルタイプを含む場合があります)。特に注意すべきは、request_duration_seconds という名前でターゲットによって公開されたヒストグラムがネイティブヒストグラムとして公開され取り込まれた場合、request_duration_seconds という系列につながりますが、従来のヒストグラムとして公開され取り込まれた場合、request_duration_seconds_sumrequest_duration_seconds_count、および request_duration_seconds_bucket と呼ばれる一連の系列につながります。ヒストグラムがネイティブヒストグラムと従来のヒストグラムの両方として取り込まれた場合、上記のすべての系列名が series エンドポイントによって返されます。

ターゲットおよびメトリックのメタデータ(エンドポイント targets/metadata および metadata)は、ターゲットによって公開された元の名前に基づいて動作するため、少し異なります。これは、request_duration_seconds と呼ばれる従来のヒストグラムが、これらのメタデータエンドポイントでは request_duration_seconds としてのみ表現されることを意味します(request_duration_seconds_sumrequest_duration_seconds_count、または request_duration_seconds_bucket ではありません)。ネイティブヒストグラムの request_duration_seconds もこの名前で表現されます。request_duration_seconds が従来のヒストグラムとネイティブヒストグラムの両方として取り込まれた場合でも、返されるメタデータは実際には同じであるため、競合は発生しません(特に返される typehistogram になります)。言い換えれば、現在、メタデータエンドポイントだけではネイティブヒストグラムと従来のヒストグラムを区別する方法はありません。series エンドポイントを介した追加のルックアップが必要です。既存のメタデータエンドポイントは、履歴情報がない、ルールによって作成されたメトリックのメタデータがない、異なるターゲット間の競合するメタデータを処理する能力が限られているなど、いずれにしても厳しく制限されているため、これを変更する計画はありません。ただし、Prometheus 全体でのメタデータ処理を改善する計画はあります。これらの取り組みは、ネイティブヒストグラムを適切にサポートする方法も考慮に入れるでしょう。(TODO: 進捗があり次第更新。)

Prometheus UI

このセクションでは、Prometheus 独自の UI によるヒストグラムのレンダリングについて説明します。これは、サードパーティのグラフフロントエンドのガイドラインとして使用**される場合があります**。

テーブル」ビューでは、ヒストグラムデータポイントは、すべてのバケットの下限と上限、および観測値のカウントと合計のテキスト表現とともに、棒グラフとしてグラフィカルにレンダリングされます。棒グラフの各棒はバケットを表します。各棒の x 軸上の位置は、対応するバケットの下限と上限によって決定されます。各棒の面積は、対応するバケットの個体数に比例します(これは一般的にヒストグラムをレンダリングする際の核となる原則です)。

グラフヒストグラムでは、指数軸と線形軸のどちらかをx軸として選択できます。前者がデフォルトです。これは標準的なスキーマによく適合します。(TODO: 非指数スキーマのデフォルトとして線形を検討する。) 便利なことに、指数スキーマのすべての通常のバケットは、指数x軸上で同じ幅を持ちます。これは、y軸が実際のバケットの集団数を表示できることを意味し、棒の「面積」(高さではなく)がバケットの集団数を代表するという上記の原則に反しません。ゼロバケットはこれの例外です。技術的には、無限の幅を持ちます。Prometheusは、それを通常の指数バケットと同じ幅でレンダリングします(これは、x軸がゼロ点の周りで厳密に指数的ではないことを意味します)。(TODO: 非指数スキーマのレンダリング方法について。)

線形x軸の場合、バケットは一般的に幅が異なります。そのため、y軸はバケットの集団数をその幅で割った値を表示します。Prometheus UIはy軸に値をレンダリングしません。なぜなら、いずれにせよ人間には解釈が難しいからです。集団数はテキスト表現で確認できます。

「グラフ」ビューでは、Prometheusはヒートマップを表示します(TODO: まだ実装されていません、下記参照)。これは、時間の経過に伴う一連のヒストグラムを90度回転させ、バケットの集団数を棒の高さではなく色としてエンコードしたものと見なすことができます。カウンターのようなヒストグラムをヒートマップとしてレンダリングするための典型的なクエリは、rateクエリとなるでしょう。ヒートマップは、分布の特性が時間の経過とともにどのように変化するかを人間が容易に把握できる、非常に強力な表現です。

TODO: ヒートマップはまだ実装されていません。代わりに、UIは観測値の合計を従来のグラフとしてプロットするだけです。[トラッキングイシュー](https://github.com/prometheus/prometheus/issues/11268)を参照してください。同じイシューでは、「テーブル」ビューでのレンジベクトルのレンダリングの扱いについても議論されています。

テンプレート展開

ネイティブヒストグラムはテンプレート展開で機能します。それらは、開区間と閉区間の数学的表記法から着想を得たテキスト表現でレンダリングされます。(これはGoのFloatHistogram.Stringメソッドによって生成されます。) ネイティブヒストグラムは多くのバケットを持つことができ、バケット境界には多くの小数点以下の桁数を持つ傾向があるため、その表現は必ずしも非常に読みやすいわけではありません。テンプレート展開では、ネイティブヒストグラムを慎重に使用してください。

浮動小数点ヒストグラムのテキスト表現の例

{count:3493.3, sum:2.349209324e+06, [-22.62741699796952,-16):1000, [-16,-11.31370849898476):123400, [-4,-2.82842712474619):3, [-2.82842712474619,-2):3.1, [-0.01,0.01]:5.5, (0.35355339059327373,0.5]:1, (1,1.414213562373095]:3.3, (1.414213562373095,2]:4.2, (2,2.82842712474619]:0.1}

リモート書き込みと読み込み

リモート書き込みと読み込みのためのprotobuf仕様は、実験的機能としてネイティブヒストグラム向けに拡張されました。ネイティブヒストグラムを処理できないレシーバーは、新しく追加されたフィールドを単に無視します。それにもかかわらず、Prometheusは、リモート書き込みを介してネイティブヒストグラムを送信するように設定する必要があります(send_native_histogramsリモート書き込み設定をtrueに設定することによって)。

リモート書き込みv2では、ネイティブヒストグラムは安定した機能です。

クラシックヒストグラムを送受信中にNHCBに変換することは魅力的に見えるかもしれません。しかし、これはリモート書き込みを介して送信される際にクラシックヒストグラムが抱える既知の一貫性の問題を解決するものではありません。代わりに、クラシックヒストグラムはスクレイピング中にNHCBに変換されるべきです。同様に、明示的なOTelヒストグラムは、OTLP取り込みの際にすでにNHCBに変換されるべきです。

TODO: リモート書き込みにおける残された潜在的な問題は、同じネイティブヒストグラム用に元々取り込まれた複数のエクセンプラーが異なるリモート書き込みリクエストで送信された場合にどうするかです。

フェデレーション

ネイティブヒストグラムのフェデレーションは、フェデレーションスクレイプがprotobuf形式を使用している限り、期待通りに機能します。OpenMetricsテキスト形式を介したフェデレーションは、その形式でネイティブヒストグラムがサポートされれば、少なくとも原則的には可能ですが、いずれにせよ効率上の理由からprotobufを介したフェデレーションが好まれます。

TODO: NHCBのフェデレーションの状況を明確にする。OpenMetricsがネイティブヒストグラムをサポートしたら更新する。

OTLP

Prometheusに組み込まれているOTLPレシーバーは、上記で説明されている互換性を利用して、受信するOTel指数ヒストグラムをPrometheusネイティブヒストグラムに変換します。スキーマ(OTel用語で「スケール」)が8より大きいヒストグラムの解像度は、スキーマ8に合わせるために削減されます。(スキーマが-4より小さいというありそうもないケースでは、取り込みは失敗します。)

明示的なOTelヒストグラムは、Prometheusのクラシックヒストグラムと同等です。そのため、Prometheusはデフォルトでそれらをクラシックヒストグラムに変換しますが、オプションでNHCBへの直接変換も提供します。

Pushgateway

ネイティブヒストグラムのサポートは、Pushgatewayに段階的に追加されてきました。v1.9で完全なサポートが実現されました。Pushgatewayは常に、その内部データモデルとしてクラシックなprotobuf形式に基づいており、必要な変更は容易でした(主にUIに関する懸念事項)。結合されたヒストグラム(クラシックとネイティブのバケットを持つ)はプッシュでき、/metricsエンドポイントを介してそのまま公開されます。(ただし、プッシュされたメトリクスをJSONとしてクエリするために使用できるクエリAPIは、1種類のバケットしか返せず、ネイティブバケットが存在する場合はそちらが優先されます。)

promtool

このセクションでは、ネイティブヒストグラムをサポートするために追加または変更されたpromtoolコマンドについて説明します。明示的に言及されていないコマンドは、ネイティブヒストグラムと直接やり取りしないため、変更は必要ありません。

promtool query ...コマンドはネイティブヒストグラムで機能します。出力形式については、クエリAPIドキュメントを参照してください。新しいコマンドpromtool query analyzeは、クエリAPIによって返されるクラシックおよびネイティブヒストグラムの使用パターンを分析するために特別に追加されました。

promtool test rulesを介したルール単体テストは、上記で説明されている形式を使用して、ネイティブヒストグラムで機能します。

promtool tsdb analyzepromtool tsdb listはネイティブヒストグラムで正常に機能します。前者の--extended出力には、ヒストグラムチャンクに関する特定のセクションがあります。

promtool tsdb dumpは、ネイティブヒストグラムの通常のテキスト表現を使用します(GoメソッドFloatHistogram.Stringによって生成されるものと同様)。

promtool tsdb create-blocks-from rulesは、ネイティブヒストグラムを出力するルールで機能します。

promtool promql ...コマンドは、ネイティブヒストグラムのために追加されたすべてのPromQL機能をサポートします。

promtool tsdb bench writeは原則としてネイティブヒストグラムを含めることができますが、現時点ではそのサポートは計画されていません。

以下のコマンドはOpenMetricsテキスト形式に依存しており、OpenMetricsでネイティブヒストグラムがサポートされない限り、ネイティブヒストグラムをサポートできません。

  • promtool check metrics
  • promtool push metrics
  • promtool tsdb dump-openmetrics
  • promtool tsdb create-blocks-from openmetrics
TODO: 進捗があり次第更新する。トラッキングイシューを参照してください。

prom2json

prom2jsonは、Prometheusの/metricsエンドポイントをスクレイピングし、メトリクスを独自のJSON形式に変換して標準出力にダンプする小さなツールです。これは、jqなどのJSONを扱うツールでのさらなる処理に便利です。

prom2json v1.4はネイティブヒストグラムのサポートを追加しました。エクスポート内のヒストグラムに少なくとも1つのバケットスパンが含まれている場合、prom2jsonはJSON出力内の通常のクラシックバケットを、PrometheusクエリAPIから着想を得た形式に従って、ネイティブヒストグラムのバケットに置き換えます。

移行に関する考慮事項

クラシックヒストグラムからネイティブヒストグラムに移行する際、考慮すべき3つの重要な問題点があります。

  1. ネイティブヒストグラムのクエリは、クラシックヒストグラムのクエリとは異なる方法で機能します。ほとんどの場合、変更は最小限で単純ですが、信頼性の高い自動変換を困難にするトリッキーなエッジケースが存在します。
  2. クラシックヒストグラムとネイティブヒストグラムは互いに集計できません。ある時点でクラシックヒストグラムからネイティブヒストグラムへの変更を行うと、移行点をまたいで機能するダッシュボードを作成するのが難しくなり、移行点を含むレンジベクトルは必然的に不完全になります(つまり、クラシックヒストグラムを選択するレンジベクトルは範囲の早い部分のデータポイントのみを含み、ネイティブヒストグラムを選択するレンジベクトルは範囲の後の部分のデータポイントのみを含みます)。
  3. クラシックヒストグラムは、関心のあるポイントに正確にバケット境界を持つように調整されている場合があります。標準スキーマを持つネイティブヒストグラムは高解像度を持つことができますが、任意の値にバケット境界を設定することはできません。これらの場合、ネイティブヒストグラムのユーザーエクスペリエンスは実際には悪化する可能性があります。

(3)に対処するためには、該当するクラシックヒストグラムを移行せず、現状維持とすることももちろん可能です。もう一つの選択肢は、計装はそのままにして、取り込み時にクラシックヒストグラムをNHCBに変換することです。これにより、ネイティブヒストグラムのストレージ性能の向上を活用できますが、ネイティブヒストグラムへの完全な移行と同様に(1)および(2)に対処する必要があります(次段落を参照)。

(1)と(2)に対処する保守的な方法は、長い移行期間を設けることであり、これにはクラシックヒストグラムとネイティブヒストグラムをしばらくの間並行して収集・保存するコストがかかります。

最初のステップは、計装を更新してクラシックヒストグラムとネイティブヒストグラムを並行して公開することです。(計装でクラシックヒストグラムを使い続け、スクレイピング中にそれらをNHCBに変換する計画の場合は、このステップをスキップできます。)

次に、クラシックヒストグラムとネイティブヒストグラムの両方をスクレイピングするようにPrometheusを設定します。上記のクラシックヒストグラムとネイティブヒストグラムの両方のスクレイピングに関するセクションを参照してください。(必要に応じて、クラシックヒストグラムのNHCBへの変換を有効にすることもできます。)

クラシックヒストグラムを含む既存のクエリは引き続き機能しますが、今後はユーザーはネイティブヒストグラムでの作業を開始し、ダッシュボード、アラート、記録ルールなどのクエリを変更し始めることができます。すでに上記で述べたように、histogram_quantile(0.9, rate(rpc_duration_seconds[1d]))のような長いレンジベクトルを持つクエリには注意を払うことが重要です。このクエリは、過去1日間の90パーセンタイルのレイテンシを計算します。しかし、ネイティブヒストグラムが少なくとも1日間収集されていない場合、クエリはその短い期間しかカバーしません。したがって、ネイティブヒストグラムが少なくとも1日間収集されてからのみ、このクエリを使用すべきです。過去1ヶ月間の毎日の90パーセンタイル遅延を表示するダッシュボードの場合、クラシックヒストグラムからネイティブヒストグラムへ適切なタイミングで正しく切り替わるクエリを作成したくなるでしょう。これは原則として可能ですが、トリッキーです。もし可能であれば、クラシックヒストグラムとネイティブヒストグラムが並行して収集される移行期間をかなり長くすることで、トリッキーな切り替えを実装する必要性を最小限に抑えることができます。例えば、クラシックヒストグラムとネイティブヒストグラムが1ヶ月間並行して収集された後、1ヶ月よりも過去を遡らないダッシュボードは、適切な切り替えを一切考慮することなく、クラシックヒストグラムのクエリからネイティブヒストグラムのクエリに単純に切り替えることができます。

すべてのクエリが正しく移行されたという確信が得られたら、Prometheusをネイティブヒストグラムのみをスクレイピングするように設定します(これが「通常の」設定です)。(スクレイプ設定でリラベルルールを使用して、クラシックヒストグラムを段階的に削除することも可能です。) すべてがまだ機能している場合、計装からクラシックヒストグラムを削除する時です。

Grafana Mimirのドキュメントには、このセクションで説明されているのと同じ哲学に従った詳細な移行ガイドが含まれています。

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