ネイティブヒストグラム [実験的]
ネイティブヒストグラムは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)でした。これらのfloatはgaugeまたはcounterを直接表現できます。Prometheusのメトリックタイプであるsummaryと(クラシックバージョンの)histogramは、エクスポージョンフォーマットに存在するまま、取り込み時にfloatコンポーネントに分解されます。両方のタイプについてsumとcount、summaryの場合は多数のquantileサンプル、(クラシック)histogramの場合は多数のbucketサンプルです。
ネイティブヒストグラムでは、新しい構造化サンプルタイプが導入されます。単一のサンプルは、以前のsumとcountに加えて、動的なバケットセットを表します。これは取り込みに限定されるものではなく、PromQL式も以前はfloatサンプルしか返せなかった場所で、新しいサンプルタイプを返すことができます。
ネイティブヒストグラムには、次の主要なプロパティがあります。
- 空のバケットに対して(ほぼ)ゼロコストを可能にする、スパースバケット表現。
- float64値の全範囲をカバー。
- インストルメンテーション中にバケット境界を設定する必要がない。
- 簡単な設定パラメータに従って選択される動的解像度。
- これらのスキーマを使用するすべてのヒストグラム間でマージ可能であることを保証する、洗練された指数バケットスキーマ。
- エクスポージョンとストレージの両方に対する効率的なデータ表現。
これらの主要なプロパティは、標準的なバケットスキーマで完全に実現されます。トレードオフが異なる他のスキーマもあり、これらのプロパティのサブセットのみを特徴とする場合があります。詳細は以下のスキーマセクションを参照してください。
既存の「クラシック」ヒストグラムと比較して、ネイティブヒストグラム(標準バケットスキーマを使用)は、設定がほとんどまたはまったく不要で、ストレージおよびクエリコストを削減しながら、任意の観測値範囲でより高いバケット解像度を可能にします。ラベルによるヒストグラムのパーティショニングさえも、より手頃になりました。
スパース表現(上記のプロパティ1)は、ネイティブヒストグラムの他の多くの利点にとって非常に重要であるため、設計プロセスの初期には、スパースヒストグラムがネイティブヒストグラムの一般的な名前でした。しかし、指数バケットスキーマやバケットの動的な性質など、他の主要なプロパティも非常に重要ですが、スパースヒストグラムという用語ではまったく捉えられていません。
設計ドキュメント
これらは、ネイティブヒストグラムの開発を導いた設計ドキュメントです。一部の詳細は現在では古くなっていますが、基盤となる概念とそれらがどのように進化してきたかを非常によく説明しています。
- Prometheus 用スパース高解像度ヒストグラム、元の設計ドキュメント。
- Prometheus スパースヒストグラムと PromQL、これは PromQL でのネイティブヒストグラムの扱いに関する正式な設計ドキュメントというよりは、探索的なドキュメントです。
カンファレンストーク
ネイティブヒストグラムについて学ぶためのより親しみやすい方法は、カンファレンストークを視聴することです。以下にその一部を紹介します。入門として、これらのトークを視聴し、その後このドキュメントに戻ってすべての詳細と技術的な側面を学ぶと良いでしょう。
- Prometheus ヒストグラムの秘密の歴史、クラシックヒストグラムとその Prometheus が長年保持していた理由について。
- Prometheus ヒストグラム – 過去、現在、未来、ネイティブヒストグラムにつながる新しいアプローチに関する最初のトーク。
- Prometheus のためのより良いヒストグラム、概念が実際にどのように機能するかを説明しています。
- Prometheus におけるネイティブヒストグラム、実際の実装後のネイティブヒストグラムを紹介し、説明します。
- ネイティブヒストグラムのための PromQL、PromQL でのネイティブヒストグラムの使用方法を説明します。
- 本番環境での Prometheus ネイティブヒストグラム、パフォーマンスとリソース消費の分析を提供します。
- OpenTelemetry の指数ヒストグラムを Prometheus で使用する、OpenTelemetry との相互運用性について説明します。
用語集
- ネイティブヒストグラムは、このドキュメントで扱っている新しい複雑なサンプルタイプインスタンスです。文脈が十分に明確な場合は、単にヒストグラムと呼ばれることがよくあります。
- クラシックヒストグラムは、固定バケットを持つヒストグラムを表す古いサンプルタイプのインスタンスであり、以前は単にヒストグラムと呼ばれていました。エクスポージョンフォーマットではそのように存在しますが、Prometheus への取り込み時に多数の float サンプルに分解されます。
- スパースヒストグラムは、ネイティブヒストグラムの古い、現在は非推奨の名前です。この名前は、古いドキュメントで時折見られることがあります。スパースバケットは、ネイティブヒストグラムのバケットを指す意味のある用語のままです。
データモデル
このセクションでは、ネイティブヒストグラムのデータモデルについて一般的に説明します。可能な限り実装固有のことは避けます。これには用語も含まれます。たとえば、このセクションで説明されるlistは、protobuf 実装ではrepeated messageになり、Go 実装では(ほとんどの場合)sliceになります。
一般的な構造
クラシックヒストグラムと同様に、ネイティブヒストグラムには観測値のcountフィールドと観測値のsumフィールドがあります。観測値のカウントは一般的に非負ですが(PromQL の中間結果を除く)、観測値の合計は任意の float64 値を持つ可能性があります。
さらに、ネイティブヒストグラムには、以下のコンポーネントが含まれており、これらは後述の専用セクションで詳細に説明されます。
- インデックスiを持つ各バケットの境界を決定する方法を識別するためのスキーマ。
- 正と負の観測値に対してミラーリングされた、インデックス付きバケットのスパース表現。
- ゼロに近い観測値をカウントするためのゼロバケット。
- (空の場合がある)カスタム値のリスト。
- Exemplars.
フレーバー
すべてのネイティブヒストグラムには、2つの独立した次元に沿った特定のフレーバーがあります。
- Counter vs. gauge: 通常、ヒストグラムは「counter ライク」であり、つまり各バケットは観測値のカウンターとして機能します。ただし、「gauge ライク」ヒストグラムもあり、各バケットは gauge であり、特定の時点での任意の分布を表します。gauge ヒストグラムの概念は、以前にOpenMetrics によってクラシックヒストグラム用に導入されました。
- 整数 vs. 浮動小数点(略して float): ヒストグラムの明らかなユースケースは観測値をカウントすることであり、各バケット(zero bucket を含む)および観測値の総countは、それぞれ非負の整数(略して uint64)として表現されます。ただし、「重み付け」または「スケーリング」されたヒストグラムにつながる特定のユースケースもあり、その場合、これらの値すべてが64ビット浮動小数点数(略して float64)として表現されます。どちらの場合も、観測値のsumは float64 であることに注意してください。
float ヒストグラムは、直接インストルメンテーションで「重み付け」された観測値に使用されることがありますが、たとえば、観測された値がヒストグラムの異なるバケットにフォールした秒数をカウントする場合などです。float ヒストグラムのより一般的なユースケースは、PromQL 内にあります。PromQL は一般的に float 値のみを操作するため、PromQL エンジンは TSDB から取得したすべてのヒストグラムをまず float ヒストグラムに変換し、記録ルールを介して TSDB に保存されるヒストグラムは float ヒストグラムになります。そのようなヒストグラムが実質的に整数ヒストグラムである場合(すべての非sumフィールドの値が uint64 として正確に表現できるため)、TSDB 実装はストレージ効率を向上させるために整数ヒストグラムに変換する MAY があります。(Prometheus v3.00 時点では、Prometheus 内のTSDB実装はこのオプションを利用していません。)しかし、counter histogram に最も一般的に適用される PromQL 関数は rate であり、これは一般的に非整数値を生成するため、記録ルールの結果は通常、値が非整数であっても float histogram になります。
PromQL 式は「負」のヒストグラムを作成することさえあります(例: ヒストグラムを-1で乗算する)。これらの負のヒストグラムは、中間結果としてのみ許可され、それ以外は無効と見なされます。これらは交換フォーマット(エクスポージョンフォーマット、リモート書き込み、OTLP)では表現できず、TSDB に保存することもできません。また、負のヒストグラムに関する詳細セクションも参照してください。
ネイティブヒストグラムを整数ヒストグラムまたは float histogram として明示的に扱うことは、すべてのスタック全体で単純さのために常に float として扱われる従来の単純な数値サンプルとは顕著な違いです。
ヒストグラムがより複雑に扱われる主な理由は、protobuf ベースのエクスポージョンフォーマットでの効率の容易な向上です。Protobuf は整数に varint エンコーディングを使用しており、追加の圧縮レイヤーを必要とせずに小さな整数値のデータサイズを削減します。この利点は、整数のバケットのデルタエンコーディングによって増幅され、一般的に小さい整数値になります。対照的に、float は protobuf では常に8バイト必要です。実際には、整数ヒストグラムの多くの整数は1バイトに収まり、ほとんどは2バイトに収まるため、protobuf エクスポージョンフォーマットに整数ヒストグラムが明示的に存在することは、バケット数が多いヒストグラムではデータサイズが最大8倍削減されます。これは、インストルメントされたターゲットによって公開されるヒストグラムの圧倒的多数が整数ヒストグラムであるため、特に重要です。
同様の理由で、RAM およびディスク上での整数ヒストグラムの表現は、float histogram よりも一般的に効率的です。ただし、これはエクスポージョンフォーマットでの利点ほど重要ではありません。1つは、Prometheus は float に Gorilla スタイルの XOR エンコーディングを使用しており、これは float のサイズを削減しますが、整数に使用される double-delta エンコーディングほどではありません。さらに重要なのは、実装が常に整数値であるヒストグラムフィールドに対して内部的に整数表現を使用するように決定できることです(上記参照)。(歴史的注: Prometheus v1 は、float サンプルの圧縮を改善するためにこのアプローチを正確に使用しており、Prometheus v3 は将来的にこのアプローチを再度採用する可能性があります。)
counter histogram では、観測値の総countおよびバケット内のカウントは個別に Prometheus counter のように動作します。つまり、counter リセット時にのみ減少します。ただし、負の値の観測結果として、観測値のsumが減少する可能性があります。PromQL 実装は、ヒストグラム全体に基づいて counter リセットを検出 MUST。(詳細については、以下のcounter reset の考慮事項セクションを参照してください)。(これは、クラシックヒストグラムおよびサマリーのsumコンポーネントでも常に問題でした。これまでのアプローチは、これらのケースで counter reset 検出がsumに対してサイレントに壊れることを受け入れることでした。幸いなことに、負の観測値は Prometheus ヒストグラムおよびサマリーの非常にまれなユースケースです。)
スキーマ
スキーマは、8ビット(略して int8)のサイズを持つ符号付き整数値です。バケット境界の計算方法を定義します。現在有効な値は -53 と、-4 から +8(およびそれらの範囲、後述のより広い範囲 -9 から +52 が予約されている)です。将来的には、より多くのスキーマが追加される可能性があります。-53 は、いわゆるカスタムバケット境界または略してカスタムバケットのスキーマですが、他のスキーマ番号は、さまざまな標準指数スキーマ(略して標準スキーマ)を表します。
標準スキーマは互いにマージ可能であり、一般的なユースケースには RECOMMENDED されます。スキーマ番号が大きいほど、解像度が高くなります。スキーマ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を含む正のバケット(上記の境界式による)は、(上記の式で計算された float64 オーバーフローになる境界ではなく)包括的な上限がMaxFloat64になります。- 次の正のバケット(前の項目からインデックスi+1)は、排他的な下限が
MaxFloat64、包括的な上限が+Infです。(これは正のオーバーフローバケットと呼ぶことができます。) MinFloat64を含む負のバケット(上記の境界式による)は、包括的な下限がMinFloat64になります(上記の式で計算された float64 アンダーフローになる境界ではなく)。- 次の負のバケット(前の項目からインデックスi+1)は、排他的な上限が
MinFloat64、包括的な下限が-Infです。(これは負のオーバーフローバケットと呼ぶことができます。) - 上記の
+Infおよび-Infバケットを超えるバケットは使用 MUST NOT されません。
ゼロに近い値に関する例外については、以下のゼロバケットセクションを参照してください。
最低解像度で-4、最高解像度で+8という現在の制限は、実用的な有用性に基づいて選択されました。さらに低いまたは高い解像度の実用的なニーズが生じた場合は、範囲の拡張を検討します。ただし、スキーマが52を超えることは意味がありません。なぜなら、バケット間の成長率が表現可能な float64 数値間の差よりも小さくなるためです。同様に、スキーマが-9未満であることも意味がありません。なぜなら、成長率が float で表現可能な最大値を超えるためです。したがって、-9 から +52(両端を含む)の間のスキーマ番号は、将来の標準スキーマ(上記のバケット境界の式に従う)のために予約されており、他のスキーマには使用 MUST NOT されません。
ネイティブヒストグラムの受信者は、取り込み時に、バケットを適切にマージすることによってスキーマ(およびそれにより解像度)を減らす MAY があります。受信者は、取り込み時に、上記のバケット境界の式に従って有効な番号(-4から8の間)にスキーマを削減する場合、9から52の間のスキーマを受け入れる MAY があります。
このオプションのスキーマ変換後も、スキーマが受信者にとって不明な場合、次のオプションがあります。
- 不明なスキーマを持つヒストグラムが1つ以上含まれるスクレイプ(フェデレーションを含む)は、Prometheusの不完全なスクレイプを回避するという慣習に従い、失敗しなければなりません。
- WAL/WBLの再再生を含む、その他のあらゆる取り込みパスにおいて、レシーバーは不明なスキーマを持つヒストグラムを無視してもよく、この省略について適切な方法でユーザーに通知すべきです。
TSDB実装が永続ストレージからヒストグラムを読み込む場合(WAL/WBLの再再生を除く)、同様のガイドラインが適用されます。スキーマ9から52は、有効なスキーマに変換されてもかまいません。それ以外の場合、不明なスキーマは取得時にエラーを返し、取得をトリガーしたPromQLクエリは失敗しなければなりません。
スキーマ-53の場合、バケット境界は、以下の「カスタム値」セクションで詳細に説明されている*カスタム値*を介して明示的に設定されます。これにより、カスタムバケット境界を持つネイティブヒストグラム(または短い*カスタムバケット*、しばしばNHCBと略される)が得られます。このようなヒストグラムは、従来のヒストグラムをネイティブヒストグラムとして表現するために使用できます。また、標準スキーマが特徴とする指数バケッティングが、ヒストグラムによって表現される分布に合わない場合にも使用できます。異なるカスタムバケット境界を持つヒストグラムは、一般的に互いにマージできません。したがって、スキーマ-53は、特定のユースケースにおいて情報に基づいた決定としてのみ使用されるべきです。
バケット
標準スキーマの場合、バケットは2つのリスト(正のバケット用と負のバケット用)として表されます。カスタムバケット(スキーマ-53)の場合、正のバケットリストのみが使用されますが、すべてのバケットに再利用されます。
空のバケットはリストから除外されてもかまいません。(これが、バケットがしばしば*スパースバケット*と呼ばれる理由です。)
浮動小数点ヒストグラムの場合、リストの要素はfloat64であり、バケットの個数を直接表します。バケットの個数は通常非負ですが、例外はPromQLの*中間結果*です。
整数ヒストグラムの場合、リストの要素は符号付き64ビット整数(短縮形:int64)であり、各要素はリストの前のバケットからのデルタとしてバケットの個数を表します。各リストの最初のバケットは絶対個数(ゼロからのデルタと見なすこともできます)を含みます。デルタは、負の絶対バケット個数にならないようにしなければなりません。
リスト内のバケットを前のセクションで定義されたインデックスにマッピングするために、*スパン*と呼ばれる2つのリスト(正のバケット用と負のバケット用)があります。
各スパンは、*オフセット*と呼ばれる符号付き32ビット整数(短縮形:int32)と*長さ*と呼ばれる符号なし32ビット整数(短縮形:uint32)のペアで構成されます。各リストで最初のスパンのみが負のオフセットを持つことができます。これは、対応するバケットリストの最初のバケットのインデックスを定義します。(NHCBの場合、インデックスは常に正です。詳細は以下の*カスタム値*セクションを参照してください。)長さは、バケットリストが開始する連続するバケットの数を定義します。後続のスパンのオフセットは、除外された(したがって個数のない)バケットの数を定義します。長さは、除外されたバケットの後に続くリスト内の連続するバケットの数を定義します。
各スパンリストのすべての長さの値の合計は、対応するバケットリストの長さに等しくなければなりません。
空のスパン(長さがゼロ)は有効であり、除外されてもかまいませんが、一般的に有用ではなく、後続のスパンのオフセットにそのオフセットを加算することで削除されるべきです。同様に、リストで最初のスパンでないスパンはゼロのオフセットを持つことがありますが、それらのオフセットは前のスパンに長さを加算することで削除されるべきです。どちらの場合も、ネイティブヒストグラムのプロデューサーがその時点で最適なリソースのトレードオフを持つ表現を選択できるように、許容されています。例えば、ヒストグラムがさまざまなステージを経て処理される場合、最後の処理ステージの後にのみ冗長なスパンを削除するのが最も効率的かもしれません。
同様の精神で、個数のないすべてのバケットをバケットリストから除外するのが最も効率的な状況もあれば、個数の少ないバケットを明示的に表現することでスパンの数を減らす方が良い状況もあります。
将来の高解像度スキーマでは、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(浮動小数点ヒストグラムの場合)の単一の値で追跡されます。通常のバケットと同様に、この数値は通常非負です。
ゼロバケットには、*ゼロしきい値*と呼ばれる追加のパラメータがあります。これは≥0のfloat64です。しきい値がゼロに設定されている場合、ちょうどゼロの観測値のみがゼロバケットに入ります。これは上記の場合です。しきい値が正の値の場合、[-しきい値, +しきい値]の閉区間内のすべての観測値が、通常のバケットではなくゼロバケットに入ります。これには2つのユースケースがあります
- ゼロに近いノイズの多い観測値は、多数のバケットを埋める傾向があります。これらの観測値は、数値的な不正確さ、または観測値のソースが実際の物理測定であるために発生する可能性があります。比較的小さいしきい値を持つゼロバケットは、これらの観測値を単一のバケットにリダイレクトします。
- ユーザーが分布のロングテール、ゼロから遠い部分に興味がある場合、ゼロバケットの比較的大きなしきい値は、関心のない範囲の多数の高解像度バケットを回避するのに役立ちます。
ゼロバケットのしきい値は、通常のバケットの境界と一致すべきです。これにより、ゼロバケットが通常のバケットの一部と重複する複雑さが回避されます。ただし、そのような重複が発生する場合、通常のバケットでカウントされる観測値は、[-しきい値, +しきい値]区間の外にある必要があります。
同じゼロしきい値を持つヒストグラムをマージするには、2つのゼロバケットを単純に加算します。ただし、ソースヒストグラムのゼロしきい値が異なる場合は、いずれかのソースヒストグラムの最大しきい値が選択されます。そのしきい値が他のソースヒストグラムのいずれかの個数のあるバケット内にある場合、しきい値は、以下のいずれかが各ソースヒストグラムに対して真になるまで増加します
- 新しいしきい値が個数のあるバケットの境界と一致する。
- 新しいしきい値がいずれの個数のあるバケット内にもない。
次に、ソースゼロバケットと、新しいしきい値内にあるすべてのソースバケットが加算され、新しいゼロバケットの個数となります。
スキーマが-53(カスタムバケット)の場合、ゼロバケットは使用されません。
カスタム値
カスタム値のリストは標準スキーマでは使用されません。追加データを格納する必要がある場合に、非標準スキーマによってカスタム方法で使用されます。
現在定義されているスキーマでカスタム値が使用されるのは-53(カスタムバケット)のみです。このセクションの残りの部分では、この特定のケースにおけるカスタム値の使用法をより詳細に説明します。
カスタム値は、カスタムバケットの包括的な上限を表します。それらは昇順にソートされます。カスタムバケット自体は、正のバケットリストと正のスパンリストを使用して格納されますが、カスタム値を介して決定される境界は負になる可能性があります。これらの「正」のバケットの各インデックスは、カスタム値リスト内でのゼロベースの位置を定義します。
排他的下限は、上限値の前のカスタム値によって定義されます。最初のカスタム値(リストのゼロ番目の位置)には前の値がないため、下限は-Infと見なされます。したがって、ゼロインデックスのカスタムバケットは、-Infから最初のカスタム値までのすべての観測値をカウントします。正の観測値のみが期待される一般的なケースでは、ゼロインデックスのカスタムバケットはゼロの上限を持つべきであり、ゼロまたはそれ以下の観測値があったかどうかを明確にマークします。(確かに正の観測値しかない場合、ゼロインデックスのカスタムバケットは個数がなく、明示的に表現されることはありません。唯一のコストは、カスタム値リストの先頭に追加のゼロ要素があることです。)
最後のカスタム値は+Infであってはなりません。最後のカスタム値を超える観測値は、+Infの上限を持つオーバーフローバケットに入ります。このオーバーフローバケットは、カスタム値リストの長さに等しいインデックスで追加されます。
Exemplars
ネイティブヒストグラムサンプルには、ゼロ、1つ、または複数のエグゼンプラーが含まれる場合があります。これらは従来のモデルと同様に機能しますが、リストとして整理されており(複数存在する可能性があるため)、タイムスタンプが必要です。
従来のヒストグラムの一部として公開されるエグゼンプラーは、タイムスタンプがあれば、ネイティブヒストグラムによって使用される場合があります。
観測値の特殊なケース
インストルメンテーションコードでは、ヒストグラムの文脈では意味が限られている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を「1つのスパンしかないProm」と見なすことができます。
Promの*ゼロバケット*はOTelでは*ゼロカウント*と呼ばれます。(Promはゼロバケットの観測値数を格納するフィールドの名前としても*ゼロカウント*を使用します。)どちらも同様に機能し、*ゼロしきい値*の存在も同様です。OTelは、指定がない場合、ゼロのしきい値を想定することに注意してください。
(TODO: OTelの仕様には、「zero_threshold が設定されていないか 0 の場合、このバケットは標準指数式では表現できない値とゼロに丸められた値を格納します。」とあります。これが本当に同じ動作を生み出すのか再確認してください。ゼロ付近に問題がある場合、Promの仕様をより正確にすることができます。OTelがNaNをゼロバケットにカウントする場合、ここに注釈を追加する必要があります。)
OTel指数ヒストグラムは、名前が示すように、標準指数バケッティングスキーマのみをサポートします。したがって、NHCB(または将来の他のバケッティングスキーマを持つネイティブヒストグラム)は、OTel指数ヒストグラムにきれいに変換できません。ただし、従来のOTelヒストグラムへの変換は still possible です。
あらゆる種類のOTelヒストグラムには、ヒストグラムで観測された最小値と最大値のオプションフィールドがあります。これらのフィールドにはPrometheusにおける同等の概念がありません。なぜなら、カウンターヒストグラムは長期間予測不可能にデータを累積し、いつでもスクレイプされる可能性があるため、最小値と最大値を追跡することは不可能であるか、限定的な用途しかないからです。ただし、ネイティブヒストグラムは、任意の期間の最小値と最大値の観測値をかなり正確に推定できることに注意してください。PromQLセクションを参照してください。
公開フォーマット
従来のPrometheusのユースケースにおけるメトリクスの公開は、文字列が支配的です。なぜなら、すべてのメトリクス名、ラベル名、ラベル値は、たとえ後者(float64サンプル値)がより冗長なテキスト形式で表現されているとしても、float64サンプル値よりもはるかに多くのスペースを占めるからです。これが、過去にprotobufベースの公開を放棄することが有利に見えた理由の1つでした。
対照的に、上記のデータモデルに従うネイティブヒストグラムは、はるかに多くの数値データで構成されています。これにより、protobufベースのフォーマットの利点が強調されます。したがって、以前に放棄されたprotobufベースの公開は、ネイティブヒストグラムを効率的に公開およびスクレイプするために復活しました。
従来のPrometheusフォーマット
ネイティブヒストグラムが考案された時点では、OpenMetricsの採用はまだ不足しており、特にOpenMetricsのprotobufバージョンには既知のアプリケーションが全くありませんでした。したがって、最初の取り組みは、ネイティブヒストグラムをサポートするために、従来のPrometheus protobufフォーマットを拡張することでした。(追加の実践的な考慮事項として、Goインストルメンテーションライブラリは依然として従来のprotobuf仕様を内部データモデルとして使用しており、初期開発を簡素化しました。)
従来のPrometheusテキスト形式はネイティブヒストグラムのために拡張されておらず、そのような拡張は計画されていません。(以下の*OpenMetrics*セクションも参照してください。)
protobuf仕様にはproto2とproto3のバージョンがあり、どちらも同じワイヤーフォーマットを作成します
これらのファイルには包括的なコメントがあり、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.
}
// [...]
以下の点に注意してください
- ネイティブヒストグラムと従来のヒストグラムは、同じ
Histogramprotoメッセージによってエンコードされます。つまり、既存のHistogramメッセージにネイティブヒストグラム用のフィールドが追加されました。 - 観測値の合計と数、および
created_timestampのフィールドは、従来のヒストグラムとネイティブヒストグラムで共有され、両方で同じように機能します。 - このフォーマットは元々、従来の浮動小数点ヒストグラムをサポートしていませんでした。ネイティブヒストグラムのためにフォーマットを拡張する際に、従来の浮動小数点ヒストグラムのサポートも副産物として追加されました(フィールド
sample_count_float、cumulative_count_floatを参照)。 BucketフィールドとBucketメッセージは、従来のヒストグラムのバケットに使用されます。同じヒストグラムの従来のバージョンとネイティブバージョンの両方を表すHistogramメッセージを作成することは完全に可能です。パーサーは、いずれかまたは両方のバージョンを選択する自由があります(*スクレイプ設定*セクションも参照してください)。- バケットの個数は、浮動小数点ヒストグラムの場合は絶対数として、整数ヒストグラムの場合は前のバケットからのデルタ(または最初のバケットの場合はゼロからのデルタ)としてエンコードされます。後者はより小さな数になり、protobufは
sint64型のvarintエンコーディングを使用するため、メッセージサイズが小さくなります。 - まだ観測値を受け取っていないネイティブヒストグラムと、バケットが設定されていない従来のヒストグラムは、protobufメッセージとまったく同じように見えます。したがって、ネイティブヒストグラムとして解析されることを意図した
Histogramメッセージには、「no-op span」(つまり、offsetとlengthが0に設定されたBucketSpan)が、繰り返しのpositive_spanフィールドに含まれている必要があります。 - ネイティブヒストグラムのエグゼンプラーは、
Histogramメッセージの繰り返しのExemplarフィールドにいくつでも追加できますが、各エグゼンプラーにはタイムスタンプが必要です。この方法でエグゼンプラーが提供されない場合、パーサーは従来のバケットのエグゼンプラー(BucketメッセージのExemplarフィールドにバケットごとに最大1つのエグゼンプラー)を使用してもかまいません。 - ネイティブヒストグラムのエグゼンプラーの数と分布は、手元のユースケースに適合すべきです。一般に、エグゼンプラーペイロードは、
Histogramメッセージの残りの部分よりも大幅に大きくすべきではなく、エグゼンプラーは異なるバケットに入り、バケット全体にほぼ均等に広がるべきです。(これは、分布のロングテールからエグゼンプラーがめったに得られないため、しばしば最も興味深いエグゼンプラーとなる分布を比例的に表すエグゼンプラー分布よりも一般的に好まれます。) - NHCBに必要なカスタム値の表現はありません。NHCBは直接公開されることはありませんが、従来のヒストグラムとして提示され、取り込み時にNHCBに(戻り)変換されます。(*フェデレーション*セクションを参照してください。)将来、例えばカスタム値も利用する将来のスキーマのために、必要に応じてカスタム値のフィールドを追加する可能性があります。
OpenMetrics
現在(2024-11-03)、OpenMetricsはネイティブヒストグラムをサポートしていません。
OpenMetricsのprotobufバージョンへのサポート追加は、従来のPrometheus protobufフォーマットとの類似性により、比較的簡単です。PR形式の提案がレビュー中です。
OpenMetricsのテキストバージョンへのサポート追加は困難ですが、protobufの生成が不可能である多くの状況があるため、非常に望ましいです。テキストフォーマットは、人間による可読性と機械による効率的な処理(エンコーディング、転送、デコーディング)の間でトレードオフを行う必要があります。作業は進行中です。詳細は設計ドキュメントを参照してください。
(TODO: 進捗に応じてセクションを更新してください。)
インストルメンテーションライブラリ
protobuf仕様は、protobufコンパイラによって作成された言語固有のバインディングを使用して、ネイティブヒストグラムを含むメトリクス公開の低レベル作成を可能にします。しかし、直接的なコードインストルメンテーションには、インストルメンテーションライブラリが必要です。
現在(2024-11-03)、ネイティブヒストグラムをサポートする2つの公式Prometheusインストルメンテーションライブラリがあります
他のインストルメンテーションライブラリにネイティブヒストグラムサポートを追加することは、ライブラリがすでにprotobuf公開をサポートしている場合は比較的簡単です。純粋にテキストベースのライブラリの場合、*テキストベースの公開フォーマット*の完成が前提条件となります。(TODO: 必要に応じて更新してください。)
このセクションでは、個々のインストルメンテーションライブラリの使用方法の詳細については触れません(それについては上記でリンクされたドキュメントを参照してください)。共通のユースケースに焦点を当て、インストルメンテーションライブラリの一部としてネイティブヒストグラムサポートを実装するための一般的なガイドラインも提供します。例として、既存のGo実装を使用します。*データモデル*および*公開フォーマット*に関するセクションは、インストルメンテーションライブラリの実装にとって非常に重要です(ただし、このセクションでは繰り返しません!)。
ヒストグラムの実際のインストルメンテーションAPIは、ネイティブヒストグラムでは変更されません。従来のヒストグラムとネイティブヒストグラムの両方が同じ方法で観測値を受け取ります(エグゼンプラーに関する微妙な違いがあります。次の段落を参照)。インストルメンテーションライブラリは、同じヒストグラムの従来のバージョンとネイティブバージョンの両方を維持し、並列で公開することもできます。これにより、スクレイパーはどちらのバージョンを摂取するかを選択できます(*公開フォーマット*セクションを参照)。ユーザーは、設定設定を通じて、従来のヒストグラムおよび/またはネイティブヒストグラムを公開するかどうかを選択します。
従来のヒストグラムのエグゼンプラーは、通常、各バケットの最新のエグゼンプラーを格納および公開することで追跡されます。従来のバケットが定義されている限り、インストルメンテーションライブラリは、各エグゼンプラーにタイムスタンプがあれば、同じヒストグラムのネイティブバージョンにも同じエグゼンプラーを公開してもかまいません。(実際、スクレイパーは、ネイティブバージョンのみを摂取している場合でも、ヒストグラムの従来のバージョンで提供されるエグゼンプラーを使用してもかまいません。詳細は*公開フォーマット*セクションを参照してください。)ただし、ネイティブヒストグラムには任意の数のエグゼンプラーを割り当てることができ、インストルメンテーションライブラリは、*公開フォーマット*セクションで説明されているエグゼンプラーのベストプラクティスを満たすために、この自由を使用すべきです。
インストルメンテーションライブラリは、標準スキーマに従うネイティブヒストグラムに対して、以下の設定パラメータを提供すべきです。名前はGoライブラリの例です。他の言語では、idiomaticなスタイルに合わせて調整する必要があります。括弧内の値は、ライブラリが提供すべきデフォルト値です。
NativeHistogramBucketFactor(1.1): 1より大きい浮動小数点数で、初期解像度を決定します。ライブラリは、バケット幅が提供された値を超えないように成長する開始スキーマを選択します。例の値については、以下の表を参照してください。NativeHistogramZeroThreshold(2-128): 0以上の浮動小数点数で、ゼロバケットの初期しきい値を設定します。
解像度は、スキーマ番号の背後にある数学をほとんどのユーザーが知らないため、直接スキーマを提供するのではなく、成長係数によって設定されます。バケットごとの成長係数に上限があるという概念は、ネイティブヒストグラムの内部動作を知らなくても理解できます。以下の表は、有効な各スキーマの例の係数を示しています。
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セクションを参照)。1時間のNativeHistogramMinResetDurationは、ほとんどの状況でうまく機能する値です。 - 最終リセットから十分な時間が経過していない場合(または
NativeHistogramMinResetDurationがゼロに設定されている場合、デフォルト値)、リセットは実行されません。代わりに、ゼロに近いバケットをゼロバケットにマージするためにゼロしきい値が増加され、これによりバケット数が減少します。しきい値の増加はNativeHistogramMaxZeroThresholdによって制限されます。この値がすでに到達している場合(またはゼロに設定されている場合、デフォルト)、このステップでは何も起こりません。 - バケット数がまだ制限を超えている場合、解像度は次の低いスキーマに変換されることによって(つまり、隣接するバケットをマージすることによって)減少します。これは、バケット数が構成された制限内になるか、スキーマ-4に到達するまで繰り返されます。
ステップ2または3がヒストグラムを変更した場合、最終リセットからNativeHistogramMinResetDurationが経過すると、リセットが実行され、バケットを削除するだけでなく、ゼロしきい値とバケット解像度の初期値に戻します。これは、*作成タイムスタンプ*の更新を含む、他の理由でのリセットと同様に扱われることに注意してください。
非常に低いNativeHistogramBucketFactor(例:1.005)と妥当なNativeHistogramMaxBucketNumber(例:160)を組み合わせることは魅力的です。これにより、各ヒストグラムは常に、指定されたバケット数「予算」内で可能な限り高い解像度を持ちます。(これはOTel指数ヒストグラムのデフォルト戦略です。さらに高いスキーマ(20)から開始しますが、これは現在Prometheusネイティブヒストグラムでは利用できません。)しかし、この戦略は、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が最優先になります。
この設定は、ネイティブヒストグラムを取り込まずにprotobufスクレイプを構成するため、または--enable-feature=native-histogramsフラグが設定されていても特定のターゲットに対して非protobufフォーマットを強制するために使用できます。従来のPrometheus protobufフォーマット(設定リストのPrometheusProto)がネイティブヒストグラムをサポートする唯一のフォーマットである限り、ネイティブヒストグラムを実際に取り込むには、機能フラグとprotobufのネゴシエーションの両方が必要です。
(TODO: ネイティブヒストグラムが安定した機能になったか、他のフォーマットでサポートされるようになったら、このセクションを更新してください。)
注テキストベースとprotobufベースの公開フォーマットを切り替えることには、明白でない影響があります。最も重要なのは、一部の実装詳細により、テキストベースのフォーマットでのスクレイピングは、protobufベースのフォーマットでのスクレイピングよりも一般的にリソース消費がはるかに少ないという直感に反する効果が生じます(詳細は追跡課題を参照)。さらに微妙なのは、quantileラベル(サマリーで使用)とleラベル(従来のヒストグラムで使用)のフォーマットへの影響です。この問題はv2のPrometheusサーバーにのみ影響します(v3ではすべての状況で一貫したフォーマットになります)。ネイティブヒストグラムの有効化がprotobuf公開フォーマットを必要とするため、ネイティブヒストグラムとは直接関係ありませんが、同じコンテキストで表示される可能性があります。詳細はv2.55のnative_histograms機能フラグのドキュメントを参照してください。
バケット数と解像度の制限
インストルメンテーションライブラリは、ネイティブヒストグラムの解像度とバケット数を制限するための設定オプションを提供すべきですが、取り込み時にそれらの制限を強制する必要は依然としてあります。ユーザーは、指定されたプログラムのインストルメンテーションを変更できない場合や、プログラムが意図的に高解像度ヒストグラムでインストルメントされ、さまざまなスクレイパーが希望に応じて解像度を下げられるようにする場合があるかもしれません。
Prometheusスクレイプ設定は、このニーズに対応するために2つの設定を提供します
native_histogram_bucket_limitは、個々のヒストグラムのバケット数の上限を設定します。制限を超過した場合、標準スキーマを持つヒストグラムの解像度は、制限に達するまで繰り返し(バケット幅を倍増させること、つまりスキーマを減らすこと)縮小されます。NHCBが制限を超過した場合、またはまれに制限がスキーマ-4でも満たせない場合、スクレイプは失敗します。native_histogram_min_bucket_factorは、バケットごとの成長係数の下限を設定します。この設定は標準スキーマにのみ関連し、NHCBには影響しません。再び、制限を超過した場合、解像度は制限に達するまで(バケット幅を倍増させること、つまりスキーマを減らすこと)繰り返し縮小されます。ただし、スキーマ-4に到達すると、たとえより高い成長係数が指定されていても、スクレイプは成功します。
両方の設定でゼロが有効な値として受け入れられます。これは「制限なし」を意味します。バケット制限の場合、バケット数は実際にはチェックされません。バケット係数の場合、Prometheusは標準スキーマが使用中のストレージバックエンドの機能を上限を超えないようにすることを保証します。Prometheusは現在、最大8の標準指数スキーマを持つヒストグラムを格納します。しかし、それは8より大きい指数スキーマを*予約された制限52*まで受け入れますが、取り込み時に解像度を縮小してスキーマ8に達するようにします(またはnative_histogram_bucket_limitまたはnative_histogram_min_bucket_factor設定によって必要とされる場合はそれより低いスキーマ)。
両方の設定にゼロ以外の値がある場合、両方の制限を満たすのに十分な程度にスキーマが減少します。
インストルメンテーション時のバケット係数(公開バケット成長係数≤設定値)は上限であり、スクレイプ設定のバケット係数は下限(取り込みバケット成長係数≥設定値)であることに注意してください。したがって、特定の制限から生じるスキーマはわずかに異なります。いくつかの例
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に設定されている場合、各ヒストグラムの両方のバージョンがスクレイプされ、取り込まれます。ただし、これは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ネイティブヒストグラム開発者ガイドも役立つリソースです。
整数ヒストグラム vs 浮動小数点ヒストグラム
TSDBは整数ヒストグラムと浮動小数点ヒストグラムを異なる方法で格納します。一般的に、整数ヒストグラムはより圧縮しやすいと予想されるため、TSDB実装は、すべてのバケット数と観測値の数がint64範囲内の整数値を持つ場合、浮動小数点ヒストグラムを整数ヒストグラムとして格納してもかまいません。これにより、整数ヒストグラムへの変換は元の浮動小数点ヒストグラムの数値的に正確な表現を作成します。(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は、ラベルのみによってタイムシリーズを識別します。シリーズ内のサンプルが浮動小数点数(したがってカウンターまたはゲージ)であるか、ヒストグラム(どのような種類であっても)であるかは、シリーズのアイデンティティに寄与しません。したがって、シリーズは、さまざまなタイプと種類のサンプルの混合を含む場合があります。タイムシリーズ内のサンプルタイプの変更は、実際には非常にまれであると予想されます。これらは通常、インストルメンテーションの変更後(たとえば、変更前にゲージ浮動小数点数であった同じメトリクス名が、変更後にカウンターヒストグラムとして使用されるまれなケース)または記録ルールの変更後(たとえば、ルールの古いバージョンがゲージ浮動小数点数を作成し、新しいバージョンが名前を保持しながらゲージヒストグラムを作成する場合)に発生します。サンプルタイプの頻繁な変更は、通常、設定ミス(たとえば、2つの異なる記録ルールが同じシリーズにフィードする異なるサンプルタイプを作成する)の結果です。したがって、TSDB実装は、サンプルタイプの変更を処理しなければなりませんが、比較的非効率的な方法で処理してもかまいません。Prometheus TSDBが現在使用中のチャンクに書き込めないサンプルタイプに遭遇すると、そのチャンクを閉じ、適切なエンコーディングで新しいチャンクを開始します。(サンプルごとにサンプルタイプが切り替わるタイムシリーズは、サンプルごとに新しいチャンクを生成するため、これは非常に非効率的です。)
ヒストグラムチャンクは、一般的な値をあまり一般的でない値よりも少ないビット数でエンコードすることでデータサイズを削減するために、数値に対していくつかのカスタムエンコーディングを使用します。各カスタムエンコーディングの詳細は、低レベルチャンクフォーマットのドキュメント(そして最終的にはそこからリンクされているコード)に記載されています。以下の3つのエンコーディングは、多くの異なるフィールドで使用されるため、後で参照できるようにここに名前を付けます。
- varbit-int は、符号付き整数用の可変ビット幅エンコーディングです。1ビットから9バイトを使用します。ゼロに近い数値ほど少ないビット数で済みます。これは、floatサンプルのチャンク内のタイムスタンプエンコーディングに似ていますが、ネイティブヒストグラムで一般的に見られる値の分布に最適化された、さまざまなビット長で異なるバックケット化が行われています。
- varbit-uint は同様のエンコーディングですが、符号なし整数用です。
- varbit-xor は、floatのシーケンス用の可変ビット幅エンコーディングです。シーケンス内の現在のfloat値と前のfloat値のXORを取ることに基づいています。floatあたり1ビットから77ビットを使用します。これは、TSDBがfloatサンプルですでに使用しているエンコーディングとまったく同じです。
ヒストグラムチャンクは、通常どおり、チャンク内のサンプル数(uint16として)で始まり、その後にヒストグラムがゲージヒストグラムかカウンターヒストグラムかを示す1バイトと、後者のカウンターリセット情報が続きます。詳細は、以下の対応するセクションを参照してください。その後、チャンクレイアウトと呼ばれるものが続きます。これは、チャンク内のすべてのヒストグラムで共有される以下の情報を含みます。
- ゼロバケットのしきい値。一般的な値(ゼロまたは特定の2のべき乗)を1バイトでエンコードし、任意の値を9バイトでエンコードするカスタムエンコーディングを使用します。
- スキーマ、varbit-intとしてエンコードされます。
- 正のスパ��。スパ��数(varbit-uint)、その後の各スパ��の長さ(varbit-uint)とオフセット(varbit-int)を繰り返しのシーケンスとしてエンコードします。
- 負のスパ��も同様です。
- スキーマ-53(NHCB)のみ、カスタム値。カスタム値の数(varbit-uint)を繰り返し、カスタム値をカスタムエンコーディングを使用した繰り返しのシーケンスとしてエンコードします。
チャンクレイアウトの後には、サンプルデータの繰り返しのシーケンスが続きます。サンプルデータは、整数ヒストグラムとfloatヒストグラムで異なります。整数ヒストグラムの場合、各サンプルのデータは以下を含みます。
- タイムスタンプ。最初のサンプルでは絶対値、2番目のサンプルでは1番目と2番目のサンプルの差、それ以降のサンプルでは「差の差」としてvarbit-intでエンコードされます(つまり、従来のfloatチャンクのタイムスタンプと同じ「ダブルデルタ」エンコーディングですが、varbit-intエンコーディングではビットのバックケット化が異なります)。
- 観測数。最初のサンプルはvarbit-uint、それ以降のサンプルはvarbit-intとしてエンコードされ、タイムスタンプと同じ「差の差」アプローチを使用します。
- ゼロバケットの人口。最初のサンプルはvarbit-uint、それ以降のサンプルはvarbit-intとしてエンコードされ、タイムスタンプと同じ「差の差」アプローチを使用します。
- 観測の合計。最初のサンプルはfloat64、それ以降のサンプルはvarbit-xorとしてエンコードされます(現在のサンプルと前のサンプルのXOR)。
- 正のバケットのバケット人口。各バケットは前のバケットからの差分として(または最初のバケットでは絶対人口として)varbit-intでエンコードされ、タイムスタンプと同じ「差の差」アプローチを使用します。(言い換えれば、「ダブルデルタ」エンコーディングは、それ自体が差分である値に適用されるため、これは「トリプルデルタ」エンコーディングと呼ばれることもあります。)
- 負のバケットのバケット人口も同様です。
floatヒストグラムのサンプルデータは以下の点で異なります。
- 観測数とゼロバケットの人口はfloatになり、したがって観測の合計と同じ方法でエンコードされます(最初のサンプルはfloat64、それ以降のサンプルはvarbit-xor)。
- バケット人口はfloatになるだけでなく、バケット間の差分ではなく絶対人口数になります。最初のサンプルでは、すべてのバケット人口はプレーンなfloat64として表されますが、それ以降のサンプルではvarbit-xorとしてエンコードされ、現在のサンプルと前のサンプルの対応するバケットがXORされます。
次のイベントが新しいチャンクのカットを引き起こします(括弧内の理由は、後述のカウンターリセットに関する考慮事項を参照)。
- 整数ヒストグラムとfloatヒストグラム間のサンプルタイプの変更(両方とも根本的に異なるチャンクエンコーディングを必要とするため)。
- ゲージヒストグラムとカウンターヒストグラム間のサンプルタイプの変更(先頭バイトが異なるタイプを示す必要があるため)。
- カウンターヒストグラムのカウンターリセット(先頭バイトにカウンターリセット情報として格納される必要があります。詳細は後述)。
- スキーマの変更(新しいチャンクレイアウトが必要であり、1つのチャンクは1つのチャンクレイアウトしか持てないため)。
- ゼロしきい値の変更(チャンクレイアウトを変更するため、上記参照)。
- カスタム値の変更(チャンクレイアウトを変更するため、上記参照)。
- ステールネスマーカーの後に通常のサンプルが続く場合(新しいチャンクを厳密に必要とするわけではありませんが、ほとんどのヒストグラムは消えて戻ってくると大きく変化すると想定できるため、新しいチャンクをカットするのが最善の選択肢となります)。
- チャンクサイズの制限を超えた場合(詳細は後述)。
スパ��の違いもチャンクレイアウトを変更しますが、必要に応じて(明示的に表現された)空のバケットを追加することで調整されるため、チャンク内のすべてのヒストグラムが同じスパ��構造を共有します。バケットが消える場合は単純です。なぜなら、欠落したバケットは、ヒストグラムがチャンクに追加される際に、空のバケットとして新しいヒストグラムに追加されるだけだからです。しかし、以前に人口があったバケットが消えることはカウンターリセット(後述)を構成するため、このケースはカウンターリセットを特徴としないゲージヒストグラムでのみ発生する可能性があります。より一般的なケースは、新しく追加されたヒストグラムに、以前に追加されたヒストグラムには存在しなかったバケットが存在する場合です。この場合、これらのバケットは、すべての以前に追加されたヒストグラムに明示的に空のバケットとして追加する必要があります。これには、チャンク全体の完全な再エンコードが必要です。(影響を受ける部分のみを再エンコードする最適化の可能性はありますが、実装は非常に複雑になります。これまでのところ、完全な再エンコードのパフォーマンスへの影響は問題となっていません。)
ステールネスマーカー
注次のセクションを理解するには、TSDBでステールネスマーカーがどのように機能するかを思い出すことが重要です。floatシリーズのステールネスマーカーは、NaN値を表すために使用できる多くのビットパターンの中で、特定のビットパターンで表されます。この非常に特定のfloat値は、以下のセクションで「特別なステールNaN値」と呼ばれます。これは、通常の算術float操作からは(ほとんど確実に)返されず、観測値の特殊ケースで議論されているものを含む「自然発生する」NaN値とは異なります。実際、特別なステールNaN値は、TSDBをクエリしたときに直接返されることはありませんが、呼び出し元に到達する前に内部的に処理されます。
ヒストグラムシリーズのステールネスをマークするために、通常の特別なステールNaN値を使用できます。しかし、これはシリーズをステールとしてマークするためだけに新しいチャンクをカットする必要があることを意味します。なぜなら、ヒストグラム値の後に続くfloat値は別のチャンクに保存する必要があるからです(上記参照)。したがって、ステールマーカーのヒストグラムバージョンもあり、観測の合計フィールドが特別なステールNaN値に設定されています。この場合、他のすべてのフィールドは無視されるため、効率的なストレージに適した値に設定することができます(ステールマーカーのヒストグラムバージョンは、本質的にストレージ最適化にすぎません)。これは、floatヒストグラムと整数ヒストグラムの両方で機能します(整数ヒストグラムでも合計フィールドはfloat値であるため)、そして適切なバージョンを使用して新しいチャンクのカットを回避できます。ステールマーカーのすべてのバージョン(float、整数ヒストグラム、floatヒストグラム)は、TSDBによって同等と見なされなければなりません。
チャンクサイズ制限
floatチャンクのサイズは1024バイトに制限されています。ヒストグラムチャンクでも一般的に同じサイズ制限が使用されます。しかし、個々のヒストグラムは多数のバケットを持つと非常に大きくなる可能性があるため、盲目的にサイズ制限を強制すると、チャンクあたりのヒストグラムが非常に少なくなる可能性があります。(最も極端なケースでは、単一のヒストグラムが1024バイトを超える場合もあり、サイズ制限をまったく強制できない可能性があります。)チャンクあたりのヒストグラムが非常に少ないと、圧縮率が悪化します。したがって、1024バイトのサイズ制限が適用される前に、チャンクあたり最低10個のヒストグラムに達する必要があります。これは、ヒストグラムチャンクが1024バイトよりもはるかに大きくなる可能性があることを意味します。
チャンクあたり最低10個のヒストグラムを必要とするのは、初期の非常に単純なアプローチであり、将来的にチャンクサイズと圧縮率のより良いトレードオフを見つけるために改善される可能性があります。
カウンターリセットに関する考慮事項
一般的に、Prometheusはカウンターが次のサンプルへの値の低下時にリセットされたと見なします(ただし、作成タイムスタンプに関する次のセクションも参照してください)。2つのヒストグラムサンプル間でカウンターリセットを検出する場合、状況はより複雑になります。
まず、ゲージヒストグラムとカウンターヒストグラムは明確に異なります(Prometheusは一般的に、ゲージまたはカウンターメトリックとして取り込まれたかどうかにかかわらず、すべてのfloatサンプルを取り込み後に平等に扱いますが)。カウンターリセットはゲージヒストグラムには適用されません。
ゲージヒストグラムの後にタイムシリーズでカウンターヒストグラムが続く場合、ゲージからカウンターへの変更は、ゲージが削除されカウンターがゼロから新規作成されたのと同等と見なされるため、カウンターリセットが発生したと想定されます。
最も一般的なケースは、カウンターヒストグラムの後に別のカウンターヒストグラムが続く場合です。この場合、以下の手順で可能なカウンターリセットが検出されます。
2つのヒストグラムがスキーマまたはゼロバケット幅で異なる場合、これらの変更は互換性のある解像度削減の一部である可能性があります(ヒストグラムのバケット数を制限するために定期的に行われます)。互換性のある解像度削減では、以下の両方が真となります。
- スキーマが変更された場合、その番号は標準指数スキーマから別の標準スキーマに減少した。
- ゼロバケット幅が変更された場合、最初のヒストグラムの人口のある通常のバケットは、2番目のヒストグラムのゼロバケットに完全に含まれるか、まったく含まれない(つまり、古い通常のバケットと新しいゼロバケットの間に部分的な重複はない)。
いずれかの条件が満たされない場合、その変更は互換性のある解像度削減ではありません。このような変更は、ヒストグラムをリセットまたは新規作成することによってのみ可能であるため、カウンターリセットと見なされ、検出手順は終了します。
両方の条件が満たされた場合、最初のヒストグラムは、そのスキーマとゼロバケット幅が2番目のヒストグラムと一致するように変換する必要があります。これは、以前に説明したのと同じ方法で行われます。隣接するバケットがマージされてスキーマが削減され、通常のバケットがゼロバケットとマージされてゼロバケットの幅が拡大されます。
この手順のこの時点では、両方のヒストグラムは同じスキーマとゼロバケット幅を持っています。これは最初からそうであったか、または最初のヒストグラムがそれに従って変換されたためです。(NHCBはゼロバケットを使用しないことに注意してください。この手順のために、それらのゼロバケット幅と人口は同等と見なされます。)この状況では、以下のいずれかがカウンターリセットを構成します。
- 観測数の減少(ただし、観測の合計の減少ではありません)。
- ゼロバケットを含む、任意のバケットの人口数の減少。これには、人口があったバケットが消えるケースも含まれます。なぜなら、表現されていないバケットは人口ゼロのバケットと同等だからです。
- カスタム値の変更。これは、カスタム値を使用するスキーマ(現在スキーマ-53、すなわちNHCB)にのみ適用されます。(TODO:原理的には、NHCBでも互換性のあるバケット変更の概念が存在する可能性がありますが、そのような概念はまだ実装されていません。)
上記のいずれも該当しない場合、カウンターリセットはありません。
この全体の手順は比較的複雑であるため、カウンターリセット検出は取り込み中に一度行うことが望ましく、結果は後で使用するために永続化されます。カウンターリセット検出は、カウンターリセットが新しいチャンクをカットするトリガーの1つであるため、 anyway、取り込み中に発生する必要があります。
カウンターリセット後に新しいチャンクをカットすることは、圧縮率の向上を目的としています。カウンターリセットはすべてのバケット人口をゼロに設定するため、表現すべきバケットが少なくなります。しかし、チャンクはチャンク内のすべてのヒストグラムのすべてのバケットのスーパーセットを表現する必要があるため、新しいチャンクをカットすることで、新しいチャンクのためのより単純なバケットセットが可能になります。
これは、チャンクの最初のサンプル以降にはカウンターリセットが発生しないことを意味します。したがって、永続化する必要があるカウンターリセット情報は、チャンクの最初のヒストグラムのものだけです。これは、チャンクのサンプル数の直後に格納される、いわゆるヒストグラムフラグに行われます。このバイトは現在、カウンターリセット情報にのみ使用されますが、将来他のフラグに使用される可能性があります。カウンターリセット情報は最初の2ビットを使用します。4つの可能なビットパターンは、`chunkenc`パッケージの`CounterResetHeader`型のGo定数で表されます。それらの名前と意味は次のとおりです。
GaugeType(ビットパターン11):チャンクにはゲージヒストグラムが含まれます。カウンターリセットはゲージヒストグラムには無関係です。CounterReset(ビットパターン10):前のチャンクの最後のヒストグラムとこのチャンクの最初のヒストグラムの間にカウンターリセットが発生しました。(カウンターリセットが新しいチャンクがカットされた理由である可能性が高いです。)NotCounterReset(ビットパターン01):前のチャンクの最後のヒストグラムとこのチャンクの最初のヒストグラムの間にカウンターリセットは発生しませんでした。(これは、前のチャンクがサイズ制限に達したために新しいチャンクがカットされた場合に一般的に発生します。)UnknownCounterReset(ビットパターン00):前のチャンクの最後のヒストグラムとこのチャンクの最初のヒストグラムの間にカウンターリセットがあったかどうかは不明です。
UnknownCounterResetは常に安全な選択です。カウンターリセット検出を妨げることはありませんが、カウンターリセット情報が必要な場合にカウンターリセット検出手順を(再度)実行する必要があるだけです。
カウンターリセット情報は、TSDBをクエリするときに呼び出し元に伝播されます(Goコードでは、Go型`Histogram`と`FloatHistogram`の`CounterResetHint`型のフィールドとして、上記のビットパターン定数と同じ名前の列挙定数を使用します)。
ゲージヒストグラムの場合、CounterResetHintは常にGaugeTypeです。他のCounterResetHint値は、問題のヒストグラムがカウンターヒストグラムであることを意味します。このように、クエリ(PromQLエンジンを含む、後述を参照)は、ヒストグラムがゲージかカウンターか(これは、floatサンプルとは著しく異なる点です)という情報を取得します。
カウンターヒストグラムが単一のチャンクから順序どおりに返される限り、チャンク内の2番目以降のヒストグラムのCounterResetHintはNotCounterResetに設定されます。(重複ブロックや順序外の取り込みは、複数のチャンクからヒストグラムシーケンスが発生する可能性があり、特別な処理が必要です。後述。)
カウンターヒストグラムチャンクから最初のヒストグラムを返す場合、CounterResetHintはUnknownCounterResetに設定されなければなりません。ただし、TSDB実装が、以前に返されたヒストグラムが、取り込み時にカウンターリセットを検出するために使用された先行ヒストグラムと実際��に同じであったことを保証できる場合は除きます。後者の場合のみ、チャンクからのカウンターリセット情報を返されたヒストグラムのCounterResetHintとして直接使用できます。
この予防策は、チャンクが削除または挿入されるさまざまな方法(例:トゥームストーンによる削除、バックフィル用のブロックの追加)があるため必要です。カウンターリセットは、1つのサンプルに帰属しますが、実際にはマークされたサンプルと先行サンプルの間で発生します。先行サンプルを削除するか、2つのサンプルの間に別のサンプルを挿入すると、以前に行われたカウンターリセット検出が無効になります。
TODO現在、Prometheus TSDBは、先行チャンクが取り込み時と同じチャンクであることを保証する手段を持っていません。そのため、Prometheusは現在、カウンターヒストグラムチャンクのすべての最初のヒストグラムに対してUnknownCounterResetを返します。それを変更するための取り組みについては、追跡Issueを参照してください。
すでに上記で示唆されているように、クエリはCounterResetHintがUnknownCounterResetに設定されている場合、カウンターリセット検出手順を(再度)実行しなければなりません。
重複ブロックまたは順序外のサンプル(クエリまたはコンパクション中)を処理する際には、特別な注意が必要です。これらのケースでは、カウンターリセットの過剰検出と過小検出の両方が発生する可能性があります。以下に例を示します。
- 過小検出の例:あるチャンクにはカウンターリセットのないサンプルABCが含まれています。別のチャンクにはカウンターリセットのないサンプルDEFが含まれています。チャンクは重複しており、同じシリーズを参照しています。それらを一緒にクエリすると、サンプルの時間順序はADBECFになります。これらのサンプルのいくつ��、あるいはすべて��間にカウンターリセットが発生している可能性があります。実際、2つのサンプルが実際には無関係なシリーズであり、誤って同じシリーズにマージされた場合、これは可能性が高いです。しかし、このような誤ったマージでさえ、TSDBによって正しく処理されなければなりません。重複チャンクが新しいチャンクにコンパクションされる場合、新しいカウンターリセット検出が発生し、新しいカウンターリセットをキャッチする必要があります。重複チャンクを直接クエリする場合(事前のコンパクションなし)、以前に返されたサンプルとは異なるチャンクから来た各サンプルに対して
UnknownCounterResetのCounterResetHintを設定する必要があり、これはクエリ���よるカウンターリセット検出を強制します(上記の安全なフォールバックを使用)。 - 過剰検出の例:サンプルABCDのシーケンスがあり、BとCの間にカウンターリセットが発生します。しかし、初期取り込みはBとCをスキップしたため、AとDのみが取り込まれ、AとDの間にカウンターリセットが検出されました。後で、BとCが取り込まれ(順序外取り込みまたは後でTSDBに別のブロックとして追加された別個のチャンクとして)、BとCの間にカウンターリセットが検出されました。この場合、各サンプルは独自のチャンクに入るため、すべてのチャンクを組み立てても、それらは重複しません。しかし、上記��ルールに従ってカウンターリセットヒントを返す際、CとDの両方が、実際にはCとDの間にカウンターリセットがないにもかかわらず、
CounterResetのCounterResetHintでクエリ���に返されます。前の例の状況と同様に、AとBの間で新しいカウンターリセット検出が実行されるか、CとDの間で別の検出が実行される必要があります。または、BとDの両方がUnknownCounterResetのCounterResetHintで返される必要があります。
要約すると、TSDBが取り込み時に2つのサンプル間でカウンターリセット検出が発生したことを安全に確立できない場合、別のカウンターリセット検出を実行するか、2番目のサンプルに対してUnknownCounterResetのCounterResetHintを返すかのいずれかを行わなければなりません。
上記の手順では検出されないカウンターリセットが発生する可能性があることに注意してください。具体的には、リセットされたヒストグラムのカウントが十分に速く増加した場合、カウンターリセット前の最後のサンプルと比較してカウントが減少していない最初のサンプルになります。(これはfloatカウンターの問題でもあり、実際により頻繁に発生します。)上記で説明したメカニズムにより、カウンターリセットが他の手段で検出された場合、このケースでもカウンターリセットを保存することができます。しかし、チャンクの挿入と削除、順序外サンプル、および重複ブロック(上記で説明した)に起因する複雑さにより、2回目のカウンターリセット検出が必要な場合、この情報が失われる可能性があります。(TODO:現在、この情報は確実に失われています。上記のTODOを参照してください。)カウンターリセットを安全にマークするより良い方法は、作成タイムスタンプを介することです(次のセクションを参照)。
作成タイムスタンプの処理
OpenMetricsは、カウンター、サマリー、およびクラシックカウンターヒストグラムのために、いわゆる作成タイムスタンプを導入しました。(この用語は「作成日時タイムスタンプ」の略である可能性が高いです。より適切な用語は「作成タイムスタンプ」または「リセットタイムスタンプ」かもしれませんが、「作成タイムスタンプ」という用語はすでに確立されています。)
作成タイムスタンプは、メトリックが最後に作成またはリセットされた日時を提供します。設計ドキュメントは、Prometheusが作成タイムスタンプをどのように処理するかを説明しています。
作成タイムスタンプは、ネイティブヒストグラムにも役立ちます。floatカウンターの合成ゼロサンプルが挿入されるのと同じ方法で、カウンターヒストグラムのためにヒストグラムサンプルのゼロ値が挿入されます。ヒストグラムのゼロ値は、人口のあるバケットがなく、観測の合計、観測数、ゼロバケットの人口はすべてゼロです。スキーマ、ゼロバケット幅、カスタム値、およびヒストグラムのfloatまたはintegerのフレーバーは、合成ゼロサンプルの直後に続くサンプルと一致させるべきです(偽のカウンターリセット検出をトリガーしないように)。
合成ゼロサンプルのカウンターリセット情報は、常にCounterResetに設定されます。
Exemplars
ネイティブヒストグラムのエグザンプラーは、個々のバケットではなく、ヒストグラムサンプル全体に添付されます(エキスポジションフォーマットセクションも参照)。したがって、単一のネイティブヒストグラムサンプルに複数のエグザンプラーが添付されることは許可されています(実際には一般的なケースです)。
エグザンプラーは、スクレイプごとに変更される場合とされない場合があります。スクレーパーは、変更されないエグザンプラーを検出して、多くの重複エグザンプラーを保存しないようにすべきです。しかし、単一のサンプルが多数のエグザンプラーを持ち、それらのサブセットのいずれかが前のスクレイプからの繰り返しエグザンプラーである可能性があるため、重複検出は潜在的に高コストです。TSDBは、新しいエグザンプラーが以前に公開されたエグザンプラーよりも新しいタイムスタンプを持つという仮定に依存してもよいです。(ネイティブヒストグラムのエグザンプラーにはタイムスタンプがなければならないことを覚えておいてください。)重複検出は、効率的な方法で可能になります。
- 新しく取り込まれたネイティブヒストグラムのエグザンプラーは、次のフィールドでソートされます。まずタイムスタンプ、次に値、そしてラベル。
- エグザンプラーはソートされた順序でエグザンプラーストレージに追加されます。
- 最後の正常にアペンドされたエグザンプラー(同じメトリックの前のスクレイプからのものである可能性があります)よりも前にソートされるか、またはそれに等しいエグザンプラーの追加は失敗します。
- 最後の正常にアペンドされたエグザンプラーよりも後にソートされるエグザンプラーの追加は成功します。
エグザンプラーが順序外であると見なされるのは、取り込まれたヒストグラムのすべてのエグザンプラーが、最後に正常にアペンドされたエグザンプラーよりも前にソートされる場合のみです。これは、新しいエグザンプラーや最後に正常にアペンドされたエグザンプラーの重複と混在する順序外のエグザンプラーを検出しないため、許容されると見なされます。
PromQL
このセクションでは、PromQLがネイティブヒストグラムをどのように処理するかを説明します。個々の操作のすべての詳細ではなく、一般的な概念に焦点を当てています。後者については、PromQLの演算子と関数に関するドキュメントを参照してください。
アノテーション
ネイティブヒストグラムの導入により、PromQL式が予期しない結果を返す状況がいくつか発生します。最も一般的なのは、出力ベクトル内の要素のいくつ������はすべてが予期せず欠落している場合です。ユーザーがこれらの状況を検出して理解するのを助けるために、ネイティブヒストグラムに作用する操作はしばしばアノテーションを使用します。アノテーションは警告レベルと情報レベルを持つことができ、評価中に遭遇した可能性のある問題を記述します。警告レベルは、ユーザーが対応する必要がある可能性が最も高い実際の問題を示すために使用されます。情報レベルは、意図的である可能性もあるが、それでもフラグを立てるのに異常に珍しい状況に使用されます。
整数ヒストグラム対floatヒストグラム
PromQLは常にfloatヒストグラムで動作します。整数ヒストグラムとして保存されているネイティブヒストグラムは、TSDBから取得されるときに自動的にfloatヒストグラムに変換されます。
ヒストグラム間の互換性
演算子または関数が2つ以上のネイティブヒストグラムに作用する場合、関連するヒストグラムは同じスキーマとゼロバケット幅を持つ必要があります。一定の範囲内で、ヒストグラムはこれらの互換性基準を満たすようにオンザフライで変換できます。
- NHCB(スキーマ-53)は、まったく同じカスタム値を持つ他のNHCBとしか互換性がありません。(原理的には、調整可能なカスタム値の違いが存在する可能性がありますが、PromQLはまだそれらを考慮していません。)
- 標準スキーマを持つヒストグラムは、より高い解像度を持つヒストグラムの解像度を低下させることによって、最小(つまり、最低解像度)の共通スキーマに常に変換できます。これは、隣接するバケットをより小さいスキーマのより大きなバケットにマージすることによって、通常どおり行われます。
- 異なるゼロバケット幅は、小さなゼロバケットを拡張し、人口のある通常のバケットを拡大されたゼロバケットにマージすることによって処理されます。最大の共通幅が人口のあるバケットの途中に位置する場合、そのバケットの境界に一致するようにさらに拡張されます。(詳細は上記のゼロバケットセクションを参照してください。)
互換性のない場合、操作は実行されず、警告レベルのアノテーションが結果に追加されます。
カウンターリセット
カウンターリセットは、上記で説明されているとおりに定義されます。TSDBから返されるカウンターリセットヒントは、明示的なカウンターリセット検出を回避し、通常のプロセスでは検出できないカウンターリセットを正しく処理するために考慮される場合があります。(これは、これらのカウンターリセットが最善努力ベースでのみ考慮されることを意味します。しかし、TSDB自体についても同様です。上記参照。)クラシックヒストグラムとサマリーのカウンターリセット処理との顕著な違いは、観測の合計の減少はそれ自体ではカウンターリセットを構成しないということです。(例えば、ヒストグラムが負の値を観測した場合でも、ネイティブヒストグラムのレート計算は正しく機能します。)
サブクエリから返されるカウンターヒストグラムのカウンターリセットヒントは、明示的なカウンターリセット検出を回避するために考慮されてはなりません。ただし、PromQLエンジンがサブクエリから返される連続するカウンターヒストグラムがTSDBでも連続していることを安全に検出できる場合は除きます。
ゲージヒストグラム対カウンターヒストグラム
TSDBから返されるカウンターリセットヒントを通じて、PromQLはネイティブヒストグラムがゲージヒストグラムかカウンターヒストグラムかを知ることができます。floatサンプルに対するPromQLの処理(floatカウンターとゲージを信頼��く区別できない)を反映して、カウンターを操作する関数はゲージヒストグラムも処理しますが、その逆も同様ですが、結果には警告レベルのアノテーションが返されます。この場合、明示的なカウンターリセット検出は、ゲージヒストグラムをカウンターヒストグラムとして扱う必要があります。
バケット内の補間
分位数または割合を推定する際、PromQLはバケット内の補間を適用する必要があります。クラシックヒストグラムでは、この補間は線形に行われます。これは、観測値がバケット内で均等に分布しているという仮定に基づいています。実際には、この仮定は大きく外れる可能性があります。(例:APIエンドポイントがほとんどのリクエストに110msで応答する場合。中央値レイテンシ、おそらく90パーセンタイルレイテンシも110ms近くになります。クラシックヒストグラムが100msと200msにバケット境界を持つ場合、その範囲のほとんどの観測値を見て、中央値を150ms、90パーセンタイルを190msと推定します。)最悪の場合、バケットの一方の端での推定で、実際の値がバケットのもう一方の端にある場合です。したがって、最大誤差はバケット全体���幅です。補間を行わず、バケット内の固定中点(例:算術平均または調和平均)を使用すると、最大誤差(算術平均の場合、バケット幅の半分)が最小化されますが、実際には線形補間は平均してより低い誤差をもたらします。クラシックヒストグラムの使用において長年補間がうまくいっているため、ネイティブヒストグラムにも補間が適用されます。
NHCBの場合、PromQLはクラシックヒストグラムと同じ補間方法を適用して、結果の一貫性を保ちます。(NHCBの主なユースケースは、クラシックヒストグラムのドロップイン交換です。)しかし、標準指数スキーマの場合、線形補間は不適合と見なされる可能性があります。指数スキーマは主に分位��推定の相対誤差を最小化することを目的としていますが、少なくとも特定の範囲の観測値においては、バケットのバランスの取れた使用からも恩恵を受けます。基本的な仮定は、ほとんどの現実的に発生する分布では、観測値の密度が小さい観測値で高くなる傾向があるということです。したがって、PromQLは標準スキーマに指数外挿を使用します。これは、スキーマ番号を1つ増やす(つまり、解像度を2倍にする)ときにバケットを2つに分割すると、平均して両方の新しいバケットに同等の人口が見られるという仮定をモデル化します。詳細については、補間方法を実装したPRを参照してください。
ゼロバケット内の補間は特別なケースです。ゼロバケットは指数バケットスキーマを破ります。したがって、ゼロバケット内では線形補間が適用されます。さらに、人口のあるすべての通常のバケットが正の場合、ゼロバケット内のすべての観測値も正であると仮定されます。つまり、補間はゼロとゼロバケットの上限の間で行われます。すべての人口のある通常のバケットが負のヒストグラムの場合、状況は鏡像になり、つまりゼロバケット内の補間はゼロバケットの下限とゼロの間で行われます。
混合シリーズ
すでに上記で議論されたように、ネイティブヒストグラムのサンプルタイプまたはフレーバーは、シリーズのアイデンティティの一部ではありません。したがって、1つの同じシリーズに異なるサンプルタイプとフレーバーの混合が含まれる場合があります。
カウンターヒストグラムとゲージヒストグラムの混合は、PromQL操作を妨げることはありませんが、入力サンプルのいくつか��不適切なフレーバーを持っている場合、結果には警告レベルのアノテーションが返されます(上記を参照)。
floatサンプルとヒストグラムサンプルの混合は、より問題があります。範囲ベクトルの操作の多くは、入力要素にfloatとヒストグラムの混合が含まれている要素を結果から削除します。これが起こる場合、結果には警告レベルのアノテーションが追加されます。具体的な例は、後述で見つけることができます。
単項マイナスと負のヒストグラム
単項マイナスはネイティブヒストグラムに使用できます。すべてのバケット人口、観測数、および観測の合計の符号を反転させたヒストグラムを返します。いずれの場合も、カウンターリセットヒントはGaugeTypeに設定されます。それ以外はすべて同じです。GaugeTypeを強制する必要があるのは、明示的なカウンターリセット検出が反転された符号によって混乱するためです。
一般的に、負のバケット人口または負の観測数を持つヒストグラムは、それ自体ではあまり意味がなく、他の式の中間結果としてのみ機能することを目的としています。それらは常にPromQL内でゲージヒストグラムとして扱われます。記録ルール���結果として永続化することはできません。(負のヒストグラムになるルールはエラーになります。)交換フォーマット(エキスポジションフォーマット、リモート書き込み、OTLP)のいずれでも負のヒストグラムを表現することは不可能です。
二項演算子
ほとんどの二項演算子は、2つのヒストグラム間、またはヒストグラムとfloat間、あるいはヒストグラムとスカラー間では機能しません。演算子がそのような不可能な組み合わせを処理する場合、対応する要素は出力ベクトルから削除され、結果に情報レベルのアノテーションが追加されます。(この状況はラベルマッチングに似ており、サンプルタイプがラベルと同様の役割を果たします。したがって、このような不一致は意図的である可能性があり、アノテーションのレベルが情報のみである理由です。)
以下は、実際に機能するすべての操作を説明します。
加算(+)と減算(-)は、2つの互換性のあるヒストグラム間で機能します。これらの演算子は、すべての対応するバケット人口、観測数、および観測の合計を加算または減算します。欠落したバケットは空であると見なされ、そのように扱われます。一般的に、両方のオペランドはゲージであるべきです。カウンターヒストグラムの加算と減算には注意が必要ですが、PromQLはそれを許可します。ゲージヒストグラムとカウンターヒストグラムの加算は、ゲージヒストグラムを生成します。2つのカウンターヒストグラムの加算は、カウンターヒストグラムを生成します。2つのオペランドが同じカウンターリセットヒントを共有する場合、結果のカウンターヒストグラムはそのカウンターリセットヒントを保持します。それ以外の場合、結果のカウンターリセットヒントはUnknownCounterResetに設定されます。減算の結果は、負のヒストグラムになる可能性があるため、常にゲージヒストグラムとしてマークされます(上記の注記を参照)。直接矛盾するカウンターリセットヒント(すなわち、CounterResetとNotCounterReset)を持つ2つのカウンターヒストグラムの加算または減算は、警告レベルのアノテーションをトリガーします。(TODO:上記で説明したように、TSDBは現在NotCounterResetを返さないため、このアノテーションは、追加のカウンターリセット追跡を含む`HistogramStatsIterator`に関連する特定の状況でのみ発生します。追跡Issueを参照してください。)
乗算(*)は、floatサンプルまたはスカラーとヒストグラムの間で、どちらの順序でも機能します。すべてのバケット人口、観測数、および観測の合計をfloat(サンプルまたはスカラー)で乗算します。これにより、「スケーリングされた」ヒストグラム、さらには負のヒストグラムになる可能性があり、通常は他の式の中間結果としてのみ有用です(上記の注記も参照)。乗算はカウンターヒストグラムとゲージヒストグラムの両方で機能し、それらのフレーバーは操作によって変更されません。
除算(/)は、左辺のヒストグラムと右辺のfloatサンプルまたはスカラーの間で機能します。これは、float(サンプルまたはスカラー)の逆数との乗算と同等です。ゼロ除算は、入力ヒストグラムの値(それぞれ正、負、またはゼロ/NaN)に応じて、+Inf、-Inf、またはNaNに設定された正規バケットがなく、ゼロバケット人口、観測数、および観測の合計を持つヒストグラムを生成します。
等価性(==)と不等価性(!=)は、フィルタリングバージョンとbool修飾子の両方で、2つのヒストグラム間で機能します。それらは、スキーマ、カスタム値、ゼロしきい値、すべてのバケット人口、および観測の合計と数を比較します。ヒストグラムがカウンターまたはゲージのフレーバーであるかは、比較には関係ありません。(カウンターヒストグラムはゲージヒストグラムと等しい場合があります。)
論理/セット二項演算子(and、or、unless)は、ヒストグラムサンプルが関与している場合でも、期待どおりに機能します。それらはベクトルの要素の存在のみをチェックし、要素(floatまたはヒストグラム、カウンターまたはゲージ)のサンプルタイプまたはフレーバーに応じて動作を変更しません。
「トリム」演算子>/と</は、ネイティブヒストグラム専用に導入されました。これらは、左辺のヒストグラムと右辺のfloatサンプルまたはスカラーの間でのみ機能します。(両方のオペランドがfloatサンプルまたはスカラーである場合には機能しません。この場合、情報レベルのアノテーションが返されます。)これらの演算子は、それぞれfloat値よりも大きいまたは小さい観測値をヒストグラムから削除し、結果のヒストグラムを返します。削除は、しきい値がバケット境界と一致する場合にのみ正確です。それ以外の場合、影響を受けるバケット内の補間を使用する必要があります(上記で説明したとおり)。カウンター対ゲージのフレーバーは維持されます。(TODO:これらの演算子はまだ実装されておらず、詳細も変更される可能性があります。追跡Issueを参照してください。)
集計演算子
次の集計演算子は、floatサンプルとヒストグラムサンプルで同じように機能します(理由は括弧内に示されています)。
group(この集計の結果はサンプル値に依存しません。)count(この集計の結果はサンプル値に依存しません。)count_values(GoのFloatHistogram.Stringメソッドによって生成されるテキスト表現が、ヒストグラムの値として使用されます。)limitk(サンプリングされた要素は変更されずに返されます。)limit_ratio(サンプリングされた要素は変更されずに返されます。)
sum集計演算子は、ネイティブヒストグラムに対して、上記の+演算子で説明されているのと同じ方法でヒストグラムを合計することによって機能します。カウンター対ゲージヒストグラムに関する影響も含まれます。(一般的に、この方法で集計されるべきなのはゲージヒストグラムのみです。)avg集計演算子は同様に機能しますが、合計を集計されたヒストグラムの数で割ります(上記の/演算子で説明されているのと同じ方法)。両方の集計演算子は、floatサンプルとヒストグラムサンプルの集計を必要とする要素を出力ベクトルから削除します。そのような削除は、警告レベルのアノテーションによってフラグが立てられます。
上記以外��集計演算子は、ネイティブヒストグラムでは機能しません。入力ベクトル内のヒストグラムは単に無視され、無視された各ヒストグラムに情報レベルのアノテーションが追加されます。
関数
次の関数は、ネイティブヒストグラムの範囲ベクトルに作用し、通常のfloat操作を対応するバケット(ゼロバケットを含む)、および観測の合計と数に個別に適用して、新しいネイティブヒストグラムを生成します。
delta()(ゲージヒストグラムの場合。)increase()(カウンターヒストグラムの場合。)rate()(カウンターヒストグラムの場合。)idelta()(ゲージヒストグラムの場合。)irate()(カウンターヒストグラムの場合。)
これらの関数は、上記のようにゲージヒストグラムまたはカウンターヒストグラムに適用されるべきです。しかし、すべて両方のフレーバーで機能しますが、不適切なフレーバーのヒストグラムが少なくとも1つ範囲ベクトルに含まれている場合、結果には警告レベルのアノテーションが追加されます。
delta()、increase()、およびrate()は、範囲内にfloatサンプルとヒストグラムサンプルの混合を含むシリーズに対しては結果を返しません。idelta()とirate()は、範囲内の最後の2つのサンプルがfloatサンプルとヒストグラムサンプルの混合であるシリーズに対しては結果を返しません。いずれの場合も、これらの理由で欠落した各出力要素に対して警告レベルのアノテーションが追加されます。
これらの関数はすべて、結果としてゲージヒストグラムを返します。
通常どおり、これらの関数は、可能な限り共通のスキーマに変換することによって、異なるスキーマを調整しようとします。しかし、カウンターに適用される関数(increase()、rate()、irate())は、1番目と2番目のサンプルの間にカウンターリセットがある場合、最初のサンプルに対してこの変換を行いません。この場合、最初のサンプルは計算に含まれないため、1番目のサンプルと他のサンプルとの間の互換性のないバケットレイアウトは単純にサイレントに無視されます。
ゼロ未満への外挿を防ぐために、floatカウンターと同じヒューリスティクスが適用されますが、観測数のみに基づいています。したがって、個々のバケットは一部のケースでゼロ未満に外挿される可能性があります。代替案は、カウントもどのバケットもゼロ未満に外挿されない最小の外挿を見つけることでしたが、これは複雑さの著しいコストを伴いながらも、必ずしもより良いヒューリスティクスにつながるわけではありません。最初のサンプルが作成日時タイムスタンプから派生した合成ゼロサンプルである一般的な重要なケースでは、カウントとすべてのバケットが合成サンプルのタイムスタンプ(外挿が制限される時点でもある)で正確にゼロであるため、限定的な外挿は実際には完全に正確に機能します。クラシックヒストグラムは、各バケットとカウント、および合計(すべて別個のシリーズであるため)に独立してヒューリスティクスを適用することに注意してください。これは一貫性のない結果につながることが知られています。NHCBは、この問題��再現せず、他のネイティブヒストグラムと同じように機能します。これは、`rate()`と`increase()`の結果が、クラシックヒストグラムと等価なNHCBを比較した場合にわずかに異なる可能性があることを意味します。
avg_over_time()とsum_over_time()は、ネイティブヒストグラムに対して、対応する集計演算子に相当する方法で機能します。特に、シリーズが範囲内にfloatサンプルとヒストグラムサンプルの混合を含む場合、対応する結果は出力ベクトルから完全に削除されます。そのような削除は、警告レベルのアノテーションによってフラグが立てられます。
changes()関数とresets()関数は、ネイティブヒストグラムサンプルに対して、floatサンプルに対する場合と同じように機能します。それらは、同じシリーズ内にfloatサンプルとヒストグラムサンプルの混合がある場合でも機能します。この場合、floatサンプルからヒストグラムサンプルへの変更、およびその逆は、changes()の変更としてカウントされ、resets()のリセットとしてカウントされます。カウンターヒストグラムからゲージヒストグラムへの、またはその逆へのフレーバーの変更は、changes()の変更としてはカウントされません。resets()はカウンターfloatとカウンターヒストグラムにのみ適用されるべきですが、この関数はゲージヒストグラムでも機能し、この場合は明示的なカウンターリセット検出を適用します。さらに、カウンターヒストグラムからゲージヒストグラムへの、またはその逆への変更はリセットとしてカウントされます。
histogram_quantile()関数は非常に特別な役割を果たします。なぜなら、それはクラシックヒストグラムで使用されるleラベルという特定の「マジック」ラベルを特別に扱う唯一の関数だからです。histogram_quantile()はネイティブヒストグラムに対しても同様に機能しますが、leラベルの特別な役割はありません。この関数はfloatサンプルを既知の方法で引き続き扱い、ネイティブヒストグラムサンプルには新しい「ネイティブ」方法を使用します。
クラシックヒストグラム(`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を返します。histogram_fractionの詳細な説明を参照してください。直感的には、このケースは、NaNはどの数値とも比較できないため、すべての観測値よりも大きい数がないことを意味します。結果がすべてのバケットを超える場合、NaNを返します。これは、NaN観測値が+Infとして観測されたかのように計算された結果を返し、結果がNaNによって歪んでいることをユーザーに知らせるために情報レベルのアノテーションも発行します。これは、クラシックヒストグラムが通常NaN観測値をどのように扱うか(ほとんどの実装で+Infバケットに入る)と一致しています。結果がすべての既存のバケットを超える場合、NaNを返します。histogram_fractionの説明を参照してください。直感的には、このケースは、NaNはどの数値とも比較できないため、すべての観測値よりも大きい数がないことを意味します。また、このケースに特有の情報レベルのアノテーションも返します。
次の関数は、ネイティブヒストグラム専用に導入されました。
histogram_avg()histogram_count()histogram_fraction()histogram_sum()histogram_stddev()histogram_stdvar()
これらの関数はすべて、floatサンプルを無条件に無視します。各関数はfloatサンプルのベクトルを返します。
histogram_count()とhistogram_sum()は、ネイティブヒストグラムに含まれる観測数または観測の合計をそれぞれ返します。それらは通常の関数であるため、その結果を範囲セレクタで使用することはできません。サブクエリを使用する代わりに、観測数または合計のレートを計算する推奨される方法は、まずヒストグラムをレート化し、次にその結果にhistogram_count()またはhistogram_sum()を適用することです。たとえば、次のクエリはネイティブヒストグラムからの観測のレート(この場合は「毎秒リクエスト」に相当)を計算します。
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を返します。
次の関数はサンプル値と直接やり取りしないため、floatサンプルと同様にネイティブヒストグラムサンプルでも機能します。
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()関数については、ネイティブヒストグラムサンプルは入力範囲ベクトルから削除されます。範囲内にfloatサンプルとヒストグラムサンプルの混合を含むシリーズの場合、ヒストグラムの削除は情報レベルのアノテーションによってフラグが立てられます。
記録ルール
記録ルールはネイティブヒストグラム値を生成する場合があります。それらは通常の取り込み時にTSDBに書き戻され、ヒストグラムがゲージヒストグラムかカウンターヒストグラムかどうかも含めて保存されます。後者の場合、カウンターリセットヒントによって明示的にマークされたカウンターリセットも保存されますが、それ以外の場合は新しいカウンターリセット検出が取り込み中に開始されます。
TSDB実装は、記録ルールによって作成されたfloatヒストグラムを、元のヒストグラムのすべてのfloat値を正確に表現できる場合に、整数ヒストグラムに変換する場合があります。
アラートルール
アラートはネイティブヒストグラムでも通常どおり機能します。ただし、アラートの出力値としてネイティブヒストグラムを避けることが推奨されます。ネイティブヒストグラムサンプルがテンプレートで使用される場合、それらは単純なテキスト形式(GoのFloatHistogram.Stringメソッドによって生成される)でレンダリングされ、人間には読みにくいです。
テストフレームワーク
PromQLテストフレームワークは拡張されており、PromQL単体テストとpromtoolによるルール単体テストの両方にネイティブヒストグラムを含めることができるようになっています。ヒストグラムサンプルの表記は複雑であり、ルール単体テストのドキュメントで説明されています。
単体テストフレームワークには、`load`コマンドの代替として`load_with_nhcb`コマンドがあります。これはクラシックヒストグラムをNHCBに変換し、クラシックヒストグラムのfloatシリーズと変換の結果であるNHCBシリーズの両方をロードします。
ネイティブヒストグラムに固有ではありませんが、そのコンテキストで非常に役立つのは、ユニットテストフレームワークのexpectキーワードで、infoレベルとwarnレベルのアノテーションに関する期待を定義できることです。
最適化
例として、PromQL実装では、動作が変わらない限り、必要と思われる最適化を適用することができます。ネイティブヒストグラムのデコードは、バケットの数が多いため、非常にコストがかかる可能性があります。同様に、PromQLエンジンの内部でヒストグラムサンプルをディープコピーすることは、単純なfloatサンプルをコピーするよりもはるかにコストがかかります。これは、すべてをデコードし、すべてをコピーするという単純なアプローチと比較して、最適化の大きな可能性を生み出します。
Prometheusは現在、不要なコピーを避けるように努めています(TODO:ただし、CoWのような適切なアプローチは、よりクリーンでバグが発生しにくいため、まだ実装されていません)。また、観測の合計とカウントのみが必要な特別なケースでは、バケットのデコードをスキップします。
PrometheusクエリAPI
ネイティブヒストグラムのサポートは、クエリAPIドキュメントに含まれています。このセクションでは、ネイティブヒストグラムに関連する部分に焦点を当て、APIドキュメントにはないコンテキストを少し提供します。
インスタントクエリとレンジクエリ
インスタント(queryエンドポイント)およびレンジ(query_rangeエンドポイント)クエリのJSONレスポンスでネイティブヒストグラムを返すには、vectorとmatrixの両方の結果タイプに新しいキーによる拡張が必要です。
vector結果タイプは、既存のvalueキーと同じレベルに新しいキーhistogramを取得します。これらのキーは互いに排他的です。つまり、vectorの各要素は、valueキー(float結果用)またはhistogramキー(ヒストグラム結果用)のいずれかを取得します。histogramキーの値は、valueキーの値(2要素配列)と同様の構造ですが、floatサンプル値を表す文字列が、以下に説明する特定のヒストグラムオブジェクトに置き換えられています。
matrix結果タイプは、既存のvaluesキーと同じレベルに新しいキーhistogramsを取得します。これらのキーは排他的ではありません。シリーズは、float値とヒストグラム値の両方を含むことができますが、指定されたタイムスタンプに対しては、floatまたはヒストグラムのいずれか1つのサンプルのみが存在する必要があります。histogramsキーの値は、valuesキーの値(n個の2要素配列の配列)と同様の構造ですが、floatサンプル値を表す文字列が、以下に説明する特定のヒストグラムオブジェクトに置き換えられています。
キーの命名は、float/histogramおよびfloats/histogramsの方が適切であることに注意してください。なぜなら、float値とヒストグラム値はどちらも値だからです。現在の命名には歴史的な理由があります。(過去にはfloatという値タイプしかなかったため、キーを単純に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エンドポイントでは、ネイティブヒストグラムを含むシリーズは、floatのみを含む従来のシリーズと同じ方法で含まれます。エンドポイントは、どのようなサンプルタイプが含まれているかについての情報を提供しません(実際、どのシリーズも、サンプルタイプのいずれかまたは両方を含む可能性があります)。特に、ターゲットによって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エンドポイントを介した追加のルックアップが必要です。既存のメタデータエンドポイントは anyway severely limited(履歴情報なし、ルールによって作成されたメトリクス用のメタデータなし、異なるターゲット間の競合するメタデータを処理する機能が制限されている)ため、この変更の計画はありません。ただし、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は従来のグラフとして観測の合計のみをプロットします。追跡Issueを参照してください。同じIssueでは、テーブルビューでのレンジベクトルのレンダリングの扱いについても議論されています。
テンプレート展開
ネイティブヒストグラムはテンプレート展開で機能します。開区間と閉区間の数学的記法に触発されたテキスト表現でレンダリングされます(これはGoのFloatHistogram.Stringメソッドによって生成されます)。ネイティブヒストグラムは多くのバケットを持つ可能性があり、バケット境界は多くの小数桁を持つ傾向があるため、この表現は必ずしも読みやすいとは限りません。テンプレート展開でネイティブヒストグラムを使用する場合は慎重に行ってください。
floatヒストグラムのテキスト表現の例
{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経由でのフェデレーションが優先されます。
NHCBは、フェデレーションエンドポイント経由で公開される際に、従来のヒストグラムとしてレンダリングされます。スクレイパーは、それらをNHCBに変換するか、従来のヒストグラムとして取り込むかを選択できます。ただし、後者は名前の競合を引き起こす可能性があります。
TODOOMがNHをサポートしたら更新。
OTLP
Prometheusに組み込まれているOTLPレシーバーは、OTel指数ヒストグラムをPrometheusネイティブヒストグラムに変換し、上記で説明されている互換性を利用します。スキーマ(OTel用語で「scale」)が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 metricspromtool push metricspromtool tsdb dump-openmetricspromtool tsdb create-blocks-from openmetrics
TODO進捗があり次第更新します。追跡Issueを参照してください。
prom2json
prom2jsonは、Prometheusの/metricsエンドポイントをスクレイプし、メトリクスを独自のJSON形式に変換して標準出力にダンプする小さなツールです。これは、例えばjqのようなJSONを処理するツールでさらに処理するのに便利です。
prom2json v1.4でネイティブヒストグラムのサポートが追加されました。提示されたヒストグラムに少なくとも1つのバケットスパンが含まれている場合、prom2jsonは、PrometheusクエリAPIに触発された形式に従って、JSON出力の通常の従来のバケットをネイティブヒストグラムのバケットに置き換えます。
移行に関する考慮事項
従来のヒストグラムからネイティブヒストグラムに移行する際には、考慮すべき3つの重要な問題源があります。
- ネイティブヒストグラムのクエリは、従来のヒストグラムのクエリとは異なります。ほとんどの場合、変更は最小限で単純ですが、信頼性の高い自動変換を困難にするトリッキーなエッジケースがあります。
- 従来のヒストグラムとネイティブヒストグラムは、互いに集計できません。ある時点での従来のヒストグラムからネイティブヒストグラムへの変更は、遷移ポイントをまたぐダッシュボードの作成を困難にし、遷移ポイントを含むレンジベクトルは必然的に不完全になります(つまり、従来のヒストグラムを選択するレンジベクトルは範囲の初期部分のデータポイントのみを含み、ネイティブヒストグラムを選択するレンジベクトルは範囲の後期部分のデータポイントのみを含みます)。
- 従来のヒストグラムは、関心のあるポイントで正確にバケット境界を持つように調整されている場合があります。標準スキーマのネイティブヒストグラムは高解像度を持つことができますが、任意の値をバケット境界として設定することはできません。これらの場合、ネイティブヒストグラムのユーザーエクスペリエンスは実際には悪くなる可能性があります。
(3)に対処するために、問題の従来のヒストグラムを移行せずにそのままにしておくことももちろん可能です。もう1つのオプションは、インストルメンテーションをそのままにし、取り込み時に従来のヒストグラムをNHCBに変換することです。これにより、ネイティブヒストグラムのストレージパフォーマンスの向上を活用できますが、(1)と(2)は、ネイティブヒストグラムへの完全な移行と同じように対処する必要があります(次の段落を参照)。
(1)と(2)に対処するための保守的な方法は、長い移行期間を許可することです。これは、しばらくの間、従来のヒストグラムとネイティブヒストグラムを並行して収集および保存するというコストがかかります。
最初のステップは、インストルメンテーションを更新して、従来のヒストグラムとネイティブヒストグラムを並行して公開することです。(計画が、インストルメンテーションで従来のヒストグラムを維持し、スクレイピング中にNHCBに変換するだけの場合は、このステップをスキップできます。)
次に、Prometheusを構成して、従来のヒストグラムとネイティブヒストグラムの両方をスクレイプします。これは、従来のヒストグラムとネイティブヒストグラムの両方をスクレイプするセクションを参照してください。(必要に応じて、従来のヒストグラムをNHCBとしてスクレイプすることも有効にしてください。)
従来のヒストグラムを含む既存のクエリは引き続き機能しますが、今後は、ユーザーはネイティブヒストグラムを操作し始め、ダッシュボード、アラート、記録ルールなどのクエリを変更できるようになります。すでに上記で述べたように、長いレンジベクトルを持つクエリに注意することが重要です。たとえばhistogram_quantile(0.9, rate(rpc_duration_seconds[1d]))です。このクエリは過去1日間の90パーセンタイルレイテンシを計算します。しかし、ネイティブヒストグラムが少なくとも1日収集されていない場合、クエリはそのより短い期間のみをカバーします。したがって、クエリはネイティブヒストグラムが少なくとも1d収集されてからのみ使用されるべきです。過去1ヶ月間の毎日の90パーセンタイルレイテンシを表示するダッシュボードの場合、正しい瞬間に従来のヒストグラムからネイティブヒストグラムに切り替えるクエリを作成したくなります。これは原則として可能ですが、トリッキーです。可能であれば、従来のヒストグラムとネイティブヒストグラムが並行して収集される移行期間は、トリッキーな切り替えの実装の必要性を最小限に抑えるために、かなり長くすることができます。たとえば、従来のヒストグラムとネイティブヒストグラムが1ヶ月間並行して収集された後、1ヶ月以上先を見ないダッシュボードは、従来のヒストグラムクエリからネイティブヒストグラムクエリに、切り替えポイントを考慮せずに簡単に切り替えることができます。
すべてのクエリが正しく移行されたと確信できたら、Prometheusをネイティブヒストグラムのみをスクレイプするように構成します(これは「通常の」設定です)。(リラベルルールを使用して、スクレイプ設定で徐々に従来のヒストグラムを削除することも可能です。)それでもすべてが機能する場合は、インストルメンテーションから従来のヒストグラムを削除する時期です。
Grafana Mimirのドキュメントには、このセクションで説明されている哲学と同じ哲学に従った詳細な移行ガイドが含まれています。