ネイティブヒストグラム [実験的]
ネイティブヒストグラムは、2022年11月に実験的な機能として導入されました。これはPrometheusスタックのほぼすべての部分に関わる概念です。ネイティブヒストグラムをサポートするPrometheusサーバーの最初のバージョンはv2.40.0でした。このサポートは、機能フラグ--enable-feature=native-histograms
を介して有効にする必要がありました。(TODO:現在のリリースv2.55およびv3.00でも同様です。安定版リリースがされたらこのセクションを更新します。)
ネイティブヒストグラムに関連する変更の広範な性質のため、これらの変更のドキュメントと基礎となる概念の説明は、様々なチャネル(影響を受けるPrometheusコンポーネントのドキュメント、ソースコード内のdocコメント、時にはソースコード自体、設計ドキュメント、カンファレンストークなど)に広く分散しています。このドキュメントは、これらの情報の断片をすべて収集し、統一された文脈で簡潔に提示することを目的としています。このドキュメントは、既存の詳細なドキュメントを再記述するよりも、リンクすることを優先しますが、他のソースを参照しなくても理解できる十分な情報を含んでいます。しかし、このドキュメントは初心者向けの導入としては適しておらず、開発者のニーズに焦点を当てているわけではないことに注意してください。前者については、ヒストグラムとサマリーに関するベストプラクティス記事の更新版を提供する予定です。(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`)でした。これらの浮動小数点値は、`gauges`または`counters`を直接表現できます。公開形式に存在するPrometheusメトリックタイプ`summary`と(クラシック版の)`histogram`は、取り込み時に浮動小数点コンポーネントに分解されます。両方のタイプに対して`sum`と`count`コンポーネント、summaryに対して複数の`quantile`サンプル、そして(クラシック)ヒストグラムに対して複数の`bucket`サンプルです。
ネイティブヒストグラムでは、新しい構造化されたサンプルタイプが導入されます。単一のサンプルは、以前から知られていた`sum`と`count`に加えて、動的なバケットのセットを表します。これは取り込みに限らず、PromQL式も、以前は浮動小数点サンプルしか返せなかった場所で、新しいサンプルタイプを返すことができます。
ネイティブヒストグラムには以下の主要な特性があります。
- スパースなバケット表現により、空のバケットのコストを(ほぼ)ゼロに抑えることができます。
- float64値の全範囲をカバーします。
- 計測中にバケット境界を設定する必要はありません。
- シンプルな設定パラメータに従って動的に解像度を選択します。
- 洗練された指数バケット化スキームにより、それらのスキームを使用するすべてのヒストグラム間でマージ可能性が保証されます。
- 公開およびストレージの両方において効率的なデータ表現。
これらの主要な特性は、標準的なバケット化スキーマで完全に実現されています。これらの特性の一部のみを特徴とする、異なるトレードオフを持つ他のスキーマも存在します。詳細については、以下のスキーマセクションを参照してください。
以前から存在していた「クラシック」ヒストグラムと比較して、ネイティブヒストグラム(標準バケット化スキーマを使用)は、低いストレージおよびクエリコストで、ほとんど設定なしに、観測値の任意の範囲にわたって高いバケット解像度を可能にします。ラベルによるヒストグラムのパーティション分割も、はるかに手頃になりました。
スパース表現(上記のリストの特性1)は、ネイティブヒストグラムの他の多くの利点にとって非常に重要であるため、設計プロセスの初期には、`スパースヒストグラム`が`ネイティブヒストグラム`の一般的な名称でした。しかし、指数バケット化スキーマやバケットの動的な性質といった他の主要な特性も非常に重要ですが、`スパースヒストグラム`という用語では全く捉えられていません。
設計ドキュメント
これらは、ネイティブヒストグラムの開発を導いた設計ドキュメントです。一部の詳細は現在廃止されていますが、根底にある概念とそれがどのように進化してきたかをかなりよく説明しています。
- Prometheus向けスパース高解像度ヒストグラム、オリジナルの設計ドキュメント。
- Prometheus スパースヒストグラムと PromQL、PromQLでのネイティブヒストグラムの扱いに関する適切な設計ドキュメントというよりも、探索的なドキュメント。
カンファレンストーク
ネイティブヒストグラムについて学ぶためのよりわかりやすい方法は、カンファレンストークを視聴することです。以下にその一部を紹介します。導入として、これらのトークを視聴し、その後このドキュメントに戻って詳細と技術的な側面について学ぶのが良いかもしれません。
- Prometheusヒストグラムの秘史は、クラシックヒストグラムとPrometheusがそれらを長期間保持してきた理由について説明しています。
- Prometheus Histograms – 過去、現在、未来は、ネイティブヒストグラムにつながる新しいアプローチに関する就任スピーチです。
- Prometheusのためのより良いヒストグラムは、この概念が実際にどのように機能するかを説明します。
- Prometheusにおけるネイティブヒストグラムは、実際の実装後にネイティブヒストグラムを提示および説明します。
- ネイティブヒストグラムのためのPromQLは、PromQLにおけるネイティブヒストグラムの使用方法を説明します。
- Prometheusにおけるネイティブヒストグラムの運用は、パフォーマンスとリソース消費に関する分析を提供します。
- OpenTelemetryの指数ヒストグラムをPrometheusで利用するは、OpenTelemetryとの相互運用性について説明します。
用語集
- ネイティブヒストグラムとは、このドキュメントで説明する完全なヒストグラムを表す新しい複合サンプルタイプのインスタンスです。文脈が十分に明確な場合は、以下では単に*ヒストグラム*と呼ばれることもあります。
- クラシックヒストグラムは、固定バケットを持つヒストグラムを表す古いサンプルタイプのインスタンスで、以前は単に*ヒストグラム*と呼ばれていました。公開形式ではそのように存在しますが、Prometheusへの取り込み時に複数の浮動小数点サンプルに分解されます。
- スパースヒストグラムは、*ネイティブヒストグラム*の古い名前で、現在では非推奨です。この名前は、古いドキュメントで時々見られるかもしれません。スパースバケットは、ネイティブヒストグラムのバケットに対する意味のある用語として残っています。
データモデル
このセクションでは、ネイティブヒストグラムのデータモデルについて一般的に説明します。実装の具体的な内容は可能な限り避け、専門用語も同様です。例えば、このセクションで記述されている*リスト*は、Protobufの実装では*repeated message*に、Goの実装では(おそらく)*slice*になります。
一般的な構造
従来のヒストグラムと同様に、ネイティブヒストグラムには観測値の*カウント*と観測値の*合計*のフィールドがあります。さらに、以下のコンポーネントが含まれており、これらは以下の専用セクションで詳しく説明されています。
- 任意のバケットの境界をインデックス*i*で決定する方法を識別する*スキーマ*。
- 正と負の観測値に対してミラーリングされた、インデックス付きバケットのスパース表現。
- ゼロに近い観測値をカウントするための*ゼロバケット*。
- (空の可能性のある)*カスタム値*のリスト。
- Exemplars.
フレーバー
任意のネイティブヒストグラムは、2つの独立した次元それぞれに特定のフレーバーを持っています。
- カウンター対ゲージ:通常、ヒストグラムは「カウンタのような」もので、つまり、そのバケットのそれぞれが観測値のカウンタとして機能します。しかし、各バケットがゲージとして機能し、ある時点での任意の分布を表す「ゲージのような」ヒストグラムも存在します。ゲージヒストグラムの概念は、以前にOpenMetricsによって従来のヒストグラムに対して導入されました。
- 整数 vs 浮動小数点 (短縮形: float): ヒストグラムの明らかな用途は観測値を数えることであり、これにより各バケット(ゼロバケットを含む)内の観測値は0以上の整数となり、観測値の合計カウントは符号なし64ビット整数(短縮形: uint64)で表されます。しかし、これらの値がすべて64ビット浮動小数点数(短縮形: float64)で表される「重み付き」または「スケールされた」ヒストグラムにつながる特定のユースケースも存在します。観測値の*合計*はどちらの場合もfloat64であることに注意してください。
フロートヒストグラムは、「重み付き」観測値の直接的な計測で時折使用されます。例えば、観測値がヒストグラムの異なるバケットに落ちた秒数を数える場合などです。しかし、フロートヒストグラムのより一般的な使用例はPromQL内です。PromQLは一般的にフロート値に対してのみ動作するため、PromQLエンジンはTSDBから取得したすべてのヒストグラムをまずフロートヒストグラムに変換し、記録ルールを介してTSDBに格納されるヒストグラムはすべてフロートヒストグラムです。このようなヒストグラムが実質的に整数ヒストグラムである場合(すべての非`sum`フィールドの値がuint64として正確に表現できるため)、TSDB実装はストレージ効率を高めるためにそれらを整数ヒストグラムに変換してもよいです。(Prometheus v3.00の時点では、Prometheus内のTSDB実装はこのオプションを利用していません。)ただし、カウンターヒストグラムに適用される最も一般的なPromQL関数は`rate`であり、これは一般的に非整数値を生成するため、記録ルールの結果は通常、非整数値のフロートヒストグラムになることに注意してください。
ネイティブヒストグラムを明示的に整数ヒストグラムまたは浮動小数点ヒストグラムとして扱うことは、従来の単純な数値サンプル(簡潔さのためにスタック全体で常に浮動小数点数として扱われる)の扱いとは異なる点です。
ヒストグラムの扱いがより複雑になる主な理由は、Protobufベースの公開形式における容易な効率向上です。Protobufは整数にvarintエンコーディングを使用するため、追加の圧縮層を必要とせずに、小さな整数値のデータサイズを削減します。この利点は、一般的に小さい整数値になる整数バケットのデルタエンコーディングによって増幅されます。一方、浮動小数点数はProtobufでは常に8バイトを必要とします。実際には、整数ヒストグラムの多くの整数は1バイトに収まり、ほとんどは2バイトに収まるため、Protobuf公開形式に整数ヒストグラムが明示的に存在することで、多くのバケットを持つヒストグラムではデータサイズが8倍近く削減されます。これは、計測対象が公開するヒストグラムの圧倒的多数が整数ヒストグラムであるため、特に重要です。
同様の理由から、整数ヒストグラムのRAMとディスクでの表現は、一般的に浮動小数点ヒストグラムよりも効率的です。ただし、これは公開形式での利点ほど重要ではありません。Prometheusは浮動小数点数にGorillaスタイルのXORエンコーディングを使用しており、これによりサイズが削減されますが、整数に使用されるdouble-deltaエンコーディングほどではありません。さらに重要なことに、実装は、実質的に整数値であるヒストグラムフィールドに対して常に内部的に整数表現を使用することを決定できます(上記参照)。(歴史的注意: Prometheus v1では、浮動小数点サンプルの圧縮を改善するためにまさにこのアプローチが使用されており、Prometheus v3では将来的にこのアプローチを再び採用する可能性があります。)
カウンターヒストグラムでは、観測値の合計*カウント*とバケット内の個別のカウントは、Prometheusのカウンターのように動作します。つまり、カウンターのリセット時にのみ減少します。ただし、負の値の観測の結果として、観測値の*合計*は減少する可能性があります。PromQLの実装は、ヒストグラム全体に基づいてカウンターのリセットを検出する必要があります(詳細については、以下のカウンターリセットの考慮事項セクションを参照してください)。(これは、従来のヒストグラムおよびサマリーの*合計*コンポーネントにとっても常に問題でした。これまでのアプローチでは、これらのケースで*合計*に対するカウンターリセット検出が静かに機能しなくなることを許容していました。幸いなことに、負の観測値はPrometheusのヒストグラムおよびサマリーでは非常にまれなユースケースです。)
スキーマ
`スキーマ`は、8ビットサイズの符号付き整数値(略して`int8`)です。これは、特定のバケット(インデックス`i`を持つ)の境界を決定する方法を定義します。現在有効な値は、-53、および-4から+8までの範囲(両端を含む)です。将来的には、より多くのスキーマが追加される可能性があります。-53は、いわゆる*カスタムバケット境界*または略して*カスタムバケット*のスキーマであり、その他のスキーマ番号は、異なる標準的な指数スキーマ(略して*標準スキーマ*)を表します。
標準スキーマは相互にマージ可能であり、一般的な用途に推奨されます。スキーマ番号が大きいほど、解像度が高くなります。スキーマ*n*はスキーマ*n*+1の半分の解像度を持つため、スキーマ*n*+1のヒストグラムは、隣接するバケットをマージすることでスキーマ*n*のヒストグラムに変換できます。
任意の標準スキーマ*n*について、インデックス*i*を持つバケットの境界は、以下のように計算されます(Python構文を使用)。
- 正のバケットの包含上限:` (2**2**-n)**i `
- 正のバケットの排他下限:` (2**2**-n)**(i-1) `
- 負のバケットの包含下限:` -((2**2**-n)**i) `
- 負のバケットの排他上限:` -((2**2**-n)**(i-1)) `
*i*は負の値を取り得る整数です。
float64で表現可能な最大および最小の有限値(以下では`MaxFloat64`および`MinFloat64`と呼ぶ)および正負の無限大値(`+Inf`および`-Inf`)に関する上記のルールには例外があります。
- (上記の境界式に従って)`MaxFloat64`を含む正のバケットは、上限値が`MaxFloat64`となります(上記式で計算される、float64をオーバーフローさせるはずの制限値ではありません)。
- 次の正のバケット(前の項のバケットに対するインデックス*i*+1)は、排他的な下限値が`MaxFloat64`、包括的な上限値が`+Inf`となります。(これは*正のオーバーフローバケット*と呼べるかもしれません。)
- (上記の境界式に従って)`MinFloat64`を含む負のバケットは、包含下限値が`MinFloat64`となります(上記式で計算される、float64をアンダーフローさせるはずの制限値ではありません)。
- 次の負のバケット(前の項目からのバケットに対するインデックス*i*+1)は、排他的な上限値が`MinFloat64`、包含的な下限値が`-Inf`となります。(これは*負のオーバーフローバケット*と呼べるかもしれません。)
- 上記の`+Inf`および`-Inf`バケットを超えるバケットは使用してはなりません。
ゼロに近い値についてはさらに例外があり、詳細は以下のゼロバケットセクションを参照してください。
現在の最低解像度の-4と最高解像度の8の制限は、実用的な有用性に基づいて選択されました。さらに低い、または高い解像度の実用的な必要性が生じた場合、範囲の拡張が検討されます。ただし、スキーマが52より大きい場合、あるバケットから次のバケットへの成長率が表現可能なfloat64数値間の差よりも小さくなるため、意味がありません。同様に、スキーマが-9より小さい場合も、成長率がfloat64で表現可能な最大の浮動小数点数を超えるため、意味がありません。したがって、(-9と+52を含む)間のスキーマ番号は、将来の標準スキーマ(上記のバケット境界の公式に従う)のために予約されており、他のスキーマには使用してはなりません。
スキーマ-53の場合、バケット境界は*カスタム値*によって明示的に設定され、詳細は以下のカスタム値のセクションで説明されています。これにより、カスタムバケット境界を持つネイティブヒストグラム(略して*カスタムバケット*、さらに略してNHCB)が生成されます。このようなヒストグラムは、クラシックヒストグラムをネイティブヒストグラムとして表現するために使用できます。また、標準スキーマが特徴とする指数関数的なバケット化が、ヒストグラムで表現される分布に合わない場合にも使用できます。異なるカスタムバケット境界を持つヒストグラムは、一般的に相互にマージできません。したがって、スキーマ-53は、特定のユースケースにおいて情報に基づいた決定としてのみ使用すべきです。
バケット
標準スキーマの場合、バケットは2つのリストとして表現され、1つは正のバケット用、もう1つは負のバケット用です。カスタムバケット(スキーマ-53)の場合、正のバケットリストのみが使用されますが、すべてのバケットのために再利用されます。
未入力のバケットはリストから除外してもよいです。(これが、バケットが*スパースバケット*と呼ばれることが多い理由です。)
浮動小数点ヒストグラムの場合、リストの要素は`float64`であり、バケットの集団を直接表します。
整数ヒストグラムの場合、リストの要素は符号付き64ビット整数(短縮形: int64)であり、各要素はリスト内の前のバケットに対する差分としてバケットの数を表します。各リストの最初のバケットには絶対的な数が含まれます(これはゼロに対する差分と見なすこともできます)。
リスト内のバケットを前述のセクションで定義されたインデックスにマッピングするために、いわゆる*スパン*のリストが2つあります。1つは正のバケット用、もう1つは負のバケット用です。
各スパンは、符号付き32ビット整数(略して`int32`)で`offset`と呼ばれる数値と、符号なし32ビット整数(略して`uint32`)で`length`と呼ばれる数値のペアで構成されます。各リストの最初のスパンのみが負の`offset`を持つことができます。それは対応するバケットリストの最初のバケットのインデックスを定義します。(NHCBの場合、インデックスは常に正であることに注意してください。詳細は、以下のカスタム値のセクションを参照してください。)`length`は、バケットリストが始まる連続するバケットの数を定義します。後続のスパンの`offset`は、除外された(したがって未入力の)バケットの数を定義します。`length`は、除外されたバケットに続くリスト内の連続するバケットの数を定義します。
各スパンリストのすべての`length`値の合計は、対応するバケットリストの長さと等しくなければなりません。
空のスパン(長さがゼロ)は有効であり、使用しても構いませんが、通常は有用ではなく、後続のスパンのオフセットにオフセットを追加することで排除すべきです。同様に、リスト内の最初のスパンではないスパンは、オフセットがゼロであっても構いませんが、それらのオフセットは前のスパンに長さを追加することで排除すべきです。これらの両方のケースが許可されているのは、ネイティブヒストグラムの生成者がその時点で最もリソース効率の良い表現を選択できるようにするためです。たとえば、ヒストグラムが様々なステージを経て処理される場合、最後の処理ステージ後にのみ冗長なスパンを排除するのが最も効率的である場合があります。
同様の精神で、バケットリストから未入力のバケットをすべて除外するのが最も効率的な場合もあれば、少数の未入力のバケットを明示的に表現することでスパンの数を減らす方が良い場合もあります。
将来の高解像度スキーマでは、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(浮動小数点ヒストグラムの場合)によって追跡されます。
ゼロバケットには、`zero threshold`と呼ばれる追加のパラメータがあり、これは`float64 >= 0`です。閾値がゼロに設定されている場合、厳密にゼロの観測値のみがゼロバケットに入り、これは上記のケースです。閾値が正の値を持つ場合、閉区間`[-threshold, +threshold]`内のすべての観測値は、通常のバケットではなくゼロバケットに入ります。これには2つの使用例があります。
- ゼロに近いノイズの多い観測値は、多数のバケットを埋める傾向があります。これらの観測値は、数値の不正確さや、観測値のソースが実際の物理測定である場合に発生する可能性があります。比較的低い閾値のゼロバケットは、これらの観測値を単一のバケットにリダイレクトします。
- ユーザーがゼロから遠く離れた分布の裾野に興味がある場合、ゼロバケットの比較的大きな閾値は、関心のない範囲で多くの高解像度バケットを避けるのに役立ちます。
ゼロバケットの閾値は、通常のバケットの境界と一致すべきであり、これによりゼロバケットが通常のバケットの一部と重なるという複雑さを回避します。ただし、そのような重なりが発生した場合、ゼロバケットと重なる通常のバケットでカウントされる観測値は、`[-threshold, +threshold]`区間の外でなければなりません。
ゼロしきい値が同じヒストグラムをマージする場合、2つのゼロバケットは単純に加算されます。ただし、ソースヒストグラムのゼロしきい値が異なる場合は、いずれかのソースヒストグラムの最大しきい値が選択されます。そのしきい値が他のソースヒストグラム内のいずれかのデータが入力されたバケット内にある場合は、各ソースヒストグラムについて次のいずれかが真になるまで、しきい値が増加します。
- 新しいしきい値が、データが入力されたバケットの境界と一致する。
- 新しいしきい値が、データが入力されたバケット内にない。
次に、ソースのゼロバケットと、新しいしきい値内にあるソースバケットを加算して、新しいゼロバケットのデータ量を算出します。
スキーマが -53(カスタムバケット)の場合、ゼロバケットは使用されません。
カスタム値
カスタム値のリストは、標準スキーマでは使用されません。追加データを格納する必要がある場合、非標準スキーマではカスタム方法で使用されます。
現在、カスタム値が使用される唯一の定義済みスキーマは -53(カスタムバケット)です。このセクションの残りの部分では、この特定のケースにおけるカスタム値の使用方法を詳細に説明します。
カスタム値は、カスタムバケットの上限値(包括的)を表します。これらは昇順にソートされています。カスタムバケット自体は、正のバケットリストと正のスパンリストを使用して格納されますが、カスタム値によって決定されるその境界は負の値になることがあります。これらの「正の」バケットのそれぞれのインデックスは、カスタム値リスト内でのその上限値の0ベースの位置を定義します。
下限値(排他的)は、上限値に先行するカスタム値によって定義されます。最初のカスタム値(リストの0番目の位置)には先行する値がないため、下限値は-Inf
とみなされます。したがって、インデックス0のカスタムバケットは、-Inf
から最初のカスタム値までのすべての観測値をカウントします。正の観測値のみが予想される一般的なケースでは、インデックス0のカスタムバケットは、ゼロ以下の観測値があった場合に明確にマークするために、上限値がゼロであるべきです。(実際に正の観測値のみがある場合、インデックス0のカスタムバケットは空のままであり、明示的に表現されることはありません。唯一のコストは、カスタム値リストの先頭に追加されるゼロ要素です。)
最後のカスタム値は+Inf
であってはなりません。最後のカスタム値より大きい観測値は、上限値が+Inf
のオーバーフローバケットに入ります。このオーバーフローバケットは、カスタム値リストの長さと等しいインデックスで追加されます。
Exemplars
ネイティブヒストグラムのサンプルは、0個、1個、またはそれ以上のExemplarを持つことができます。Exemplarは従来のExemplarと同じように機能しますが、リスト(複数存在するため)で編成され、タイムスタンプを必須とします。
クラシックヒストグラムの一部として公開されるExemplarは、タイムスタンプがあれば、ネイティブヒストグラムで使用できます。
観測値の特殊なケース
ヒストグラムの文脈ではあまり意味がないため、計測コードはNaN
と±Inf
の値を観測することを避けるべきです。ただし、これらの値は以下に説明するように適切に処理される必要があります。
観測値の合計は、通常の浮動小数点演算に従って、観測値を観測値の合計に加算することによって通常通り計算されます。(例: NaN
の観測値は合計をNaN
に設定します。+Inf
の観測値は、合計がすでにNaN
または-Inf
でない限り、合計を+Inf
に設定します。この場合、合計はNaN
に設定されます。)
NaN
の観測値はどのバケットにも入りませんが、観測のカウントを増やします。これは、観測のカウントがすべてのバケット(負、正、ゼロバケット)の合計よりも大きく、その差がNaN
観測の数であることを意味します。(NaN
観測がない整数ヒストグラムの場合、すべてのバケットの合計は観測のカウントと等しくなります。通常の浮動小数点精度限界内では、NaN
観測がない浮動小数点ヒストグラムについても同じことが言えます。)
+Inf
または-Inf
の観測は、観測数のカウントを増やし、以下の方法で選択されたバケットを増やします。
- 標準スキーマの場合、
+Inf
観測は上記の**正のオーバーフローバケット**をインクリメントします。 - 標準スキーマの場合、
-Inf
観測は上記の**負のオーバーフローバケット**をインクリメントします。 - スキーマ-53(カスタムバケット)の場合、
+Inf
観測は、カスタム値リストの長さと等しいインデックスを持つバケットをインクリメントします。 - スキーマ-53(カスタムバケット)の場合、
-Inf
観測はインデックス0のバケットをインクリメントします。
OpenTelemetryとの相互運用性
標準スキーマを持つPrometheus (Prom) ネイティブヒストグラムは、OpenTelemetry (OTel) 指数ヒストグラムに簡単にマッピングでき、その逆も可能です。詳細は以下に詳述します。
Promの*スキーマ*はOTelの*スケール*と等しいですが、OTelは-4未満および+8より大きい値を許可するという制約があります。上記で説明したように、Promは実際に必要になった場合に備えて、その範囲を拡張するためにさらに多くのスキーマ番号を予約しています。
インデックスは1だけオフセットされます。つまり、Promのインデックス*n*のバケットは、OTelではインデックス*n-1*になります。
OTelは、バケットをスパースではなく密に表現します。OTelを「単一スパンのProm」と見なすこともできます。
Promの*ゼロバケット*は、OTelでは*ゼロカウント*と呼ばれます。(Promも*ゼロカウント*を、ゼロバケットの観測値のカウントを格納するフィールド名として使用します)。どちらも*ゼロしきい値*の存在を含め、同じように機能します。OTelは、指定がない場合はしきい値がゼロであることを示唆していることに注意してください。
(TODO: OTelの仕様には、「zero_thresholdが設定されていないか0の場合、このバケットは標準の指数関数式で表現できない値、およびゼロに丸められた値を格納します」とあります。これが本当に同じ動作を生み出すか二重に確認してください。ゼロに近いところで問題がある場合、Promの仕様をより正確にすることができます。OTelがNaNをゼロバケットにカウントする場合、ここに注意を追加する必要があります。)
OTelの指数ヒストグラムは、標準的な指数バケッティングスキーマのみをサポートしています(名前が示す通りです)。したがって、NHCB(または将来の他のバケッティングスキーマを持つネイティブヒストグラム)は、OTelの指数ヒストグラムにきれいに変換することはできません。ただし、固定バケットを持つ従来のOTelヒストグラムへの変換は依然として可能です。
OTelのあらゆる種類のヒストグラムには、ヒストグラム内で観測された最小値と最大値のオプションフィールドがあります。これらのフィールドはPrometheusには同等の概念がありません。Prometheusのカウンタヒストグラムは、長期間かつ予測不可能な期間にわたってデータを蓄積し、いつでもスクレイピングできるため、最小値と最大値を追跡することは実現不可能であるか、利用価値が限られているためです。ただし、ネイティブヒストグラムは、任意の期間における最大および最小観測値をかなり正確に推定できることに注意してください。詳細はPromQLセクションを参照してください。
公開フォーマット
従来のPrometheusのユースケースにおけるメトリクス公開は、文字列が中心でした。すべてのメトリクス名、ラベル名、ラベル値が、float64のサンプル値よりもはるかに多くのスペースを占めていたからです。後者がより冗長なテキスト形式で表現されたとしてもです。これが、かつてprotobufベースの公開を放棄することが有利だと考えられた理由の1つです。
対照的に、上記のデータモデルに従うネイティブヒストグラムは、はるかに多くの数値データで構成されています。これにより、protobufベースのフォーマットの利点が拡大されます。したがって、以前に放棄されたprotobufベースの公開が、ネイティブヒストグラムを効率的に公開およびスクレイピングするために復活しました。
従来のPrometheusフォーマット
ネイティブヒストグラムが考案された当時、OpenMetricsの採用はまだ不十分であり、特にOpenMetricsのprotobufバージョンには既知のアプリケーションが全くありませんでした。したがって、初期のアプローチは、ネイティブヒストグラムをサポートするために従来のPrometheus protobufフォーマットを拡張することでした。(追加の実用的な考慮事項として、Go計測ライブラリが内部データモデルとして従来のprotobuf仕様をまだ使用しており、初期開発を簡素化したことが挙げられます。)
従来のPrometheusテキスト形式はネイティブヒストグラムのために拡張されておらず、そのような拡張は計画されていません。(下記のOpenMetricsセクションも参照してください。)
protobuf仕様にはproto2とproto3のバージョンがあり、どちらも同じワイヤフォーマットを生成します。
これらのファイルには包括的なコメントが含まれており、プロト仕様から上記のデータモデルへの簡単なマッピングを可能にするはずです。
以下は、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_float
、cumulative_count_float
を参照)。 Bucket
フィールドとBucket
メッセージは、従来のヒストグラムのバケットに使用されます。同じヒストグラムの従来のバージョンとネイティブバージョンの両方を表すHistogram
メッセージを作成することは完全に可能です。パーサーはどちらか一方または両方のバージョンを選択する自由があります(スクレイピング設定セクションも参照)。- バケットのデータ量は、浮動小数点ヒストグラムの場合は絶対値としてエンコードされ、整数ヒストグラムの場合は前のバケットに対する差分(最初のバケットの場合はゼロに対する差分)としてエンコードされます。後者の方が数字が小さくなるため、protobufが
sint64
型にvarintエンコーディングを使用するため、メッセージサイズが小さくなります。 - まだ観測値がないネイティブヒストグラムと、バケットが設定されていないクラシックヒストグラムは、protobufメッセージとしてはまったく同じに見えます。したがって、ネイティブヒストグラムとして解析されることを意図した
Histogram
メッセージは、繰り返しのpositive_span
フィールドに「no-op span」、つまりoffset
とlength
が0に設定されたBucketSpan
を含める必要があります。 - ネイティブヒストグラムのExemplarは、
Histogram
メッセージの繰り返しExemplar
フィールドにいくつでも追加できますが、それぞれにタイムスタンプが必須です。この方法でExemplarが提供されない場合、パーサーはクラシックバケット用に提供されたタイムスタンプ付きExemplar(Bucket
メッセージのExemplar
フィールド内のバケットごとに最大1つのExemplarとして)を使用することができます。 - ネイティブヒストグラムのExemplarの数と分布は、手元のユースケースに適合している必要があります。一般的に、Exemplarのペイロードは
Histogram
メッセージの残りの部分よりも大幅に大きくすべきではなく、Exemplarは異なるバケットに属し、バケット全体にほぼ均等に分布している必要があります。(これは、観測値の分布に比例してExemplarを表現するよりも一般的に推奨されます。後者の場合、分布のロングテールからExemplarがほとんど得られず、それらが最も興味深いExemplarであることが多いためです。)
OpenMetrics
現在(2024年11月3日)、OpenMetricsはネイティブヒストグラムをサポートしていません。
OpenMetricsのprotobufバージョンへのサポート追加は、従来のPrometheus protobufフォーマットとの類似性があるため、比較的簡単です。PR形式の提案がレビュー中です。
OpenMetricsのテキストバージョンへのサポート追加はより困難ですが、protobufの生成が実行不可能な状況が多いため、非常に望ましいものでもあります。テキスト形式は、人間による読みやすさと、機械による効率的な処理(エンコーディング、転送、デコーディング)のバランスを取る必要があります。作業は進行中です。詳細については、設計ドキュメントを参照してください。
(TODO: 進捗に合わせてセクションを更新。)
計測ライブラリ
protobuf仕様は、protobufコンパイラによって作成された言語固有のバインディングを使用して、ネイティブヒストグラムを含むメトリクス公開の低レベルな作成を可能にします。しかし、直接的なコード計測には、計測ライブラリが必要です。
現在(2024年11月3日)、ネイティブヒストグラムをサポートするPrometheusの公式計測ライブラリは2つあります。
他の計測ライブラリにネイティブヒストグラムのサポートを追加することは、ライブラリがすでにprotobuf公開をサポートしていれば比較的簡単です。純粋なテキストベースのライブラリの場合、テキストベースの公開フォーマットの完成が前提条件となります。(TODO: 必要に応じてこれを更新する。)
このセクションでは、個々の計測ライブラリの使用方法の詳細については触れません(それについては上記のドキュメントを参照してください)が、一般的な使用パターンに焦点を当て、計測ライブラリの一部としてネイティブヒストグラムのサポートを実装するための一般的なガイドラインも提供します。既に存在するGo実装が例として使用されます。データモデルと公開形式に関するセクションは、計測ライブラリの実装に非常に重要です(ただし、このセクションでは繰り返しません)。
ヒストグラムの実際の計測APIは、ネイティブヒストグラムのために変更されません。クラシックヒストグラムとネイティブヒストグラムの両方は、同じ方法で観測値を受け取ります(Exemplarに関する微妙な違いは次の段落を参照)。計測ライブラリは、同じヒストグラムのクラシックバージョンとネイティブバージョンを両方維持し、並行して公開することもできます。これにより、スクレイパーは取り込むバージョンを選択できます(詳細は公開フォーマットのセクションを参照)。ユーザーは、設定によってクラシックヒストグラムと/またはネイティブヒストグラムを公開するかどうかを選択します。
従来のヒストグラムのExemplarは通常、各バケットの最新のExemplarを保存および公開することによって追跡されます。従来のバケットが定義されている限り、各Exemplarにタイムスタンプがある場合、計測ライブラリは同じヒストグラムのネイティブバージョンにも同じExemplarを公開できます。(実際、スクレイパーは、ヒストグラムの従来のバージョンに提供されたExemplarを、ネイティブバージョンのみを取り込んでいる場合でも使用できます。公開フォーマットセクションの詳細を参照してください。) しかし、ネイティブヒストグラムには任意の数のExemplarを割り当てることができ、計測ライブラリは、公開フォーマットセクションで説明されているExemplarのベストプラクティスを満たすためにこの自由を利用するべきです。
計測ライブラリは、標準スキーマに従うネイティブヒストグラムに対して、以下の設定パラメータを提供するべきです。名前はGoライブラリからの例です。他の言語では、その言語の慣用的なスタイルに合わせて調整する必要があります。括弧内の値は、ライブラリが提供すべきデフォルト値です。
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攻撃ベクトルとしても使用される可能性があります。したがって、計測ライブラリはバケット制限戦略を提供するべきです。ライブラリが使用される典型的なユースケースに応じて、デフォルトで設定することもできます。(TODO: デフォルトで戦略が設定されるべきだと言うべきかもしれません。Goライブラリは現在、デフォルトでバケットを制限していませんし、これまでのところ問題は報告されていません。)
以下に、Go計測ライブラリによって実装されたバケット制限戦略を説明します。他のライブラリもこの例に従うことができますが、ライブラリの一般的な使用パターンによっては、他の戦略も考えられます。
この戦略は3つのパラメータで定義されます:符号なし整数NativeHistogramMaxBucketNumber
、期間NativeHistogramMinResetDuration
、および浮動小数点数NativeHistogramMaxZeroThreshold
。NativeHistogramMaxBucketNumber
がゼロ(デフォルト)の場合、バケットはまったく制限されず、他の2つのパラメータは無視されます。NativeHistogramMaxBucketNumber
が正の値に設定されている場合、ライブラリは各ヒストグラムのバケット数を指定された値に維持しようとします。制限の典型的な値は160であり、これはOTel指数ヒストグラムが同様の戦略で使用するデフォルト値でもあります。(ラベルによるパーティション分割は多数のヒストグラムを作成することに注意してください。制限はそれぞれ個別に適用され、集計されたすべてには適用されません。)制限を超過した場合、バケット数が再び制限内に収まるまで、いくつかの対処法が順番に適用されます。
- ヒストグラムの最後のリセット(ヒストグラムの作成を含む)から少なくとも
NativeHistogramMinResetDuration
が経過した場合、ヒストグラム全体がリセットされます。つまり、すべてのバケットが削除され、観測値の合計とカウント、およびゼロバケットがゼロに設定されます。Prometheusはこれを通常のカウンタリセットとして処理します。これは、スクレイピング間で一部の観測値が失われることを意味するため、リセットはスクレイピング間隔と比較して稀に行われるべきです。さらに、頻繁なカウンタリセットは、TSDBでのストレージ効率の低下につながる可能性があります(詳細はTSDBセクションを参照)。NativeHistogramMinResetDuration
を1時間に設定すると、ほとんどの状況でうまく機能するはずです。 - 最後のリセットから十分な時間が経過していない場合(または
NativeHistogramMinResetDuration
がゼロに設定されている場合、これはデフォルト値です)、リセットは実行されません。代わりに、ゼロ閾値が増加され、ゼロに近いバケットがゼロバケットにマージされ、その方法でバケット数が削減されます。閾値の増加はNativeHistogramMaxZeroThreshold
によって制限されます。この値が既に到達している場合(またはゼロに設定されている場合、これはデフォルトです)、このステップでは何も起こりません。 - バケット数がまだ制限を超えている場合、ヒストグラムの解像度は、次の低いスキーマに変換することによって、すなわち隣接するバケットをマージすることによって、バケット幅を2倍にして削減されます。これは、バケット数が設定された制限内になるか、スキーマ-4に達するまで繰り返されます。
ステップ2または3でヒストグラムが変更された場合、最後のリセットからNativeHistogramMinResetDuration
が経過すると、バケットを削除するだけでなく、ゼロ閾値とバケット解像度の初期値に戻すためにリセットが実行されます。これは、いわゆる作成タイムスタンプの更新を含め、他の理由によるリセットと同じように扱われることに注意してください。
非常に低いNativeHistogramBucketFactor
(例:1.005)と適切なNativeHistogramMaxBucketNumber
(例:160)を組み合わせて設定したくなります。こうすることで、各ヒストグラムは常に、指定されたバケット数の「予算」内で可能な限り最高の解像度を持ちます。(これはOTel指数ヒストグラムで使用されるデフォルトの戦略であり、Prometheusのネイティブヒストグラムでは現在利用できないさらに高いスキーマ(20)から開始します。) しかし、この戦略は一般的にPrometheusのユースケースには*推奨されません*。観測値が入力されると、作成後および各リセット後に解像度がかなり頻繁に削減されます。これにより、計測されたプログラムとTSDBの両方でチャーンが発生し、特に後者では問題になります。ヒストグラムを含む一般的なクエリでは、多くのヒストグラムをマージする必要があり、その際に最も低い共通の解像度が使用されるため、ユーザーは結局低い解像度になります。TSDBは、取り込み時に解像度を制限することでチャーンから保護できますが(以下を参照)、合理的に低い解像度が取り込み時に強制されるのであれば、計測時にこの解像度を設定する方が簡単です。ただし、計測時に合理的な解像度を想定できない特定のケースでは、スクレイパーがスクレイピング時に希望の解像度を選択する柔軟性を持つ必要があるため、この戦略は計測されたプログラム内のリソースオーバーヘッドに見合う価値があるかもしれません。
ラベルによるパーティショニング
多数のバケットを持つ従来のヒストグラムのラベルによるパーティション分割は慎重に行う必要がありますが、ネイティブヒストグラムでは状況はより緩和されます。ネイティブヒストグラムのパーティション分割は、依然として多数の個々のヒストグラムを作成します。しかし、結果として得られるパーティション化されたヒストグラムは、元のパーティション化されていないヒストグラムよりもそれぞれ少ないバケットを埋めることが多いでしょう。(たとえば、HTTPリクエストの期間を追跡するヒストグラムをHTTPステータスコードでパーティション分割する場合、ステータスコード404で応答されたリクエストを追跡する個々のヒストグラムは、不明なパスを特定するのにかかる典型的な期間の周りに非常に急峻なバケット分布を持ち、少数のバケットしか埋めないかもしれません。)すべてのパーティション化されたヒストグラムの合計バケット数は依然として増加しますが、パーティション化されたヒストグラムの数よりも小さい係数で増加します。(たとえば、すでに非常に重い従来のヒストグラムにラベルを追加すると、100個のラベル付きヒストグラムになり、総コストは100倍になります。ネイティブヒストグラムの場合、従来のヒストグラムが高解像度である場合、単一のヒストグラムのコストはすでに低いかもしれません。パーティション分割後、ラベル付きネイティブヒストグラムの埋められたバケットの合計数は、元のネイティブヒストグラムのバケット数の100倍よりも大幅に小さくなります。)
NHCB
現在(2024年11月3日)、計測ライブラリは、カスタムバケット境界を持つネイティブヒストグラム(NHCBs)を直接設定する方法を提供していません。NHCBsのユースケースは、ネイティブヒストグラム対応のスクレイパーが、取り込み時に従来のヒストグラムをNHCBsに変換できるようにすることです(次のセクションを参照)。ただし、計測中にカスタムバケットが直接望ましい正当なユースケースがあります。そのような場合、現在の方法は、従来のヒストグラムで計測し、取り込み時にNHCBに変換するようにスクレイパーを設定することです。しかし、将来的には計測ライブラリでNHCBsをより直接的に扱うことが可能になるかもしれません。
スクレイピング設定
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
機能フラグのドキュメントを参照してください。
バケット数と解像度の制限
計測ライブラリは、ネイティブヒストグラムの解像度とバケット数を制限する設定オプションを提供するべきですが、取り込み時にこれらの制限を強制する必要は依然としてあります。ユーザーは、特定のプログラムの計測を変更できない場合や、異なるスクレイパーが都合の良いように解像度を減らせるように、プログラムが高解像度ヒストグラムで意図的に計測されている場合があります。
Prometheusのスクレイプ設定には、このニーズに対応するための2つの設定があります。
native_histogram_bucket_limit
は、個々のヒストグラムのバケット数の上限値(包括的)を設定します。この制限を超えた場合、標準スキーマのヒストグラムの解像度は、制限に達するまで繰り返し削減されます(バケットの幅を2倍にする、つまりスキーマを減らすことによって)。NHCBが制限を超過した場合、またはスキーマ-4でも制限を満たせない稀なケースでは、スクレイピングは失敗します。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
を設定することで、広い分布を持つ少数のヒストグラムに非常に多くのバケット数を受け入れるのに十分なリソースを解放できるかもしれません。もう1つの例は、何らかの理由で一部のヒストグラムの解像度が低い場合(おそらく計測側ですでに低い場合)です。これらの低解像度ヒストグラムが定期的に集計に含まれる場合、結果も同じ低解像度になります(詳細はPromQLの詳細を参照)。低解像度ヒストグラムと定期的に集計される他のヒストグラムをより高い解像度で保存しても、あまり意味がないかもしれません。
クラシックヒストグラムとネイティブヒストグラムの両方のスクレイピング
上記で説明したように、計測されたプログラムによって公開されるヒストグラムには、クラシックヒストグラムとネイティブヒストグラムの両方が含まれる場合があり、一部の要素(観測のカウントや合計など)は共有されます。このセクションでは、Prometheusによってどの要素がスクレイピングされ、その動作をどのように制御するかを説明します。
--enable-feature=native-histograms
フラグがない場合、Prometheusはスクレイピング中にネイティブヒストグラムの部分を完全に無視します。(TODO: この機能フラグがno-opになったら更新する。) このフラグが設定されている場合、Prometheusは、同じヒストグラムに対して両方が公開されていても、クラシックヒストグラムの部分よりもネイティブヒストグラムの部分を優先します。Prometheusは、ネイティブヒストグラムデータがないヒストグラムの場合でも、クラシックヒストグラムの部分をスクレイピングします。
移行シナリオのような状況では、計測されたプログラムによって両方のバージョンが公開されている場合、同じヒストグラムに対してクラシックバージョンとネイティブバージョンの両方をスクレイピングすることが望ましい場合があります。この動作を有効にするために、スクレイプ設定にブール値設定always_scrape_classic_histograms
があります。これはデフォルトでfalseですが、trueに設定すると、少なくとも1つのクラシックバケットと少なくとも1つのネイティブバケットスパン(これはno-opスパンである可能性があります)がある場合、各ヒストグラムの両方のバージョンがスクレイピングされて取り込まれます。これにより、TSDBで競合が発生することはありません。なぜなら、クラシックヒストグラムは接尾辞付きの複数のシリーズとして取り込まれるのに対し、ネイティブヒストグラムは修正されていない名前を持つ単一のシリーズとして取り込まれるからです。(例:rpc_latency_seconds
というヒストグラムは、rpc_latency_seconds
という名前のネイティブヒストグラムシリーズと、クラシック部分の複数のシリーズ、つまりrpc_latency_seconds_sum
、rpc_latency_seconds_count
、および異なるle
ラベルを持つ複数のrpc_latency_seconds_bucket
シリーズをもたらします。)
クラシックヒストグラムをNHCBとしてスクレイピングする
前述のNHCBは、従来のヒストグラムをネイティブヒストグラムとしてモデル化することができます。ブール型のスクレイプ設定オプションconvert_classic_histograms_to_nhcb
を介して、Prometheusは従来のヒストグラムをNHCBとして取り込むように設定できます。
NHCBは従来のヒストグラムと同様にマージ可能性に制限がありますが、一般的に格納コストははるかに低いです。
TSDB
注このセクションでは、TSDBへのネイティブヒストグラムの保存に関する概要と、見落としがちな重要な個別の側面について説明します。実装の詳細、ディスク上の形式の定義、またはコードベースのガイドを目的としたものではありません。さまざまなストレージ形式に関する詳細なドキュメントと、もちろん通常生成されるGoDocがあり、tsdbパッケージとstorageパッケージが適切な出発点です。前述のPrometheus Native Histograms Developer's Guideも役立つリソースです。
整数ヒストグラムと浮動小数点ヒストグラム
TSDBは、整数ヒストグラムと浮動小数点ヒストグラムを異なる方法で保存します。一般的に、整数ヒストグラムはより良く圧縮されると予想されるため、すべてのバケットカウントと観測のカウントがint64の範囲内の整数値である場合、TSDBの実装は浮動小数点ヒストグラムを整数ヒストグラムとして保存することができます。これにより、元の浮動小数点ヒストグラムの数値的に正確な表現が作成されます。(Prometheus TSDBはこのオプションをまだ利用していないことに注意してください。)
エンコーディング
ネイティブヒストグラムは、TSDBに2つの新しいチャンクエンコーディング(Go型chunkenc.Encoding
)を必要とします。整数ヒストグラムにはchunkenc.EncHistogram
(文字列表現histogram
、数値2)、浮動小数点ヒストグラムにはchunkenc.EncFloatHistogram
(文字列表現floathistogram
、数値3)です。
同様に、WALとインメモリのスナップショットには2つの新しいレコードタイプ(Go型record.Type
)があります。整数ヒストグラムにはrecord.HistogramSamples
(文字列表現histogram_samples
、数値9)、浮動小数点ヒストグラムにはrecord.FloatHistogramSamples
(文字列表現float_histogram_samples
、数値10)です。後方互換性の理由から、さらに2つのヒストグラムレコードタイプがあります。record.HistogramSamplesLegacy
(histogram_samples_legacy
、7)とrecord.FloatHistogramSamplesLegacy
(float_histogram_samples_legacy
、8)です。これらはNHCBに必要なカスタム値が導入される前に使用されていました。古いWALの読み取りを可能にするためにサポートされています。
Prometheusは時系列をそのラベルだけで識別します。時系列のサンプルが浮動小数点数(したがってカウンタまたはゲージ)であるか、ヒストグラムであるか(どのようなフレーバーであっても)は、時系列のIDには寄与しません。したがって、時系列は異なるタイプとフレーバーのサンプルの混合を含むことができます。時系列内のサンプルタイプの変更は、実際には非常にまれであると予想されます。これらは通常、ターゲットの計測の変更後(同じメトリック名が、変更前はゲージ浮動小数点数に、変更後はカウンタヒストグラムに使用される稀なケース)または記録ルールの変更後(たとえば、ルールの古いバージョンがゲージ浮動小数点数を作成し、新しいバージョンのルールが同じ名前を保持したままゲージヒストグラムを作成する場合)に発生します。サンプルタイプの頻繁な変更は、通常、誤った設定(たとえば、異なるサンプルタイプを作成する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としてエンコードされ、最初のサンプルでは絶対値、2番目のサンプルでは最初のサンプルと2番目のサンプルの間の差分、それ以降のサンプルでは「差分の差分」(つまり、従来の浮動小数点チャンクのタイムスタンプに使用されるのと同じ「ダブルデルタ」エンコーディングですが、varbit-intエンコーディングのビットバケットが異なります)が使用されます。
- 観測値のカウントは、最初のサンプルではvarbit-uintとしてエンコードされ、それ以降のサンプルではvarbit-intとしてエンコードされ、タイムスタンプと同じ「差分の差分」アプローチを使用します。
- ゼロバケットの個体群は、最初のサンプルではvarbit-uintとして、それ以降のサンプルではvarbit-intとしてエンコードされ、タイムスタンプと同じ「差分の差分」アプローチを使用します。
- 観測値の合計は、最初のサンプルではfloat64としてエンコードされ、それ以降のサンプルではvarbit-xorとしてエンコードされます(現在のサンプルと前のサンプルの間でXOR演算を実行)。
- 正のバケットのバケット人口は、それぞれ前のバケットからの差分(または最初のバケットでは絶対人口)としてエンコードされ、タイムスタンプと同じ「差分の差分」アプローチを使用してvarbit-intとしてエンコードされます。(つまり、「ダブルデルタ」エンコーディングは、それ自体がすでに差分である値に適用されるため、これは「トリプルデルタ」エンコーディングと呼ばれることもあります。)
- 負のバケットのバケット数は同様に。
浮動小数点ヒストグラムのサンプルデータには、以下の違いがあります。
- 観測値のカウントとゼロバケットの集団は浮動小数点数となり、したがって観測値の合計と同じ方法でエンコードされます(最初のサンプルではfloat64、それ以降のサンプルではvarbit-xor)。
- バケットのデータ量は浮動小数点数になっただけでなく、バケット間の差分ではなく絶対的なデータ量になりました。最初のサンプルでは、すべてのバケットのデータ量はプレーンなfloat64として表現され、それ以降のすべてのサンプルでは、現在のサンプルと前のサンプルの対応するバケットをXORすることでvarbit-xorとしてエンコードされます。
以下のイベントは、新しいチャンクの作成をトリガーします(括弧内に理由を記載)。
- 整数ヒストグラムと浮動小数点ヒストグラム間のサンプルタイプの変更(両方ともまったく異なるチャンクエンコーディングが必要なため)。
- ゲージヒストグラムとカウンターヒストグラム間のサンプルタイプの変更(先頭バイトが異なるタイプを示す必要があるため)。
- カウンタヒストグラムのカウンタのリセット(カウンタリセット情報として先頭バイトに格納するため、詳細は後述)。
- スキーマの変更(新しいチャンクレイアウトが必要であり、1つのチャンクには1つのチャンクレイアウトしか持つことができないため)。
- ゼロ閾値の変更(チャンクレイアウトが変更されるため、上記を参照)。
- カスタム値の変更(チャンクレイアウトが変更されるため、上記を参照)。
- 陳腐化マーカーの後に通常のサンプルが続く場合(新しいチャンクを厳密には必要としませんが、ほとんどのヒストグラムは消失と復帰の際に非常に大きく変化すると仮定できるため、新しいチャンクを作成するのが最善の選択肢です)。
- チャンクサイズ制限を超過(詳細は下記を参照)。
スパンの違いもチャンクレイアウトを変更しますが、それらは必要に応じて(明示的に表現される)空のバケットを追加することで調整され、チャンク内のすべてのヒストグラムが同じスパン構造を共有します。バケットが消滅した場合、ヒストグラムがチャンクに追加される際に、新しいヒストグラムに空のバケットとして追加されるだけなので、これは簡単です。しかし、以前にデータが入力されていたバケットの消滅はカウンタリセットを構成するため(下記を参照)、このケースはゲージヒストグラム(カウンタリセットを特徴としない)でのみ発生します。より一般的なケースは、以前に追加されたヒストグラムには存在しなかったバケットが、新しく追加されたヒストグラムに存在する場合です。この場合、これらのバケットは、以前に追加されたすべてのヒストグラムに明示的に空のバケットとして追加する必要があります。これにはチャンク全体の完全な再エンコードが必要です。(影響を受ける部分のみを再エンコードすることで最適化の可能性があります。これを実装するのはかなり複雑でしょう。これまでのところ、完全な再エンコードのパフォーマンスへの影響は問題として際立っていません。)
鮮度マーカー
注以下のセクションを理解するためには、TSDBにおける鮮度マーカーの仕組みを思い出すことが重要です。浮動小数点シリーズにおける鮮度マーカーは、NaN
値を表すために使用できる多くのビットパターンの中から、特定の1つのビットパターンで表現されます。この非常に特殊な浮動小数点値は、以下のセクションで「特別な陳腐化NaN
値」と呼ばれます。これは通常の算術浮動小数点演算によって(ほぼ確実に)返されることはなく、そのため観測値の特殊なケースで議論されたものを含む、「自然に発生する」NaN
値とは異なります。実際、特別な陳腐化NaN
値はTSDBのクエリ時に直接返されることはありませんが、呼び出し元に到達する前に内部で処理されます。
ヒストグラム系列の陳腐化をマークするには、通常の特殊な陳腐化NaN
値を使用できます。しかし、これでは系列を陳腐化としてマークするためだけに新しいチャンクを作成する必要があります。ヒストグラム値の後に続く浮動小数点値は別のチャンクに保存する必要があるためです(上記を参照)。したがって、合計観測値のフィールドが特殊な陳腐化NaN
値に設定されている陳腐化マーカーのヒストグラムバージョンも存在します。この場合、他のすべてのフィールドは無視され、効率的なストレージに適した値に設定できます(陳腐化マーカーのヒストグラムバージョンは本質的にストレージ最適化にすぎないため)。これは浮動小数点ヒストグラムと整数ヒストグラムの両方に機能し(合計フィールドは整数ヒストグラムでも浮動小数点値であるため)、新しいチャンクの作成を避けるために適切なバージョンを使用できます。陳腐化マーカーのすべてのバージョン(浮動小数点、整数ヒストグラム、浮動小数点ヒストグラム)は、TSDBによって同等に扱われる必要があります。
チャンクサイズ制限
浮動小数点チャンクのサイズは1024バイトに制限されています。ヒストグラムチャンクも一般的に同じサイズ制限が適用されます。ただし、個々のヒストグラムは多くのバケットを持つ場合、非常に大きくなる可能性があるため、無闇にサイズ制限を強制すると、ヒストグラムが非常に少ないチャンクになる可能性があります。(最も極端な場合、単一のヒストグラムが1024バイトを超えることもあり、サイズ制限をまったく強制できないこともあります。)チャンクあたりのヒストグラム数が非常に少ないと、圧縮率が悪くなります。したがって、1024バイトのサイズ制限が適用される前に、チャンクあたり最低10個のヒストグラムに達する必要があります。これは、ヒストグラムチャンクが1024バイトよりもはるかに大きくなる可能性があることを意味します。
チャンクあたり最低10個のヒストグラムを要求することは、初期の非常に単純なアプローチであり、チャンクサイズと圧縮率のより良いトレードオフを見つけるために将来改善される可能性があります。
カウンタリセットに関する考慮事項
一般的に、Prometheusは、カウンタの値が1つのサンプルから次のサンプルに減少した場合に、そのカウンタがリセットされたと見なします(ただし、作成タイムスタンプに関する次のセクションも参照)。2つのヒストグラムサンプルの間でカウンタのリセットを検出する場合、状況はより複雑になります。
まず第一に、ゲージヒストグラムとカウンタヒストグラムは明確に異なります(Prometheusは通常、取り込み後、ゲージメトリックとして取り込まれたかカウンタメトリックとして取り込まれたかに関わらず、すべての浮動小数点サンプルを等しく扱います)。カウンタリセットはゲージヒストグラムには適用されません。
時系列において、ゲージヒストグラムの後にカウンタヒストグラムが続く場合、カウンタリセットが発生したと見なされます。なぜなら、ゲージからカウンタへの変更は、ゲージが削除され、カウンタがゼロから新しく作成されたのと同等と見なされるからです。
最も一般的なケースは、カウンタヒストグラムの後に別のカウンタヒストグラムが続く場合です。この場合、以下の手順でカウンタリセットの可能性が検出されます。
2つのヒストグラムのスキーマまたはゼロバケット幅が異なる場合、これらの変更は互換性のある解像度削減の一部である可能性があります(これはヒストグラムのバケット数を減らすために定期的に発生します)。互換性のある解像度削減の場合、以下の両方が真です。
- スキーマが変更された場合、その番号は1つの標準指数スキーマから別の標準スキーマへと減少しています。
- ゼロバケット幅が変更された場合、最初のヒストグラム内の埋められた通常のバケットは、2番目のヒストグラムのゼロバケットに完全に含まれるか、まったく含まれません(つまり、古い通常のバケットと新しいゼロバケットの部分的な重複はありません)。
いずれかの条件が満たされない場合、その変更は互換性のある解像度削減ではありません。このような変更は、ヒストグラムをリセットまたは新規作成することによってのみ可能であるため、カウンターのリセットと見なされ、検出手順は終了します。
両方の条件が満たされる場合、最初のヒストグラムを変換して、そのスキーマとゼロバケット幅が2番目のヒストグラムのものと一致するようにする必要があります。これは、以前に説明したのと同じ方法で行われます。スキーマを削減するために隣接するバケットがマージされ、ゼロバケットの幅を増やすために通常のバケットがゼロバケットとマージされます。
この手順の時点で、両方のヒストグラムは同じスキーマとゼロバケット幅を持っています。これは、最初からそうであったか、最初のヒストグラムがそれに応じて変換されたためです。(NHCBはゼロバケットを使用しないことに注意してください。この手順の目的のために、それらのゼロバケット幅と母集団カウントは等しいと見なされます。)この状況では、以下のいずれかがカウンターリセットを構成します。
- 観測数の減少(ただし、観測の合計の減少はない)。
- ゼロバケットを含む任意のバケットの母集団カウントの減少。これは、埋められたバケットが消滅する場合も含まれます。これは、表現されていないバケットが母集団ゼロのバケットと同等であるためです。
- カスタム値の変更。これは、カスタム値を使用するスキーマ(現在のスキーマ-53、すなわちNHCB)にのみ適用されます。(TODO:原則として、NHCBにも互換性のあるバケット変更の概念があるかもしれませんが、そのような概念はまだ実装されていません。)
上記のいずれも該当しない場合、カウンターのリセットはありません。
この全体の手順は比較的複雑であるため、カウンターリセットの検出は、取り込み中に一度行われ、その結果が後で使用するために永続化されることが望ましいです。カウンターリセットの検出は、カウンターリセットが新しいチャンクを切り出すトリガーの1つであるため、取り込み中にいずれにせよ行われる必要があります。
カウンターリセット後に新しいチャンクを切り出す目的は、圧縮率を向上させることです。カウンターリセットはすべてのバケットの母集団をゼロに設定するため、表現するバケットが少なくなります。しかし、チャンクはチャンク内のすべてのヒストグラムのすべてのバケットのスーパーセットを表現する必要があるため、新しいチャンクを切り出すことで、新しいチャンクのより単純なバケットセットが可能になります。
これは、チャンク内の最初のサンプル後にカウンターリセットが決して発生しないことを意味します。したがって、永続化する必要がある唯一のカウンターリセット情報は、チャンク内の最初のヒストグラムのものです。これは、チャンク内のサンプル数の直後に保存される1バイトの「ヒストグラムフラグ」と呼ばれるものに保存されます。このバイトは現在、カウンターリセット情報にのみ使用されていますが、将来的には他のフラグにも使用される可能性があります。カウンターリセット情報は最初の2ビットを使用します。可能な4つのビットパターンは、chunkenc
パッケージ内のCounterResetHeader
型のGo定数として表されます。それらの名前と意味は次のとおりです。
GaugeType
(ビットパターン11
):チャンクにはゲージヒストグラムが含まれます。カウンターリセットはゲージヒストグラムには関係ありません。CounterReset
(ビットパターン10
):前のチャンクの最後のヒストグラムとこのチャンクの最初のヒストグラムの間でカウンターリセットが発生しました。(新しいチャンクが切り出された実際の理由はカウンターリセットであった可能性が高いです。)NotCounterReset
(ビットパターン01
):前のチャンクの最後のヒストグラムとこのチャンクの最初のヒストグラムの間でカウンターリセットは発生しませんでした。(これは通常、前のチャンクがサイズ制限に達したために新しいチャンクが切り出された場合に発生します。)UnknownCounterReset
(ビットパターン00
):前のチャンクの最後のヒストグラムとこのチャンクの最初のヒストグラムの間でカウンターリセットがあったかどうかは不明です。
UnknownCounterReset
は常に安全な選択です。これはカウンターリセット検出を妨げるものではなく、カウンターリセット情報が必要な場合に(再度)カウンターリセット検出手順を実行する必要があるだけです。
カウンターリセット情報は、TSDBをクエリする際に呼び出し元に伝播されます(Goコードでは、Go型Histogram
およびFloatHistogram
のCounterResetHint
型のフィールドとして、上記のビットパターン定数と同じ名前の列挙定数を使用します)。
ゲージヒストグラムの場合、CounterResetHint
は常にGaugeType
です。その他のCounterResetHint
値は、対象のヒストグラムがカウンターヒストグラムであることを意味します。このようにして、クエリア(PromQLエンジンを含む。下記参照)は、ヒストグラムがゲージかカウンターかという情報(浮動小数点サンプルとは著しく異なる)を取得します。
単一のチャンクからカウンターヒストグラムが順番に返される限り、チャンク内の2番目以降のヒストグラムのCounterResetHint
はNotCounterReset
に設定されます。(重複するブロックや順不同の取り込みは、複数のチャンクからのヒストグラムシーケンスにつながる可能性があり、特別な処理が必要です。下記を参照。)
カウンターヒストグラムチャンクから最初のヒストグラムを返す際、TSDB実装が、以前に返されたヒストグラムが、取り込み時にカウンターリセットを検出するための先行するヒストグラムとして実際に使用された同じヒストグラムであることを保証できない限り、CounterResetHint
はUnknownCounterReset
に設定されなければなりません。後者の場合にのみ、チャンクからのカウンターリセット情報を、返されたヒストグラムのCounterResetHint
として直接使用できます。
チャンクが削除または挿入される様々な方法(例:トゥームストーンによる削除やバックフィル用のブロック追加)があるため、この予防措置が必要です。カウンターリセットは1つのサンプルに起因するものの、実際にはマークされたサンプルと先行するサンプルの間で発生しています。先行するサンプルの削除や、2つのサンプルの間に別のサンプルを挿入すると、以前に実行されたカウンターリセット検出は無効になります。
TODO現在、Prometheus TSDBは、先行するチャンクが取り込み時と同じチャンクであることを保証する手段を持っていません。そのため、Prometheusは現在、カウンターヒストグラムチャンクからのすべての最初のヒストグラムに対してUnknownCounterReset
を返します。これを変更するための取り組みについては、トラッキングイシューを参照してください。
上記で示唆されているように、CounterResetHint
がUnknownCounterReset
に設定されている場合、クエリアはカウンターリセット検出手順を(再度)実行する必要があります。
重複するブロックや順不同のサンプルを処理する際(クエリ時または圧縮時)、特別な注意が必要です。以下の例に示すように、これらのケースでは、カウンターリセットの過検出と過少検出の両方が発生する可能性があります。
- 過少検出の例: 1つのチャンクにサンプルABCが含まれており、カウンターリセットはありません。別のチャンクにサンプルDEFが含まれており、これもカウンターリセットはありません。これらのチャンクは重複しており、同じシリーズを参照しています。それらをまとめてクエリすると、サンプルの時間順序はADBECFとなります。この場合、これらのサンプルの一部またはすべて、あるいはその間にカウンターリセットが発生している可能性は十分にあります。これは、2つのサンプルが実際には無関係なシリーズのものであり、誤って同じシリーズにマージされた場合に特に起こりやすいです。しかし、このような偶発的なマージであっても、TSDBは正しく処理する必要があります。重複するチャンクが新しいチャンクに圧縮される場合、新しいカウンターリセット検出が行われ、新しいカウンターリセットが捕捉されます。重複するチャンクを直接クエリする場合(事前の圧縮なし)、以前に返されたサンプルとは異なるチャンクから来る各サンプルに対して
UnknownCounterReset
のCounterResetHint
を設定する必要があり、これによりクエリアによるカウンターリセット検出が義務付けられます(上記で説明した安全なフォールバックを利用して)。 - 過剰検出の例: BとCの間でカウンターリセットが発生するサンプルABCDのシーケンスがあります。しかし、最初の取り込みでBとCが欠落し、AとDのみが取り込まれ、AとDの間でカウンターリセットが検出されました。その後、BとCが取り込まれ(順不同の取り込みまたは後でTSDBに別々のブロックとして追加された別々のチャンクを介して)、BとCの間でカウンターリセットが検出されました。この場合、各サンプルは独自のチャンクに入るため、すべてのチャンクを組み立てても重複することはありません。しかし、上記のルールに従ってカウンターリセットヒントを返すと、CとDの両方が
CounterResetHint
としてCounterReset
でクエリアに返されますが、現在CとDの間にはカウンターリセットはありません。前の例の状況と同様に、AとBの間で、そしてCとDの間で新しいカウンターリセット検出を実行する必要があります。または、BとDの両方をUnknownCounterReset
のCounterResetHint
で返す必要があります。
要約すると、TSDBが2つのサンプル間のカウンターリセット検出が取り込み時に発生したことを安全に確立できない場合、別のカウンターリセット検出を実行するか、2番目のサンプルに対してUnknownCounterReset
のCounterResetHint
を返す必要があります。
上記で説明した手順では検出されないカウンターリセットの可能性に注意してください。具体的には、リセットされたヒストグラムのカウントが十分に速く増加し、カウンターリセット後の最初のサンプルに、カウンターリセット前の最後のサンプルと比較して減少したカウントがない場合です。(これは浮動小数点カウンターでも問題となり、実際に発生しやすいです。)上記で説明したメカニズムを使用すると、他の手段でカウンターリセットが検出された場合、この場合でもカウンターリセットを保存できます。しかし、チャンクの挿入と削除、順不同のサンプル、および重複するブロックによって生じる複雑さ(上記参照)のため、2回目のカウンターリセット検出が必要な場合、この情報が失われる可能性があります。(TODO:現在、この情報は確実に失われます。上記のTODOを参照。)カウンターリセットを安全にマークするためのより良い方法は、作成タイムスタンプを介することです(次セクションを参照)。
作成タイムスタンプの取り扱い
OpenMetricsは、カウンター、サマリー、および従来のカウンターヒストグラムに、いわゆる「作成タイムスタンプ」を導入しました。(この用語は、おそらく「作成時刻タイムスタンプ」の略です。より適切な用語は「作成タイムスタンプ」または「リセットタイムスタンプ」であったかもしれませんが、「作成タイムスタンプ」という用語は今やしっかりと確立されています。)
作成タイムスタンプは、メトリックが作成またはリセットされた最新の時間を提供します。設計ドキュメントは、Prometheusが作成タイムスタンプをどのように処理するかを説明しています。
作成タイムスタンプはネイティブヒストグラムにも役立ちます。浮動小数点カウンターに合成ゼロサンプルが挿入されるのと同じように、カウンターヒストグラムにはヒストグラムサンプルのゼロ値が挿入されます。ヒストグラムのゼロ値には埋められたバケットがなく、観測の合計、観測のカウント、ゼロバケットの母集団はすべてゼロです。スキーマ、ゼロバケット幅、カスタム値、およびヒストグラムの浮動小数点と整数の種類は、合成ゼロサンプルに直接続くサンプルと一致すべきです(不正確なカウンターリセットの検出をトリガーしないため)。
合成ゼロサンプルのカウンターリセット情報は常にCounterReset
に設定されます。(TODO:現在、Prometheusはおそらくシリーズの最初のサンプルに対してUnknownCounterReset
を設定していますが、これは間違いではありませんが、CounterReset
に設定する方が理にかなっていると思います。)
エクセンプラー
ネイティブヒストグラムのエクセンプラーは、個々のバケットではなく、ヒストグラムサンプル全体に付加されます。(エクスポジション形式のセクションも参照。)したがって、単一のネイティブヒストグラムサンプルに複数のエクセンプラーが付加されることは許容されており(実際、一般的なケースです)。
エクセンプラーは、あるスクレイプから次のスクレイプへと変更される場合とされない場合があります。スクレーパーは、多くの重複エクセンプラーの保存を避けるために、変更されていないエクセンプラーを検出するべきです。ただし、単一のサンプルに多くのエクセンプラーがあり、その任意のサブセットが最後のスクレイプからの繰り返しエクセンプラーである可能性があるため、重複検出は潜在的に高価です。TSDBは、新しいエクセンプラーは以前に公開されたエクセンプラーのいずれよりも最近のタイムスタンプを持つという仮定に依拠することができます。(ネイティブヒストグラムのエクセンプラーはタイムスタンプを持つ必要があることに注意してください。)これにより、重複検出は効率的な方法で可能になります。
- 新しく取り込まれたネイティブヒストグラムのエクセンプラーは、以下のフィールドによってソートされます。まずタイムスタンプ、次に値、そしてラベルです。
- エクセンプラーは、ソートされた順序でエクセンプラー保存領域に追加されます。
- 追加は、最後に正常に追加されたエクセンプラー(同じメトリックの以前のスクレイプからのものである可能性があります)よりも前にソートされるか、またはそれと等しいエクセンプラーに対しては失敗します。
- 追加は、最後に正常に追加されたエクセンプラーの後にソートされるエクセンプラーに対しては成功します。
エクセンプラーは、取り込まれたヒストグラムのすべてのエクセンプラーが最後に正常に追加されたエクセンプラーの前にソートされる場合にのみ、順不同と見なされます。これは、新しいエクセンプラーや最後に正常に追加されたエクセンプラーの複製と混在する順不同のエクセンプラーを検出しませんが、これは許容されると見なされます。
PromQL
このセクションでは、PromQL がネイティブヒストグラムをどのように扱うかを説明します。個々の操作のすべての詳細ではなく、一般的な概念に焦点を当てています。後者については、オペレーターと関数に関する PromQL ドキュメントを参照してください。
アノテーション
ネイティブヒストグラムの導入により、PromQL式が予期しない結果を返す特定の状況、最も一般的には出力ベクター内の一部の要素またはすべての要素が予期せず欠落しているケースが発生します。ユーザーがこれらの状況を検出し理解するのを助けるために、ネイティブヒストグラムに対して作用する操作はしばしばアノテーションを使用します。アノテーションには警告レベルと情報レベルがあり、評価中に遭遇する可能性のある問題を説明します。警告レベルは、ユーザーが対処する必要がある実際の問題である可能性が高い状況をマークするために使用されます。情報レベルは、意図的なものである可能性もありますが、それでもフラグを立てるには十分に異常な状況のために使用されます。
整数ヒストグラム vs. 浮動小数点ヒストグラム
PromQLは常に浮動小数点ヒストグラムを扱います。整数ヒストグラムとして格納されているネイティブヒストグラムは、TSDBから取得される際に自動的に浮動小数点ヒストグラムに変換されます。
ヒストグラム間の互換性
オペレーターまたは関数が2つ以上のネイティブヒストグラムに作用する場合、関係するヒストグラムは同じスキーマとゼロバケット幅を持っている必要があります。特定の制限内で、ヒストグラムはこれらの互換性基準を満たすようにその場で変換できます。
- NHCB (スキーマ -53) は、他の NHCB とのみ互換性があり、その他の NHCB も正確に同じカスタム値を持っている必要があります。(原則として、互換性のあるカスタム値の差を調整できる可能性がありますが、PromQL はまだそれらを考慮していません。)
- 標準スキーマのヒストグラムは、より大きいスキーマ(つまり高解像度)のヒストグラムの解像度を減少させることで、常に最小の(つまり低解像度の)共通スキーマに変換できます。これは、隣接するバケットをより小さいスキーマの大きいバケットにマージする通常の方法で行われます。
- 異なるゼロバケット幅は、より小さいゼロバケットを拡張し、必要に応じて埋められた通常のバケットを拡張されたゼロバケットにマージすることで処理されます。最大の共通幅が埋められたバケットの途中で終わる場合、そのバケットの境界と一致するようにさらに拡張されます。(詳細は上記のゼロバケットのセクションを参照してください。)
互換性がないために操作ができない場合、結果に警告レベルのアノテーションが追加されます。
カウンターリセット
カウンターリセットは、上記で説明したとおりに定義されます。TSDBから返されるカウンターリセットヒントは、明示的なカウンターリセット検出を回避し、通常の手順では検出できないカウンターリセットを正しく処理するために考慮される場合があります。(これは、これらのカウンターリセットが最善の努力ベースで考慮されることを意味します。ただし、TSDB自体についても同じことが言えます。上記を参照。)従来のヒストグラムやサマリーのカウンターリセット処理との顕著な違いは、観測値の合計の減少自体はカウンターリセットを構成しないことです。(たとえば、ヒストグラムが負の値を観測した場合でも、ネイティブヒストグラムのレートを計算することは正しく機能します。)
サブクエリから返されるカウンターヒストグラムのカウンターリセットヒントは、PromQLエンジンがサブクエリから返される連続するカウンターヒストグラムがTSDBでも連続していることを安全に検出できる場合を除き、明示的なカウンターリセット検出を回避するために考慮してはならないことに注意してください。
ゲージヒストグラム vs. カウンターヒストグラム
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
修飾子の両方で機能します。これらは、スキーマ、カスタム値、ゼロしきい値、すべてのバケットの母集団、および観測の合計と数を比較します。ヒストグラムがカウンターまたはゲージのどちらのタイプであるかは比較には関係ありません。(カウンターヒストグラムがゲージヒストグラムと等しいこともあり得ます。)
論理/集合二項演算子 (and
, or
, unless
) は、ヒストグラムサンプルが関与する場合でも、期待どおりに機能します。これらはベクトル要素の存在のみをチェックし、要素のサンプルタイプやタイプ (浮動小数点数またはヒストグラム、カウンターまたはゲージ) に応じて動作を変更しません。
「トリム」演算子>/
と</
は、特にネイティブヒストグラムのために導入されました。これらは、左側にヒストグラムがあり、右側に浮動小数点サンプルまたはスカラーがある場合にのみ機能します。(両側に浮動小数点サンプルまたはスカラーがある場合には機能しません。この場合、情報レベルのアノテーションが返されます。)これらの演算子は、ヒストグラムから右側の浮動小数点値よりも大きいまたは小さい観測値を取り除き、結果のヒストグラムを返します。削除は、しきい値がバケット境界と一致する場合にのみ正確です。そうでない場合は、上記で説明したように、影響を受けるバケット内で補間を使用する必要があります。ヒストグラムのカウンターまたはゲージのタイプは維持されます。(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番目のサンプルと他のサンプルの間の互換性のないバケットレイアウトは、単にサイレントに無視されます。
ゼロ未満への外挿を防ぐため、浮動小数点カウンターと同じヒューリスティックが適用されますが、観測値の数のみに基づいています。したがって、個々のバケットは場合によってはゼロ未満に外挿される可能性があります。別の方法としては、カウントもバケットもゼロ未満に外挿されない最小の外挿を見つけることができたかもしれません。しかし、これは必ずしもより良いヒューリスティックにつながるとは限らず、複雑さにおいて大きなコストを伴います。範囲内の最初のサンプルが作成タイムスタンプから派生した合成ゼロサンプルであるという一般的で重要なケースでは、制限された外挿は完全に正確に機能します。これは、合成サンプルのタイムスタンプでカウントとすべてのバケットがゼロであり、これも外挿が制限される時点であるためです。従来のヒストグラムは、各バケット、カウント、合計にヒューリスティックを個別に適用することに注意してください(これらはすべて別々の系列であるため)。これは矛盾につながることが知られています。NHCBはこの問題を再現せず、他のネイティブヒストグラムと同じ方法で機能します。これは、従来のヒストグラムと同等のNHCBを比較する場合、rate()
とincrease()
の結果がわずかに異なる場合があることを意味します。
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
の観測値として扱います。これは、NaN
が histogram_quantile
が返すどの値よりも小さいことはないという論理に基づいています。結果が既存のバケットに収まる限り、NaN
観測値が +Inf
として観測されたかのように計算された結果を返し、結果が NaN
のために歪んでいることをユーザーに知らせる情報レベルのアノテーションも発行します。これは、従来のヒストグラムが通常 NaN
観測値を扱う方法(ほとんどの実装では +Inf
バケットに収まります)と一貫しています。結果が既存のすべてのバケットを超えた場合、NaN
を返します。その理由の詳細な説明は、以下の histogram_fraction
を参照してください。直感的には、このケースは、NaN
がどの数値とも比較できないため、クォンタイル内のすべての観測値よりも大きい数値がないことを意味します。このケースに特化した情報レベルのアノテーションも返します。
以下の関数は、ネイティブヒストグラムのために特別に導入されました。
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)
は、histogram
内の観測値が指定された境界(スカラー値lower
およびupper
)の間にある推定割合を返します。推定の誤差は、基礎となるネイティブヒストグラムの解像度と、指定された境界がヒストグラム内のバケット境界とどれだけ密接に一致しているかに依存します。+Inf
および-Inf
は有効な境界値であり、特定の値より上または下にあるすべての観測値の割合を推定するのに役立ちます。ただし、値NaN
の観測値は常に指定された境界の外側にあると見なされます(+Inf
および-Inf
であっても)。指定された境界が包括的か排他的かは、指定された境界が基礎となるネイティブヒストグラム内のバケット境界と正確に一致する場合にのみ関連します。この場合、動作はヒストグラムのスキーマの正確な定義に依存します。
q = histogram_fraction(-Inf, x, histogram)
の値は、x
以下の観測値の割合が q
であることを意味します。一方、y = histogram_quantile(q, histogram)
は、観測値の q
割合が y
以下であることを意味します。histogram_quantile
は y
のおおよその最小値を計算するため、一般に y<=x
が成り立ちます。90% の観測値が NaN
であるケースを考えてみましょう。histogram_fraction
は NaN
観測値をどのバケットの外側にあると見なすため、histogram_fraction
の最大値は 0.1
となります。例えば histogram_quantile(0.5, histogram)
が何らかの実数 y
を返した場合、上記の議論に従って、y<=x
であり histogram_fraction(-Inf, x, histogram)
が 0.5
に等しいような x
が見つかるはずですが、そのような y
は存在しないため、histogram_quantile
の結果がすべてのバケットの外側である場合、NaN
を返します。
以下の関数はサンプル値と直接やり取りしないため、ネイティブヒストグラムサンプルで浮動小数点サンプルと同様に機能します。
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
コマンドの代替としてload_with_nhcb
というコマンドがあり、従来のヒストグラムをNHCBに変換し、従来のヒストグラムの浮動小数点系列と変換によって生成されたNHCB系列の両方をロードします。
ネイティブヒストグラムに固有ではありませんが、その文脈で非常に役立つのが、ユニットテストフレームワークのexpect
キーワードです。これは、情報および警告レベルのアノテーションに関する期待値を定義できます。
最適化
通常通り、PromQLの実装は、動作が同じである限り、適切と判断するあらゆる最適化を適用できます。ネイティブヒストグラムのデコードは、潜在的に多くのバケットがあるため、非常に高価になる可能性があります。同様に、PromQLエンジン内でヒストグラムサンプルをディープコピーすることは、単純な浮動小数点サンプルをコピーするよりもはるかに高価です。これは、常にすべてをデコードし、常にすべてをコピーするという素朴なアプローチと比較して、最適化の大きな可能性を生み出します。
Prometheusは現在、不要なコピーを避けるように努めています(TODO:しかし、よりクリーンでバグが少ないため、適切なCoWのようなアプローチはまだ実装する必要があります)。また、観測値の合計とカウントのみが必要な特殊なケースでは、バケットのデコードをスキップします。
Prometheus クエリ API
クエリAPIドキュメントには、ネイティブヒストグラムのサポートが含まれています。このセクションでは、ネイティブヒストグラムに関連する部分に焦点を当て、APIドキュメントには含まれていないいくつかのコンテキストを提供します。
インスタントおよび範囲クエリ
インスタント(query
エンドポイント)およびレンジ(query_range
エンドポイント)クエリのJSON応答でネイティブヒストグラムを返すには、vector
とmatrix
の両方の結果タイプに新しいキーによる拡張が必要です。
vector
結果タイプは、既存のvalue
キーと同じレベルに新しいキーhistogram
を取得します。これらのキーは相互に排他的であり、つまり、vector
内の各要素は、value
キー(浮動小数点結果の場合)またはhistogram
キー(ヒストグラム結果の場合)のいずれかを持っています。histogram
キーの値は、value
キーの値と同様に(2要素配列)構造化されていますが、浮動小数点サンプル値を表す文字列が、以下に説明する特定のヒストグラムオブジェクトに置き換えられている点が異なります。
matrix
の結果型は、既存のvalues
キーと同じレベルに新しいキーhistograms
を取得します。これらのキーは相互に排他的ではありません。系列には浮動小数点値とヒストグラム値の両方を含めることができますが、指定されたタイムスタンプに対しては、浮動小数点またはヒストグラムのいずれか1つのサンプルのみが存在する必要があります。histograms
キーの値は、values
キーの値と同様に(N個の2要素配列の配列)構造化されていますが、浮動小数点サンプル値を表す文字列が、以下に説明する特定のヒストグラムオブジェクトに置き換えられている点が異なります。
両方の浮動小数点値とヒストグラム値は値であるため、キーのより良い命名はfloat
/histogram
とfloats
/histograms
であることに注意してください。現在の命名には歴史的な理由があります。(過去には1つの値型、つまり浮動小数点数しかなかったため、キーを単にvalue
とvalues
と呼ぶのは明白な選択でした。)ここでの意図は、ネイティブヒストグラムについて知らない既存のコンシューマーを壊さないことです。
上記のヒストグラムオブジェクトは、以下の構造を持っています。
{
"count": "<count_of_observations>",
"sum": "<sum_of_observations>",
"buckets": [ [ <boundary_rule>, "<left_boundary>", "<right_boundary>", "<count_in_bucket>" ], ... ]
}
count
とsum
は、同じ名前のヒストグラムフィールドに直接対応します。各バケットは、ゼロバケットを含め、その境界とカウントが明示的に表現されます。したがって、スパンとスキーマは応答の一部ではなく、ヒストグラムオブジェクトの構造は使用されるスキーマに依存しません。
<boundary_rule>
プレースホルダーは0から3までの整数で、以下の意味を持ちます。
- 0: 「開左」(左境界は排他的、右境界は包括的)
- 1: 「開右」(左境界は包括的、右境界は排他的)
- 2: 「両方開」(両境界は排他的)
- 3: 「両方閉」(両境界は包括的)
標準スキーマの場合、正のバケットは「左開」、負のバケットは「右開」であり、ゼロバケット(負の左境界と正の右境界を持つ)は「両方閉」です。NHCBの場合、すべてのバケットは「左開」です(従来のヒストグラムの動作を反映)。将来のスキーマでは異なる境界ルールを利用する可能性があります。
メタデータ
series
エンドポイントの場合、ネイティブヒストグラムを含む系列は、浮動小数点数のみを含む従来の系列と同じ方法で含まれます。このエンドポイントは、どのサンプルタイプが含まれるかに関する情報を提供しません(実際、任意の系列にはどちらか一方または両方のサンプルタイプが含まれる可能性があります)。特に、ターゲットによってrequest_duration_seconds
という名前で公開されたヒストグラムがネイティブヒストグラムとして公開され取り込まれる場合、request_duration_seconds
という系列になりますが、従来のヒストグラムとして公開され取り込まれる場合、request_duration_seconds_sum
、request_duration_seconds_count
、request_duration_seconds_bucket
という系列セットになります。ヒストグラムがネイティブヒストグラムと従来のヒストグラムの両方として取り込まれる場合、上記のすべての系列名がseries
エンドポイントによって返されます。
ターゲットおよびメトリックのメタデータ (エンドポイント targets/metadata
および metadata
) は、ターゲットによって公開された元の名前で動作するため、少し異なります。つまり、request_duration_seconds
という名前の従来のヒストグラムは、これらのメタデータエンドポイントでは request_duration_seconds
としてのみ表現され (request_duration_seconds_sum
、request_duration_seconds_count
、request_duration_seconds_bucket
としてではない)、ネイティブヒストグラムの request_duration_seconds
もこの名前で表現されます。request_duration_seconds
が従来のヒストグラムとネイティブヒストグラムの両方として取り込まれる場合でも、返されるメタデータは実際には同じであるため (最も注目すべきは、返される type
が histogram
であるため)、衝突は発生しません。言い換えれば、現在、メタデータエンドポイント単独ではネイティブヒストグラムと従来のヒストグラムを区別する方法はありません。series
エンドポイントを介した追加の検索が必要です。既存のメタデータエンドポイントはとにかく非常に制限されている (履歴情報がない、ルールによって作成されたメトリックのメタデータがない、異なるターゲット間の競合するメタデータを処理する能力が限定的) ため、これを変更する計画はありません。ただし、Prometheus のメタデータ処理全般を改善する計画はあります。これらの取り組みでは、ネイティブヒストグラムを適切にサポートする方法も考慮されます。(TODO: 進捗に応じて更新してください。)
Prometheus UI
このセクションでは、Prometheus独自のUIによるヒストグラムのレンダリングについて説明します。これは、サードパーティのグラフ作成フロントエンドのガイドラインとして使用できます。
「Table」ビューでは、ヒストグラムのデータポイントは棒グラフとしてグラフィカルにレンダリングされ、すべてのバケットのテキスト表現と、それぞれの下限と上限、観測値のカウントと合計が表示されます。棒グラフの各棒はバケットを表します。各棒のx軸上の位置は、対応するバケットの下限と上限によって決定されます。各棒の面積は、対応するバケットの母集団に比例します(これはヒストグラムのレンダリング全般の主要な原則です)。
グラフィカルヒストグラムでは、指数軸と線形軸のいずれかを選択できます。前者がデフォルトです。これは標準スキーマに適しています。(TODO:非指数スキーマの場合は線形をデフォルトとして検討する。)都合の良いことに、指数スキーマのすべての通常バケットは、指数x軸上で同じ幅を持ちます。これは、棒の「面積」(高さではない)がバケット母集団を表すという上記の原則に違反することなく、y軸に実際のバケット母集団を表示できることを意味します。ゼロバケットは例外です。技術的には無限の幅を持ちます。Prometheusは、単に通常の指数バケットと同じ幅でレンダリングします(これにより、x軸はゼロ点付近で厳密に指数的ではありません)。(TODO:非指数スキーマのレンダリング方法。)
線形x軸の場合、バケットの幅は一般に変化します。そのため、y軸にはバケットの母集団をその幅で割った値が表示されます。Prometheus UIでは、y軸の値は人間には解釈しにくいため、レンダリングされません。母集団はテキスト表現で確認できます。
「グラフ」ビューでは、Prometheusはヒートマップを表示します(TODO:まだです、以下を参照)。これは、時間の経過に伴うヒストグラムの系列を90度回転させ、バケットの母集団を棒の高さではなく色として符号化したものと見なすことができます。カウンターのようなヒストグラムをヒートマップとしてレンダリングする一般的なクエリは、rate
クエリです。ヒートマップは、時間の経過とともに分布の特性が変化するのを人間が簡単に識別できる非常に強力な表現です。
TODOヒートマップはまだ実装されていません。代わりに、UIは観測値の合計を従来のグラフとしてプロットするだけです。詳細については、トラッキングイシューを参照してください。同じイシューでは、「Table」ビューでのレンジベクトルのレンダリングの扱いについても議論されています。
テンプレート展開
ネイティブヒストグラムはテンプレート展開で機能します。それらは、開区間と閉区間の数学的表記法に触発されたテキスト表現でレンダリングされます。(これは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によるフェデレーションが好ましいです。
TODONHCBのフェデレーションの現状を明確にする。OMがNHをサポートしたら更新する。
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 analyze
とpromtool 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つの重要な問題源があります。
- ネイティブヒストグラムのクエリ方法は、従来のヒストグラムのクエリ方法とは異なります。ほとんどの場合、変更は最小限で簡単ですが、信頼できる自動変換を行うことを困難にするトリッキーなエッジケースがあります。
- 従来のヒストグラムとネイティブヒストグラムは相互に集計できません。ある時点で従来のヒストグラムからネイティブヒストグラムへの変更を行うと、移行点をまたがるダッシュボードの作成が困難になり、移行点を含む範囲ベクトルは必然的に不完全になります(つまり、従来のヒストグラムを選択する範囲ベクトルは範囲の初期部分のデータポイントのみを含み、ネイティブヒストグラムを選択する範囲ベクトルは範囲の後期部分のデータポイントのみを含みます)。
- 従来のヒストグラムは、関心のある点に正確にバケット境界を持つように調整されている場合があります。標準スキーマを持つネイティブヒストグラムは高解像度を持つことができますが、任意の値にバケット境界を設定することはできません。そのような場合、ネイティブヒストグラムのユーザーエクスペリエンスは実際には悪化する可能性があります。
(3)に対処するために、問題の従来のヒストグラムを移行せずに現状を維持することももちろん可能です。もう1つの選択肢は、計測器は同じままで、取り込み時に従来のヒストグラムを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のドキュメントには、このセクションで説明されている哲学と同じ詳細な移行ガイドが記載されています。