原生直方圖

原生直方圖於 2022 年 11 月作為實驗性功能引入。它們是一個影響 Prometheus 堆疊幾乎每個部分的_概念_。支援原生直方圖的第一個 Prometheus 伺服器版本是 v2.40.0。必須透過功能標誌 --enable-feature=native-histograms 來啟用支援。從 v3.8.0 開始,原生直方圖作為穩定功能受支援。但是,抓取原生直方圖仍然需要透過 scrape_native_histograms 配置設定顯式啟用。為了方便從功能標誌過渡到配置設定,在 v3.8 中設定功能標誌的唯一剩餘效果是將 scrape_native_histograms 預設設定為 true。從 v3.9 開始,該功能標誌將不再生效,並且需要顯式設定 scrape_native_histograms。透過 Remote-Write 傳送需要透過 send_native_histograms 遠端寫入配置啟用。(從 v4 開始,scrape_native_histogramssend_native_histograms 都將預設設定為 true。)

由於與原生直方圖相關的更改具有普遍性,因此這些更改的文件和底層概念的解釋廣泛分佈在各種渠道(如受影響的 Prometheus 元件文件、原始碼中的文件註釋,有時是原始碼本身、設計文件、會議演講等)。本文件旨在收集所有這些資訊,並在統一的上下文中簡明扼要地呈現它們。本文件傾向於連結現有的詳細文件而不是重複闡述,但它包含足夠的資訊,無需參考其他來源即可理解。綜上所述,需要注意的是,本文件既不適合作為初學者的介紹,也不側重於開發人員的需求。對於前者,計劃提供更新版的關於直方圖和摘要的《最佳實踐》文章。(待辦:以及一篇博文,甚至是一系列博文。)對於後者,有 Carrie Edward 的《Prometheus 原生直方圖開發者指南》 

雖然正式規範應在其各自的上下文中進行(例如,OpenMetrics 更改將在通用的 OpenMetrics 規範中指定),但本文件的某些部分採取了規範的形式。在這些部分中,關鍵詞“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”的用法如 RFC 2119  中所述。

儘管該功能已被認為是穩定的,並且我們預計在 v4.0.0 之前不會有重大更改,但本文件仍然包含許多待辦事項。這些待辦事項是提醒我們完善文件、修復小問題和新增新功能。

簡介

原生直方圖的核心思想是將直方圖作為 Prometheus 資料模型中的一等公民對待。將直方圖提升為“原生”樣本型別是以下列出的關鍵屬性的根本前提,這也解釋了選擇“原生直方圖”這個名稱的原因。

在引入原生直方圖之前,所有 Prometheus 樣本值都是 64 位浮點值(簡稱 float64 或 float)。這些浮點數可以直接表示 gauge 或 counter。Prometheus 指標型別 summary 和(經典版)histogram,在暴露格式中存在時,在攝入時會被分解為浮點元件:兩種型別都有一個 sum 和一個 count 元件,一個 summary 有多個分位數樣本,一個(經典)histogram 有多個桶樣本。

原生直方圖引入了一種新的結構化樣本型別。單個樣本表示先前已知的總和(sum)和計數(count)以及一組動態桶。這不僅限於攝入,PromQL 表示式也可以返回新的樣本型別,而以前只能返回浮點樣本。

原生直方圖具有以下關鍵特性:

  1. 稀疏的桶表示,使得空桶的成本(幾乎)為零。
  2. 覆蓋浮點數 float64 值的完整範圍。
  3. 在埋點過程中無需配置桶邊界。
  4. 根據簡單的配置引數動態選擇解析度。
  5. 複雜的指數分桶模式,確保使用這些模式的所有直方圖之間可以合併。
  6. 高效的資料表示,適用於暴露和儲存。

這些關鍵特性透過標準分桶模式得到了充分實現。還有其他具有不同權衡的模式,它們可能只具備這些特性的一部分。詳情請參見下面的“模式”部分

與先前存在的“經典”直方圖相比,原生直方圖(採用標準分桶模式)在任意觀測值範圍內提供更高的桶解析度,同時儲存和查詢成本更低,且幾乎不需要配置。即使透過標籤對直方圖進行分割槽也變得更加經濟。

由於稀疏表示(上述列表中的特性 1)對於原生直方圖的許多其他優勢至關重要,因此在設計過程早期,“稀疏直方圖”是原生直方圖的常用名稱。然而,其他關鍵特性,如指數分桶模式或桶的動態性質,也同樣非常重要,但“稀疏直方圖”這一術語並未完全涵蓋它們。

設計文件

這些是指導原生直方圖開發的設計文件。一些細節現在已過時,但它們很好地描述了底層概念及其演變過程。

會議演講

瞭解原生直方圖的一種更易懂的方式是觀看會議演講,以下精選了一些。作為入門,觀看這些演講可能很有意義,然後再回到本文件瞭解所有細節和技術要點。

術語表

  • 原生直方圖是本文所述的代表完整直方圖的新複雜樣本型別的一個例項。在上下文足夠清晰的情況下,下文通常僅稱其為直方圖。
  • 經典直方圖是較舊的樣本型別的一個例項,代表具有固定桶的直方圖,以前也簡稱為直方圖。它以這種形式存在於暴露格式中,但在攝入 Prometheus 後會分解為多個浮點樣本。
  • 稀疏直方圖是原生直方圖的一箇舊名稱,現已棄用。這個名稱可能偶爾仍會在舊文件中出現。稀疏桶對於原生直方圖的桶來說仍然是一個有意義的術語。

資料模型

本節概述了原生直方圖的資料模型。它儘可能避免涉及實現細節,包括術語。例如,本節中描述的列表在 protobuf 實現中將成為重複訊息,在 Go 實現中(很可能)成為切片。

通用結構

與經典直方圖類似,原生直方圖有一個用於觀測計數(count)的欄位和一個用於觀測總和(sum)的欄位。雖然觀測計數通常是非負的(PromQL 中的中間結果是唯一的例外),但觀測總和可以是任何 float64 值。

此外,原生直方圖還包含以下元件,這些元件將在下面的專門章節中詳細描述:

  • 一個模式(schema),用於識別確定任何給定索引 i 的桶邊界的方法。
  • 索引桶的稀疏表示,正向和負向觀測均有對應。
  • 一個零桶(zero bucket),用於計數接近零的觀測值。
  • 一個(可能為空的)自定義值列表。
  • 範例.

型別

任何原生直方圖都在兩個獨立維度上具有特定型別:

  1. 計數器(Counter)與儀表盤(Gauge):通常,直方圖是“計數器式”的,即其每個桶都充當觀測值的計數器。然而,也有“儀表盤式”直方圖,其中每個桶都是一個儀表盤,表示某一時刻的任意分佈。儀表盤直方圖的概念之前由 OpenMetrics  為經典直方圖引入。
  2. 整數(Integer)與浮點(Floating point,簡稱 float):直方圖的顯而易見用例是計數觀測值,導致每個桶(包括零桶)內的觀測值為 ≥ 0 的整數,以及觀測總計數表示為無符號 64 位整數(簡稱 uint64)。然而,也存在導致“加權”或“縮放”直方圖的特定用例,其中所有這些值都表示為 64 位浮點數(簡稱 float64)。請注意,在任何一種情況下,觀測值的總和(sum)都是 float64。

浮點直方圖偶爾在直接埋點中用於“加權”觀測,例如計算觀測值落入直方圖不同桶中的秒數。然而,浮點直方圖更常見的用例是在 PromQL 內部。PromQL 通常只處理浮點值,因此 PromQL 引擎首先會將從 TSDB 中檢索到的每個直方圖轉換為浮點直方圖,並透過記錄規則存回 TSDB 的任何直方圖也是浮點直方圖。如果這樣的直方圖實際上是整數直方圖(因為所有非求和欄位的值都可以精確地表示為 uint64),TSDB 實現可以(MAY)將其轉換回整數直方圖以提高儲存效率。(截至 Prometheus v3.00,Prometheus 內部的 TSDB 實現並未利用此選項。)但請注意,應用於計數器直方圖最常見的 PromQL 函式是 rate,它通常產生非整數,因此記錄規則的結果通常仍是具有非整數值的浮點直方圖。

PromQL 表示式甚至可能建立“負”直方圖(例如,透過將直方圖乘以 -1)。這些負直方圖僅允許作為中間結果存在,否則被認為是無效的。它們不能在任何交換格式(暴露格式、遠端寫入、OTLP)中表示,也不能儲存在 TSDB 中。另請參閱關於負直方圖的詳細章節

將原生直方圖明確地視為整數直方圖與浮點直方圖,這與傳統簡單數字樣本的處理方式顯著不同。為了簡化,傳統簡單數字樣本在整個堆疊中始終被視為浮點數。

更復雜地處理直方圖的主要原因是 protobuf 暴露格式能輕鬆獲得效率提升。Protobuf 對整數使用變長編碼(varint encoding),這在不需要額外壓縮層的情況下減小了小整數值的資料大小。整數桶的增量編碼(delta encoding)進一步放大了這一優勢,通常會產生更小的整數值。相比之下,浮點數在 protobuf 中始終需要 8 位元組。實際上,整數直方圖中的許多整數只需 1 位元組即可儲存,大多數只需 2 位元組,因此在 protobuf 暴露格式中顯式存在整數直方圖,對於包含許多桶的直方圖而言,資料大小可直接減少近 8 倍。這一點尤為重要,因為透過埋點目標暴露的絕大多數直方圖都是整數直方圖。

出於類似原因,記憶體和磁碟中整數直方圖的表示通常比浮點直方圖更高效。不過,這比暴露格式中的優勢關聯性較小。一方面,Prometheus 對浮點數使用 Gorilla 風格的 XOR 編碼,這減少了它們的大小,儘管不如整數使用的雙增量編碼效果顯著。更重要的是,實現總是可以決定在內部對實際上是整數值的直方圖欄位使用整數表示(見上文)。(歷史注:Prometheus v1 曾採用這種方法來改進浮點樣本的壓縮,Prometheus v3 將來很可能再次採用這種方法。)

在計數器直方圖中,觀測值的總計數和各個桶中的計數都表現得像 Prometheus 計數器,即它們僅在計數器重置時才下降。然而,觀測值的總和可能會由於觀測到負值而減少。PromQL 實現必須(MUST)根據整個直方圖檢測計數器重置(詳情請參閱下面的“計數器重置注意事項”部分)。(請注意,對於經典直方圖和摘要的 sum 元件,這也一直是一個問題。到目前為止的方法是接受在這種情況下,sum 的計數器重置檢測會默默地失效。幸運的是,負值觀測對於 Prometheus 直方圖和摘要來說是一個非常罕見的用例。)

模式

模式(schema)是一個有符號 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 的最大和最小有限值(下文稱為 MaxFloat64MinFloat64)以及正負無窮大值(+Inf-Inf):

  • 包含 MaxFloat64 的正桶(根據上述邊界公式)具有 MaxFloat64 的上限(包含),而不是根據上述公式計算出的會溢位 float64 的限制。
  • 下一個正桶(相對於上一項中的桶,索引為 i+1)具有 MaxFloat64 的下限(不包含)和 +Inf 的上限(包含)。(它可稱為正溢位桶。)
  • 包含 MinFloat64 的負桶(根據上述邊界公式)具有 MinFloat64 的下限(包含),而不是根據上述公式計算出的會導致 float64 下溢的限制。
  • 下一個負桶(相對於上一項中的桶,索引為 i+1)具有 MinFloat64 的上限(不包含)和 -Inf 的下限(包含)。(它可稱為負溢位桶。)
  • 上述 +Inf-Inf 桶之外的桶禁止(MUST NOT)使用。

對於接近零的值,還有更多例外情況,請參閱下面的“零桶”部分

目前最低解析度為 -4,最高解析度為 8 的限制是根據實際用途選擇的。如果出現對更低或更高解析度的實際需求,將考慮擴充套件範圍。然而,模式大於 52 沒有意義,因為從一個桶到下一個桶的增長因子將小於可表示的 float64 數字之間的差異。同樣,模式小於 -9 也沒有意義,因為增長因子將超過可表示為 float64 的最大浮點數。因此,-9 到 +52 之間(含)的模式數字保留用於未來的標準模式(遵循上述桶邊界公式),並且禁止(MUST NOT)用於任何其他模式。

原生直方圖的接收者可以(MAY)在攝入時透過適當合併桶來降低模式,從而降低攝入直方圖的解析度。如果接收者在攝入時將模式降低到有效數字(即 -4 到 8 之間),則可以(MAY)接受 9 到 52 之間的模式,並遵循上述桶邊界公式。

如果在此可選的模式轉換後,接收者仍然不認識該模式,則有以下選項:

  • 如果一個抓取(包括聯邦)包含一個或多個具有未知模式的直方圖,則整個抓取必須(MUST)失敗,遵循 Prometheus 避免不完整抓取的慣例。
  • 對於任何其他攝入路徑(包括重播 WAL/WBL),接收者可以(MAY)忽略具有未知模式的直方圖,並且應該(SHOULD)以適當的方式通知使用者此遺漏。

當 TSDB 實現從其持久儲存中讀取直方圖時(不包括重播 WAL/WBL),適用類似的指南:9 到 52 之間的模式可以(MAY)轉換為有效模式。否則,未知模式在檢索時必須(MUST)返回錯誤,並且觸發檢索的 PromQL 查詢必須(MUST)失敗。

對於模式 -53,桶邊界透過自定義值顯式設定,這將在下面的“自定義值”部分詳細描述。這會產生一個具有自定義桶邊界的原生直方圖(或簡稱自定義桶,通常進一步縮寫為 NHCB)。這樣的直方圖可以用於將經典直方圖表示為原生直方圖。如果標準模式的指數分桶與直方圖要表示的分佈不匹配,也可以使用它。具有不同自定義桶邊界的直方圖通常不能相互合併。因此,模式 -53 應該(SHOULD)僅在特定用例中作為知情決策使用。

對於標準模式,桶表示為兩個列表,一個用於正桶,一個用於負桶。對於自定義桶(模式 -53),僅使用正桶列表,但將其重新用於所有桶。

任何未填充的桶都可以(MAY)從列表中排除。(這就是這些桶通常被稱為稀疏桶的原因。)

對於浮點直方圖,列表中的元素是 float64,直接表示桶的填充量。桶的填充量通常是非負的,PromQL 中的中間結果是唯一的例外。

對於整數直方圖,列表中的元素是有符號 64 位整數(簡稱 int64),每個元素表示桶填充量相對於列表中前一個桶的增量。每個列表中的第一個桶包含一個絕對填充量(也可以看作是相對於零的增量)。這些增量禁止(MUST NOT)計算出負的絕對桶填充量。

為了將列表中的桶對映到上一節定義的索引,有兩個所謂的“跨度”(span)列表,一個用於正桶,一個用於負桶。

每個跨度由一對數字組成,一個是有符號 32 位整數(簡稱 int32),稱為 offset(偏移量),另一個是無符號 32 位整數(簡稱 uint32),稱為 length(長度)。每個列表中只有第一個跨度可以具有負偏移量。它定義了其對應桶列表中第一個桶的索引。(請注意,對於 NHCB,索引總是正數,詳情請參閱下面的“自定義值”部分。)長度定義了桶列表開始時連續桶的數量。後續跨度的偏移量定義了被排除(因此未填充的桶)的數量。長度定義了排除桶之後列表中連續桶的數量。

每個跨度列表中所有長度值的總和必須(MUST)等於相應桶列表的長度。

空跨度(長度為零)是有效的,並且可以(MAY)使用,儘管它們通常沒有用處,並且應該(SHOULD)透過將其偏移量新增到下一個跨度的偏移量來消除。同樣,不是列表中第一個跨度的跨度可以(MAY)具有零偏移量,儘管這些偏移量應該(SHOULD)透過將其長度新增到前一個跨度來消除。允許這兩種情況,以便原生直方圖的生產者可以(MAY)選擇當時具有最佳資源權衡的表示。例如,如果直方圖經過多個階段處理,則可能在最後一個處理階段之後才消除冗餘跨度最有效。

本著類似的精神,在某些情況下,從桶列表中排除每個未填充的桶是最有效的,但在其他情況下,透過顯式表示少量未填充的桶來減少跨度數量可能更好。

請注意,未來高解析度模式可能需要過大的偏移量,無法用 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 的單個未填充桶,則第二個和第三個跨度可以合併為一個,結果如下:

  • 正桶列表:[3, 2, -4, -1, 3, -1]
  • 正跨度列表:[[-2, 2], [2,4]]

或者透過顯式表示所有上述未填充桶來將所有跨度合併為一個:

  • 正桶列表:[3, 2, -5, 0, 1, -1, 3, -1]
  • 正跨度列表:[[-2, 8]]

零桶

精確為零的觀測值不符合上述標準模式定義的任何桶。它們被計數在一個稱為“零桶”的專用桶中。

零桶中的觀測值數量由單個 uint64(對於整數直方圖)或 float64(對於浮點直方圖)跟蹤。與常規桶一樣,這個數字通常是非負的。

零桶有一個額外的引數,稱為零閾值(zero threshold),它是一個 float64 值,且 ≥ 0。如果閾值設定為零,則只有精確為零的觀測值進入零桶,如上所述。如果閾值具有正值,則閉區間 [-threshold, +threshold] 內的所有觀測值都進入零桶,而不是常規桶。這有兩種用例:

  • 接近零的噪聲觀測往往會填充大量桶。這些觀測可能由於數值不精確或觀測來源是實際物理測量而發生。具有相對較小閾值的零桶將這些觀測重定向到單個桶中。
  • 如果使用者對遠離零的分佈長尾更感興趣,那麼零桶的相對較大閾值有助於避免在不感興趣的範圍內出現許多高解析度桶。

零桶的閾值應該(SHOULD)與常規桶的邊界重合,這樣可以避免零桶與常規桶部分重疊的複雜情況。但是,如果發生此類重疊,在與零桶重疊的常規桶中計數的觀測值必須(MUST)在 [-threshold, +threshold] 區間之外。

要合併具有相同零閾值的直方圖,只需將兩個零桶相加。然而,如果源直方圖中的零閾值不同,則選擇任何源直方圖中的最大閾值。如果該閾值恰好位於其他源直方圖的任何已填充桶內,則會增加閾值,直到每個源直方圖滿足以下條件之一:

  • 新閾值與已填充桶的邊界重合。
  • 新閾值不在任何已填充桶內。

然後將源零桶和現在在新閾值內的任何源桶相加,得到新零桶的填充量。

如果模式為 -53(自定義桶),則不使用零桶。

自定義值

自定義值列表不用於標準模式。非標準模式以自定義方式使用它,以防需要儲存額外資料。

目前唯一使用自定義值的模式是 -53(自定義桶)。本節的其餘部分將更詳細地描述在這種特定情況下自定義值的用法。

自定義值表示自定義桶的上限(包含)。它們按升序排列。自定義桶本身使用正桶列表和正跨度列表儲存,儘管它們的邊界(透過自定義值確定)可以是負數。這些“正”桶的索引定義了其上限在自定義值列表中的從零開始的位置。

下限(不包含)由上限之前的自定義值定義。對於列表中的第一個自定義值(位置為零),沒有前一個值,在這種情況下,下限被視為包含 -Inf。因此,索引為零的自定義桶計算介於(含)-Inf 和第一個自定義值之間的所有觀測值。在只期望正值觀測的常見情況下,索引為零的自定義桶的上限應該(SHOULD)為零,以明確標記是否存在任何零或零以下的觀測值。(如果確實只有正值觀測,則索引為零的自定義桶將保持未填充狀態,因此永遠不會顯式表示。唯一的成本是自定義值列表開頭額外的零元素。)

自定義值禁止(MUST NOT)為 +Inf。大於最後一個自定義值的觀測值進入一個上限為 +Inf 的溢位桶。這個溢位桶的索引等於自定義值列表的長度。因此,通常包含在經典直方圖中的 +Inf 桶的上限在自定義值中沒有顯式表示。

自定義值禁止(MUST NOT)為 NaN。這在 OpenMetrics 中被明確排除,但其他暴露格式原則上可能在經典直方圖中具有 NaN 的上限(推測是由於某種錯誤——這樣的邊界沒有任何意義)。這樣的經典直方圖必須(MUST)被拒絕,並且不能轉換為 NHCB。

Exemplars(範例)

一個原生直方圖樣本可以有零個、一個或多個範例。它們的工作方式與傳統範例相同,但它們以列表形式組織(因為可以有多個),並且它們必須(MUST)具有時間戳。

作為經典直方圖一部分暴露的範例,如果它們具有時間戳,則可以(MAY)被原生直方圖使用。

觀測值的特殊情況

埋點程式碼應該(SHOULD)避免觀測 NaN±Inf 值,因為它們在直方圖上下文中意義有限。然而,這些值仍然必須(MUST)得到妥善處理,如下所述。

觀測值之和的計算方式與通常相同,即遵循正常的浮點算術,將觀測值加到觀測值之和中。(例如,觀測值為 NaN 會將總和設為 NaN。觀測值為 +Inf 會將總和設為 +Inf,除非總和已為 NaN-Inf,在這種情況下總和設為 NaN。)

觀測值為 NaN 不會進入任何桶,但會增加觀測值計數。這意味著觀測值計數可能大於所有桶(負數桶、正數桶和零桶)的總和,差值即為 NaN 觀測值的數量。(對於沒有 NaN 觀測值的整數直方圖,所有桶的總和等於觀測值計數。在通常的浮點精度限制內,對於沒有 NaN 觀測值的浮點直方圖也同樣適用。)

觀測值為 +Inf-Inf 會增加觀測值計數,並增加透過以下方式選擇的桶:

  • 使用標準模式時,+Inf 觀測值會增加如上所述的正溢位桶
  • 使用標準模式時,-Inf 觀測值會增加如上所述的負溢位桶
  • 使用模式 -53(自定義桶)時,+Inf 觀測值會增加索引等於自定義值列表長度的桶。
  • 使用模式 -53(自定義桶)時,-Inf 觀測值會增加索引為零的桶。

OpenTelemetry 互操作性

Prometheus (Prom) 具有標準模式的原生直方圖可以輕鬆對映到 OpenTelemetry (OTel) 指數直方圖,反之亦然,詳情如下。

Prom 的模式等於 OTel 中的比例,但 OTel 允許低於 -4 和高於 +8 的值。如上所述,Prometheus 保留了更多模式編號,以備實踐中需要時擴充套件其範圍。

索引偏移一位,即 Prom 桶的索引為 n,則 OTel 的索引為 n-1

OTel 採用密集而不是稀疏的桶表示。可以將 OTel 視為“只有一個跨度的 Prom”。

Prom 的零桶在 OTel 中稱為零計數。(Prom 也使用零計數來命名儲存零桶中觀測值計數的欄位)。兩者的工作方式相同,包括零閾值的存在。請注意,如果未給出閾值,OTel 預設閾值為零。

(待辦:OTel 規範寫道:“當 zero_threshold 未設定或為 0 時,此桶儲存無法使用標準指數公式表示的值以及四捨五入為零的值。” 雙重檢查這是否真的會產生相同的行為。如果接近零時存在問題,我們可以使 Prom 的規範更精確。如果 OTel 在零桶中計算 NaN,我們必須在此處新增一個說明。)

OTel 指數直方圖僅支援標準指數分桶模式(正如其名稱所示)。因此,NHCB(或帶有其他未來分桶模式的原生直方圖)無法乾淨地轉換為 OTel 指數直方圖。但是,仍然可以轉換為具有固定桶的傳統 OTel 直方圖。

任何型別的 OTel 直方圖都具有可選欄位,用於記錄直方圖中觀察到的最小值和最大值。Prometheus 中沒有與這些欄位等效的概念,因為計數器直方圖在漫長且不可預測的時間段內累積資料,並且可以隨時抓取,因此跟蹤最小值和最大值要麼不可行,要麼用途有限。請注意,原生直方圖可以相當準確地估算任意時間段內的最大和最小觀測值,詳見PromQL 部分

展現格式

在經典的 Prometheus 用例中,指標展現以字串為主,因為所有指標名稱、標籤名稱和標籤值佔用的空間遠多於 float64 樣本值,即使後者以可能更冗長的文字形式表示。

相比之下,原生直方圖遵循上述資料模型,包含更多的數值資料。這放大了基於 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 start_timestamp = 15;

  // Everything below here is for native histograms (also known as sparse histograms).

  // 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.
}

// [...]

請注意以下幾點:

  • 原生直方圖和經典直方圖都由相同的 Histogram proto 訊息編碼,即現有 Histogram 訊息透過原生直方圖的欄位進行了擴充套件。
  • 觀測值之和與計數以及 start_timestamp 的欄位在經典直方圖和原生直方圖之間共享,並且對兩者都以相同方式工作。
  • 該格式最初不支援經典浮點直方圖。在擴充套件原生直方圖格式的同時,作為副產品添加了對經典浮點直方圖的支援(參見欄位 sample_count_float, cumulative_count_float)。
  • Bucket 欄位和 Bucket 訊息用於經典直方圖的桶。完全可以建立一個同時表示經典和原生版本的 Histogram 訊息。解析器可以自由選擇其中一個或兩個版本(另請參閱抓取配置部分)。
  • 在浮點直方圖的情況下,桶填充編碼為絕對數字;在整數直方圖的情況下,則編碼為與前一個桶的增量(或對於第一個桶,與零的增量)。後者導致數字更小,從而編碼為更小的訊息大小,因為 protobuf 對 sint64 型別使用 varint 編碼。
  • 尚未收到觀測值的原生直方圖和未配置任何桶的經典直方圖在 protobuf 訊息中看起來完全相同。因此,旨在解析為原生直方圖的 Histogram 訊息 必須 包含一個“無操作範圍”,即 BucketSpan,其 offsetlength 均設為 0,位於重複的 positive_span 欄位中。
  • 原生直方圖的任意數量的示例 可以 新增到 Histogram 訊息的重複 Exemplar 欄位中,但每個示例 必須 具有時間戳。如果沒有以這種方式提供示例,解析器 可以 使用為經典桶提供的時間戳示例(在 Bucket 訊息的 Exemplar 欄位中,每個桶最多一個示例)。
  • 原生直方圖示例的數量和分佈 應 適應當前的用例。通常,示例負載 不應 遠大於 Histogram 訊息的其餘部分,並且示例 應 落入不同的桶中,並大致均勻地覆蓋整個桶範圍。(這通常優於按比例代表觀測值分佈的示例分佈,因為後者很少會產生來自分佈長尾的示例,而這通常是最有趣的示例。)
  • NHCBs 所需的自定義值沒有表示。NHCB 從不直接暴露,而是以經典直方圖的形式呈現,以便在攝取時(回)轉換為 NHCB。這對於聯邦也同樣適用。如果將來有需要,例如對於也利用自定義值的未來模式,我們可能仍會新增自定義值的欄位。

OpenMetrics

目前(2024-11-03),OpenMetrics 不支援原生直方圖。

由於與經典 Prometheus protobuf 格式的相似性,向 OpenMetrics 的 protobuf 版本新增支援相對簡單。一個PR 形式的提案 正在審查中。

向 OpenMetrics 的文字版本新增支援更困難,但同樣高度期望,因為在許多情況下生成 protobuf 是不可行的。文字格式必須在人類可讀性和機器高效處理(編碼、傳輸、解碼)之間進行權衡。相關工作正在進行中。詳見設計文件 

(待辦:隨著進展更新本節。)

儀器庫

protobuf 規範使得能夠使用 protobuf 編譯器建立的特定語言繫結來低級別建立包含原生直方圖的指標展現。然而,對於直接程式碼儀器,需要一個儀器庫。

目前(2026-06-15),有三個官方 Prometheus 儀器庫支援原生直方圖:

如果儀器庫已經支援 protobuf 展現,那麼向其他儀器庫新增原生直方圖支援相對容易。對於純文字庫,完成基於文字的展現格式是先決條件。(待辦:根據需要更新此內容。)

本節不涵蓋如何使用單個儀器庫的詳細資訊(請參閱上面連結的文件),而是側重於常見的使用模式,並提供關於如何作為儀器庫的一部分實現原生直方圖支援的通用指南。現有的Go 實現 用於示例。關於資料模型展現格式的部分與儀器庫的實現高度相關(但本節不再重述!)。

直方圖的實際儀器 API 不會因原生直方圖而改變。經典直方圖和原生直方圖以相同的方式接收觀測值(關於示例有細微差異,見下一段)。儀器庫甚至可以維護同一直方圖的經典版本和原生版本並並行暴露它們,以便抓取器可以選擇攝取哪個版本(詳見關於展現格式的部分)。使用者透過配置設定選擇是暴露經典直方圖和/或原生直方圖。

經典直方圖的示例通常透過儲存和暴露每個桶的最新示例來跟蹤。只要定義了經典桶,儀器庫就可以為同一直方圖的原生版本暴露相同的示例,只要每個示例都有時間戳。(實際上,抓取器可以使用經典版本直方圖提供的示例,即使它只攝取原生版本,詳見展現格式部分)。然而,原生直方圖 可以 分配任意數量的示例,儀器庫 應 利用此自由來遵循展現格式部分中描述的示例最佳實踐。

儀器庫 應 為遵循標準模式的原生直方圖提供以下配置引數。名稱是 Go 庫中的示例——它們必須根據其他語言的慣用風格進行調整。括號中的值是庫 應 提供的預設值。

  • NativeHistogramBucketFactor (1.1):一個大於 1 的浮點數,用於確定初始解析度。庫選擇一個起始模式,使得桶寬度從一個桶到下一個桶的增長因子不大於所提供的值。示例值請參見下表。
  • NativeHistogramZeroThreshold (2-128):一個值為零或更大的浮點數,用於設定零桶的初始閾值。

解析度透過增長因子設定,而不是直接提供模式,因為大多數使用者不瞭解模式數字背後的數學原理。桶之間增長因子的上限概念在不瞭解原生直方圖內部工作原理的情況下也是可以理解的。下表列出了每個有效模式的示例因子。

NativeHistogramBucketFactor結果模式
65536-4
256-3
16-2
4-1
20
1.51
1.22
1.13
1.054
1.035
1.026
1.017
1.0058

限制桶的數量

原生直方圖的桶在首次填充時動態建立。觀測值分佈出人意料地廣泛可能導致桶的數量出人意料地高,需要比預期更多的記憶體。如果觀測值分佈可以從外部操縱,這甚至可能被用作透過耗盡程式所有可用記憶體來進行 DoS 攻擊。因此,儀器庫 應 提供桶限制策略。它 可以 預設設定一個,具體取決於庫的典型用例。(待辦:也許我們應該說預設情況下 應 設定一個策略。Go 庫目前預設不限制桶,到目前為止沒有報告任何問題。)

以下描述了 Go 儀器庫實現的桶限制策略。其他庫 可以 遵循此示例,但其他策略也可能可行,具體取決於庫的典型使用模式。

該策略由三個引數定義:一個無符號整數 NativeHistogramMaxBucketNumber、一個持續時間 NativeHistogramMinResetDuration 和一個浮點數 NativeHistogramMaxZeroThreshold。如果 NativeHistogramMaxBucketNumber 為零(這是預設值),桶根本不受限制,並且忽略其他兩個引數。如果 NativeHistogramMaxBucketNumber 設定為正值,庫會嘗試將每個直方圖的桶計數保持在提供的值。限制的典型值是 160,這也是 OTel 指數直方圖在類似策略中使用的預設值。(請注意,按標籤分割槽將建立多個直方圖。限制分別適用於每個直方圖,而不是所有直方圖的總和。)如果超出限制,將按順序應用一系列補救措施,直到桶的數量再次在限制內:

  1. 如果自直方圖上次重置(包括直方圖的建立)以來至少經過了 NativeHistogramMinResetDuration,則整個直方圖被重置,即所有桶都被刪除,觀測值之和、計數以及零桶都被設為零。Prometheus 將此視為正常的計數器重置,這意味著某些觀測值將在抓取之間丟失,因此相對於抓取間隔,重置應很少發生。此外,頻繁的計數器重置可能導致 TSDB 中儲存效率降低(詳見TSDB 部分)。一小時的 NativeHistogramMinResetDuration 在大多數情況下應該執行良好。
  2. 如果自上次重置以來時間不足(或 NativeHistogramMinResetDuration 設定為零,這是預設值),則不執行重置。相反,零閾值增加,將接近零的桶合併到零桶中,以此方式減少桶的數量。閾值的增加受 NativeHistogramMaxZeroThreshold 限制。如果已達到此值(或設定為零,這是預設值),則此步驟不發生任何事情。
  3. 如果桶的數量仍然超過限制,則透過將直方圖轉換為下一個較低的模式來降低其解析度,即透過合併相鄰的桶,從而使桶的寬度加倍。重複此操作,直到桶計數在配置限制內或達到模式 -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),儀器庫不提供直接配置具有自定義桶邊界(NHCBs)的原生直方圖的方法。NHCBs 的用例是允許啟用原生直方圖的抓取器在攝取時將經典直方圖轉換為 NHCBs(詳見下一節)。然而,在儀器化過程中直接需要自定義桶的用例是存在的。在這些情況下,當前的方法是使用經典直方圖進行儀器化,並配置抓取器在攝取時將其轉換為 NHCB。然而,未來儀器庫中可能會更直接地處理 NHCB。

抓取配置

要使 Prometheus 伺服器能夠抓取原生直方圖,請在單個抓取配置或全域性設定中設定 scrape_native_histograms: true。啟用 scrape_native_histograms 還會改變內容協商,使其優先選擇經典的基於 protobuf 的展現格式,而不是 OpenMetrics 1.x 文字格式。

微調內容協商

可以透過 scrape_protocols 配置設定全域性或按抓取配置微調抓取協議協商。它是一個定義內容協商優先順序的列表。其值取決於啟用了哪些功能標誌(例如 --enable-feature=created-timestamp-zero-ingestion)、使用者直接設定的值,以及最後 scrape_native_histograms 是否啟用。

如果 scrape_native_histograms 已啟用,且使用者未全域性或按抓取配置透過功能標誌或直接設定 scrape_protocols,則抓取配置的有效值將更改為 [ PrometheusProto, OpenMetricsText1.0.0,OpenMetricsText0.0.1, PrometheusText0.0.4 ],以啟用抓取原生直方圖。

scrape_protocols 設定可用於配置 protobuf 抓取而不攝取原生直方圖,或即使 scrape_native_histograms 已啟用,也強制某些目標使用非 protobuf 格式。只要經典的 Prometheus protobuf 格式(配置列表中的 PrometheusProto)是唯一支援原生直方圖的格式,實際攝取原生直方圖就需要 scrape_native_histograms 和 protobuf 協商。

注意在基於文字和基於 protobuf 的展現格式之間切換有一些不明顯的含義。最重要的是,某些實現細節導致了一種反直覺的效果,即基於文字格式的抓取通常比基於 protobuf 格式的抓取對資源的需求少得多(詳見跟蹤問題 )。更微妙的是對 quantile 標籤(用於摘要)和 le 標籤(用於經典直方圖)的標籤值格式的影響。這個問題隻影響 Prometheus 伺服器的 v2 版本(v3 在所有情況下都具有一致的格式),並且與原生直方圖沒有直接關係,但可能會在相同上下文中出現,因為啟用原生直方圖需要 protobuf 展現格式。詳見 v2.55 的native-histograms 功能標誌文件

限制桶計數和解析度

雖然儀器庫 應 提供配置選項來限制原生直方圖的解析度和桶計數,但仍需要在攝取時強制執行這些限制。使用者可能無法更改給定程式的儀器化,或者程式可能故意使用高解析度直方圖進行儀器化,以使不同的抓取器可以根據需要降低解析度。

Prometheus 抓取配置提供了兩個設定來解決此需求:

  1. native_histogram_bucket_limit 為單個直方圖中的桶數量設定了一個上限(包含)。如果超出限制,具有標準模式的直方圖的解析度會重複降低(透過將桶的寬度加倍,即降低模式),直到達到限制。如果 NHCB 超出限制,或者在極少數情況下,即使使用模式 -4 也無法滿足限制,抓取將失敗。
  2. native_histogram_min_bucket_factor 為桶到桶的增長因子設定了一個下限(包含)。此設定僅與標準模式相關,對 NHCB 無效。同樣,如果超出限制,直方圖的解析度會重複降低(透過將桶的寬度加倍,即降低模式),直到達到限制。然而,一旦達到模式 -4,抓取仍將成功,即使指定了更高的增長因子。

這兩個設定都接受零作為有效值,這意味著“無限制”。在桶限制的情況下,這意味著桶的數量根本不會被檢查。在桶因子的情況下,Prometheus 仍將確保標準模式不會超出所用儲存後端的容量。Prometheus 目前儲存標準指數模式最高為 8 的直方圖。然而,它接受大於 8 到保留限制 52 的指數模式,但在攝取時降低其解析度,以達到模式 8(如果 native_histogram_bucket_limitnative_histogram_min_bucket_factor 設定要求,則更低)。

如果兩個設定都有非零值,則模式會充分降低以滿足這兩個限制。

請注意,儀器化期間設定的桶因子是上限(暴露的桶增長因子 ≤ 配置值),而抓取配置中設定的桶因子是下限(攝取的桶增長因子 ≥ 配置值)。因此,某些限制導致的模式略有不同。一些示例:

native_histogram_min_bucket_factor結果最大模式
65536-4
256-3
16-2
4-1
20
1.41
1.12
1.093
1.044
1.025
1.016
1.0057
1.0028

關於設定限制的一般考慮:native_histogram_bucket_limit 適用於為單個直方圖的成本設定硬限制。native_histogram_min_bucket_factor 無法實現同樣的效果,因為即使解析度低,如果觀測值分佈足夠廣泛,直方圖也可能有很多桶。native_histogram_min_bucket_factor 非常適合避免不必要的總體資源成本。例如,如果當前用例只需要一定的解析度,為所有直方圖設定相應的 native_histogram_min_bucket_factor 可能會釋放足夠的資源,以接受少數觀測值分佈廣泛的直方圖具有非常高的桶計數。另一個例子是,某些直方圖由於某種原因(可能已經在儀器化端)具有低解析度。如果聚合定期包含這些低解析度直方圖,結果將具有相同的低解析度(詳見PromQL 詳細資訊如下)。將定期與低解析度直方圖聚合的其他直方圖以更高解析度儲存可能沒有太大用處。

抓取經典直方圖和原生直方圖

上文所述,被儀器化程式暴露的直方圖可能包含經典直方圖和原生直方圖,並且某些部分甚至共享(如觀測值的計數和總和)。本節解釋 Prometheus 將抓取哪些部分,以及如何控制行為。

如果抓取配置中的 scrape_native_histogramsfalse(v3 中的預設值),Prometheus 將在抓取期間完全忽略原生直方圖部分。如果 scrape_native_histogramstrue(v4+ 中的預設值),Prometheus 將優先選擇原生直方圖部分而非經典直方圖部分,即使兩者都為同一直方圖暴露。Prometheus 仍將抓取沒有原生直方圖資料的直方圖的經典直方圖部分。

遷移場景等情況下,可能希望為同一直方圖抓取經典和原生兩個版本,前提是儀器化程式暴露了兩個版本。為了啟用此行為,抓取配置中有一個布林設定 always_scrape_classic_histograms。它預設為 false,但如果設定為 true,則每個直方圖的兩個版本都將被抓取和攝取,前提是至少有一個經典桶和至少一個原生桶範圍(這可能是一個無操作範圍)。這不會在 TSDB 中引起任何衝突,因為經典直方圖作為帶有後綴的多個序列被攝取,而原生直方圖作為只有一個序列被攝取,名稱未修改。(示例:名為 rpc_latency_seconds 的直方圖會生成一個名為 rpc_latency_seconds 的原生直方圖序列,以及經典部分的多個序列,即 rpc_latency_seconds_sumrpc_latency_seconds_count,以及多個具有不同 le 標籤的 rpc_latency_seconds_bucket 序列。)

將經典直方圖抓取為 NHCB

前述 NHCB 能夠將經典直方圖建模為原生直方圖。透過布林抓取配置選項 convert_classic_histograms_to_nhcb,可以將 Prometheus 配置為將經典直方圖作為 NHCB 攝取。

雖然 NHCBs 支援不同桶佈局之間的自動協調,但它們的合併能力仍然受到根本限制。協調只保留涉及的 NHCB 之間桶邊界的精確匹配。如果大多數桶邊界匹配,則會產生有用的結果。然而,桶佈局的任意更改很容易造成沒有邊界匹配的情況,導致直方圖只有一個桶(溢位桶)。

NHCB 的一個關鍵優勢是它們的儲存成本通常低得多。特別是,增加額外桶的增量成本相對較低,這允許以可承受的成本攝取具有許多桶的經典直方圖。

TSDB

注意本節提供了在 TSDB 中儲存原生直方圖的高階概述,並解釋了一些可能容易遺漏的重要個別方面。它不旨在解釋實現細節、定義磁碟格式或引導瀏覽程式碼庫。有各種儲存格式的詳細文件 ,當然還有通常生成的 GoDoc,其中tsdb 包 storage 包 是合適的起點。一份有用的資源是前述的Prometheus 原生直方圖開發者指南 

整數直方圖 vs 浮點直方圖

TSDB 以不同方式儲存整數直方圖和浮點直方圖。通常,整數直方圖有望獲得更好的壓縮效果,因此,如果所有桶計數和觀測值計數在 int64 範圍內具有整數值,TSDB 實現 可以 將浮點直方圖儲存為整數直方圖,以便轉換為整數直方圖可以建立原始浮點直方圖的數值精確表示。(請注意,Prometheus TSDB 尚未利用此選項。)

編碼

原生直方圖在 TSDB 中需要兩種新的塊編碼(Go 型別 chunkenc.Encoding):chunkenc.EncHistogram(字串表示 histogram,數值 2)用於整數直方圖,以及 chunkenc.EncFloatHistogram(字串表示 floathistogram,數值 3)用於浮點直方圖。

同樣,WAL 和記憶體快照(Go 型別 record.Type)也有兩種新的記錄型別:record.HistogramSamples(字串表示 histogram_samples,數值 9)用於整數直方圖,以及 record.FloatHistogramSamples(字串表示 float_histogram_samples,數值 10)用於浮點直方圖。為了向後相容,還有兩種直方圖記錄型別:record.HistogramSamplesLegacyhistogram_samples_legacy,7)和 record.FloatHistogramSamplesLegacyfloat_histogram_samples_legacy,8)。它們在引入 NHCB 所需的自定義值之前使用。它們受支援,以便仍然可以讀取舊的 WAL。

Prometheus 僅透過標籤識別時間序列。序列中的樣本是浮點數(因此是計數器或儀表)還是直方圖(無論何種型別)都不會影響序列的身份。因此,一個序列 可以 包含不同型別和風格的混合樣本。時間序列中樣本型別的變化在實踐中預計非常罕見。它們通常發生在目標儀器化更改之後(在極少數情況下,相同的指標名稱在更改前用於例如儀表浮點數,更改後用於計數器直方圖)或記錄規則更改之後(例如,規則的舊版本建立了一個儀表浮點數,而新版本現在建立了一個儀表直方圖,同時保留其名稱)。樣本型別的頻繁更改通常是配置錯誤的結果(例如,兩個不同的記錄規則建立不同的樣本型別並輸入到同一序列中)。因此,TSDB 實現 必須 處理樣本型別更改,但它 可以 以相對低效的方式進行。當 Prometheus TSDB 遇到無法寫入當前使用的塊的樣本型別時,它會關閉該塊並使用適當的編碼啟動一個新塊。(對於每個樣本來回切換樣本型別的時間序列將導致每個樣本建立一個新塊,這確實非常低效。)

直方圖塊使用多種自定義編碼來處理數值,以透過用更少的位編碼常見值來減小資料大小,而不是不常見的值。每種自定義編碼的詳細資訊在低階塊格式文件 中描述(最終在從那裡連結的程式碼中)。以下三種編碼用於許多不同的欄位,因此在此處命名以供將來參考:

  • varbit-int 是一種有符號整數的可變位寬編碼。它使用 1 位到 9 位元組。接近零的數字需要更少的位。這類似於浮點樣本塊中的時間戳編碼,但位長度的分桶方式不同,針對原生直方圖中常見的數值分佈進行了最佳化。
  • varbit-uint 是一種類似的編碼,但用於無符號整數。
  • varbit-xor 是一種浮點數序列的可變位寬編碼。它基於序列中當前和前一個浮點值的 XOR 運算。每個浮點數使用 1 位到 77 位。這與 TSDB 已經用於浮點樣本的編碼完全相同。

直方圖塊通常以塊中的樣本數量(作為 uint16)開始,接著是一個位元組,描述直方圖是儀表直方圖還是計數器直方圖,併為後者提供計數器重置資訊。詳見下文相應部分。這之後是所謂的塊佈局,它包含以下資訊,由塊中所有直方圖共享

  • 零桶的閾值,使用自定義編碼,將常見值(零或某個 2 的冪)編碼為僅一個位元組,但對於任意值需要 9 個位元組。
  • 模式,編碼為 varbit-int。
  • 正向範圍,編碼為範圍數量(varbit-uint),然後是重複序列中每個範圍的長度(varbit-uint)和偏移量(varbit-int)。
  • 負向範圍以相同方式。
  • 僅對於模式 -53 (NHCB),自定義值編碼為自定義值的數量(varbit-uint),然後是重複序列中的自定義值,使用自定義編碼。

塊佈局之後是樣本資料的重複序列。整數直方圖和浮點直方圖的樣本資料不同。對於整數直方圖,每個樣本的資料包含以下內容:

  • 時間戳,編碼為 varbit-int,第一個樣本是絕對值,第二個樣本是第一個和第二個樣本之間的增量,後續樣本是“增量的增量”(即與傳統浮點塊中時間戳使用的“雙增量”編碼相同,只是 varbit-int 編碼的位分桶方式不同)。
  • 觀測值計數,第一個樣本編碼為 varbit-uint,後續樣本編碼為 varbit-int,使用與時間戳相同的“增量的增量”方法。
  • 零桶填充,第一個樣本編碼為 varbit-uint,後續樣本編碼為 varbit-int,使用與時間戳相同的“增量的增量”方法。
  • 觀測值之和,第一個樣本編碼為 float64,後續樣本編碼為 varbit-xor(當前樣本與前一個樣本進行 XOR 運算)。
  • 正桶的桶填充,每個桶相對於前一個桶的增量(或第一個桶的絕對填充),編碼為 varbit-int,使用與時間戳相同的“增量的增量”方法。(換句話說,“雙增量”編碼應用於本身已經是增量的值,這就是為什麼有時稱之為“三增量”編碼的原因。)
  • 負桶的桶填充以相同方式。

浮點直方圖的樣本資料有以下不同:

  • 觀測值計數和零桶填充現在是浮點數,因此與觀測值之和以相同方式編碼(第一個樣本為 float64,後續樣本為 varbit-xor)。
  • 桶填充現在不僅是浮點數,而且是絕對填充計數,而不是桶之間的增量。在第一個樣本中,所有桶填充表示為普通 float64,而對於所有後續樣本,它們編碼為 varbit-xor,對當前樣本和前一個樣本的相應桶進行 XOR 運算。

以下事件觸發新塊的切割(原因在括號中描述):

  • 整數直方圖和浮點直方圖之間的樣本型別更改(因為兩者都需要完全不同的塊編碼)。
  • 儀表直方圖和計數器直方圖之間的樣本型別更改(因為前導位元組必須表示不同的型別)。
  • 計數器直方圖的計數器重置(作為計數器重置資訊儲存在前導位元組中,詳見下文)。
  • 模式更改(這意味著我們需要新的塊佈局,而一個塊只能有一種塊佈局)。
  • 零閾值更改(這會更改塊佈局,如上所述)。
  • 自定義值更改(這會更改塊佈局,如上所述)。
  • 停滯標記後跟一個常規樣本(這並不嚴格要求新塊,但可以假設大多數直方圖在消失和重新出現時會發生很大變化,因此切割新塊是最佳選擇)。
  • 塊大小限制被超出(詳見下文)。

範圍差異也會改變塊佈局,但它們透過根據需要新增(顯式表示的)未填充桶進行協調,以便塊中的所有直方圖共享相同的範圍結構。如果一個桶消失,這很簡單,因為缺失的桶在直方圖追加到塊時簡單地作為未填充桶新增到新直方圖。然而,以前填充的桶的消失構成計數器重置(詳見下文),所以這種情況只可能發生在儀表直方圖(不具有計數器重置)。更常見的情況是,新追加的直方圖中存在以前追加的直方圖中不存在的桶。在這種情況下,這些桶必須作為顯式未填充桶新增到所有先前追加的直方圖。這需要對整個塊進行完整的重新編碼。(僅重新編碼受影響的部分存在一些最佳化潛力。實現這將相當複雜。到目前為止,完整重新編碼的效能影響並未顯現出問題。)

停滯標記

注意為了理解以下部分,回顧停滯標記在 TSDB 中如何工作很重要。浮點序列中的停滯標記由用於表示 NaN 值的眾多位模式中的一個特定位模式表示。這種非常特殊的浮點值在以下部分被稱為“特殊停滯 NaN 值”。它(幾乎肯定)不會由常規的算術浮點運算返回,因此與“自然發生的” NaN 值不同,包括觀測值的特殊情況中討論的那些。實際上,查詢 TSDB 時從不直接返回特殊停滯 NaN 值,而是在其到達呼叫者之前在內部處理。

為了標記直方圖序列中的停滯,可以使用通常的特殊停滯 NaN 值。然而,這將需要切割一個新塊,僅僅為了標記序列為停滯,因為緊隨直方圖值之後的浮點值必須儲存在不同的塊中(如上所述)。因此,還有一個停滯標記的直方圖版本,其中觀測值之和的欄位設定為特殊停滯 NaN 值。在這種情況下,所有其他欄位都被忽略,這允許將它們設定為適合高效儲存的值(因為停滯標記的直方圖版本本質上只是一個儲存最佳化)。這適用於浮點直方圖和整數直方圖(因為求和欄位即使在整數直方圖中也是浮點值),並且可以使用適當的版本來避免切割新塊。TSDB 必須將所有版本的停滯標記(浮點數、整數直方圖、浮點直方圖)視為等效。

塊大小限制

浮點塊的大小限制為 1024 位元組。同樣的尺寸限制通常也用於直方圖塊。然而,如果單個直方圖有很多桶,它們會變得非常大,因此盲目強制執行大小限制可能導致塊中直方圖數量很少。(在最極端的情況下,單個直方圖甚至可能佔用超過 1024 位元組,因此大小限制根本無法強制執行。)每個塊的直方圖數量很少時,壓縮率會變差。因此,每個塊必須達到最少 10 個直方圖,然後 1024 位元組的大小限制才會生效。這意味著直方圖塊可能遠大於 1024 位元組。

要求每個塊最少 10 個直方圖是最初的、非常簡單的方法,未來可能會改進,以在塊大小和壓縮率之間找到更好的權衡。

計數器重置考慮

通常,Prometheus 認為只要計數器的值從一個樣本下降到下一個樣本,就發生了重置(但也請參閱關於開始時間戳的下一節)。在兩個直方圖樣本之間檢測計數器重置時情況更復雜。

首先,儀表直方圖和計數器直方圖是明確不同的(而 Prometheus 通常在攝取後平等對待所有浮點樣本,無論它們是作為儀表還是計數器指標攝取)。計數器重置不適用於儀表直方圖。

如果時間序列中儀表直方圖後面跟著計數器直方圖,則假定發生了計數器重置,因為從儀表到計數器的變化被認為等同於儀表被刪除,計數器從零開始重新建立。

最常見的情況是一個計數器直方圖後面跟著另一個計數器直方圖。在這種情況下,透過以下程式檢測可能的計數器重置:

如果兩個直方圖都使用標準模式,但在模式或零桶寬度上有所不同,這些變化可能是相容解析度降低的一部分(這會定期發生以減少直方圖的桶計數)。對於相容解析度降低,以下兩點都成立:

  • 如果模式已更改,其編號已從一個標準指數模式減少到另一個標準模式。
  • 如果零桶寬度已更改,則第一個直方圖中的任何填充的常規桶要麼完全包含在第二個直方圖的零桶中,要麼完全不包含(即舊常規桶與新零桶沒有部分重疊)。

如果任何條件不滿足,則更改不是相容解析度降低。因為這種更改只能透過重置或新建直方圖來實現,所以它被視為計數器重置,檢測程式結束。

如果兩個條件都滿足,則第一個直方圖必須轉換,使其模式和零桶寬度與第二個直方圖的模式和零桶寬度匹配。這與前面描述的方式相同:合併相鄰的桶以降低模式,並將常規桶與零桶合併以增加零桶的寬度。

如果兩個直方圖都是 NHCB(模式 -53),則它們的自定義值中的任何差異將按下面描述的方式進行協調。

在此程式的這一點,兩個直方圖具有相同的模式和零桶寬度,要麼是因為一開始就是這樣,要麼是因為其中一個直方圖已相應轉換。(請注意,NHCB 不使用零桶。為了本程式的需要,它們的零桶寬度和填充計數被視為相等。)在這種情況下,以下任何一種情況都構成計數器重置:

  • 觀測計數下降(但值得注意的是,觀測總和未下降)。
  • 任何桶(包括零桶)的填充計數下降。這包括填充的桶消失的情況,因為未表示的桶等同於填充計數為零的桶。

如果以上情況均未發生,則沒有計數器重置。

由於整個過程相對複雜,計數器重置檢測最好在攝取期間進行一次,並將結果持久化以供後續使用。攝取期間無論如何都必須進行計數器重置檢測,因為計數器重置是觸發切割新資料塊的條件之一。

在計數器重置後切割新資料塊旨在提高壓縮率。計數器重置會將所有桶的填充計數設定為零,從而減少需要表示的桶。然而,一個數據塊必須表示該資料塊中所有直方圖的所有桶的超集,因此切割新資料塊可以為新資料塊啟用一組更簡單的桶。

這反過來意味著在資料塊中的第一個樣本之後,永遠不會發生計數器重置。因此,唯一需要持久化的計數器重置資訊是資料塊中第一個直方圖的資訊。這發生在所謂的直方圖標誌中,一個儲存在資料塊樣本數量之後的單位元組。該位元組目前僅用於計數器重置資訊,但將來可能用於其他標誌。計數器重置資訊使用前兩位。四種可能的位模式在chunkenc包中表示為型別為CounterResetHeader的Go常量。它們的名稱和含義如下:

  • GaugeType (位模式11):該資料塊包含量規直方圖。計數器重置與量規直方圖無關。
  • CounterReset (位模式10):在上一資料塊的最後一個直方圖和當前資料塊的第一個直方圖之間發生了計數器重置。(很可能計數器重置實際上是切割新資料塊的原因。)
  • NotCounterReset (位模式01):在上一資料塊的最後一個直方圖和當前資料塊的第一個直方圖之間未發生計數器重置。(這通常發生在新資料塊是因為上一資料塊達到大小限制而被切割的情況。)
  • UnknownCounterReset (位模式00):在上一資料塊的最後一個直方圖和當前資料塊的第一個直方圖之間是否發生了計數器重置是未知的。

UnknownCounterReset 始終是一個安全的選擇。它不會阻止計數器重置檢測,而只是要求在需要計數器重置資訊時必須(再次)執行計數器重置檢測過程。

當查詢TSDB時,計數器重置資訊會傳播給呼叫者(在Go程式碼中,作為Go型別HistogramFloatHistogram中型別為CounterResetHint的欄位,使用與上述位模式常量同名的列舉常量)。

對於量規直方圖,CounterResetHint始終是GaugeType。任何其他CounterResetHint值都意味著所討論的直方圖是計數器直方圖。透過這種方式,查詢器(包括PromQL引擎,參見下文)可以獲得直方圖是量規還是計數器的資訊(這與浮點樣本顯著不同)。

只要計數器直方圖按順序從單個數據塊返回,資料塊中第二個及後續直方圖的CounterResetHint將設定為NotCounterReset。(重疊資料塊和亂序攝取可能導致直方圖序列來自多個數據塊,這需要特殊處理,參見下文。)

當從計數器直方圖資料塊返回第一個直方圖時,CounterResetHint必須設定為UnknownCounterReset除非TSDB實現能夠確保先前返回的直方圖確實是攝取時用於檢測計數器重置的前一個直方圖。僅在後一種情況下,資料塊中的計數器重置資訊才可以直接用作返回直方圖的CounterResetHint

這種預防措施是必要的,因為資料塊可能會以各種方式被移除或插入(例如透過墓碑刪除或新增塊進行回填)。計數器重置雖然歸因於一個樣本,但實際上是發生在標記樣本和前一個樣本之間。移除前一個樣本或在兩個樣本之間插入另一個樣本會使先前執行的計數器重置檢測失效。

待辦事項目前,Prometheus TSDB無法確保前一個數據塊與攝取時的資料塊相同。因此,Prometheus目前對計數器直方圖資料塊中的所有第一個直方圖返回UnknownCounterReset。請參閱追蹤問題 以瞭解改變這一現狀的努力。

如上所述,如果CounterResetHint設定為UnknownCounterReset,查詢器必須(再次)執行計數器重置檢測過程。

在處理重疊資料塊或亂序樣本(用於查詢或在壓縮期間)時,必須特別小心。在這些情況下,可能會發生計數器重置的過度檢測和欠檢測,如下例所示:

  • 欠檢測示例:一個數據塊包含樣本ABC,沒有計數器重置。另一個數據塊包含樣本DEF,同樣沒有計數器重置。這些資料塊重疊並引用同一時間序列。當它們一起查詢時,樣本的時間順序變為ADBECF。現在很可能在某些甚至所有這些樣本之間存在計數器重置。如果這兩個樣本實際上來自不相關的系列並意外合併到同一系列中,則這種情況很可能發生。然而,即使是這種意外合併也必須由TSDB正確處理。如果重疊的資料塊被壓縮成一個新的資料塊,則必須進行新的計數器重置檢測,以捕獲新的計數器重置。如果直接查詢重疊的資料塊(沒有事先壓縮),則對於每個來自與先前返回樣本不同資料塊的樣本,必須設定UnknownCounterResetCounterResetHint,這要求查詢器進行計數器重置檢測(利用上述安全回退機制)。
  • 過檢測示例:存在樣本序列ABCD,其中在B和C之間發生了計數器重置。然而,最初的攝取錯過了B和C,因此只攝取了A和D,並在A和D之間檢測到計數器重置。隨後,B和C被攝取(透過亂序攝取或作為單獨的資料塊稍後新增到TSDB作為單獨的塊),並在B和C之間檢測到計數器重置。在這種情況下,每個樣本都進入自己的資料塊,因此當組裝所有資料塊時,它們甚至不重疊。然而,根據上述規則返回計數器重置提示時,C和D都將以CounterResetCounterResetHint返回給查詢器,儘管C和D之間現在沒有計數器重置。與上一個示例中的情況類似,必須在A和B之間執行新的計數器重置檢測,並在C和D之間執行另一個。或者B和D都必須以UnknownCounterResetCounterResetHint返回。

總之,每當TSDB無法安全地確定兩個樣本之間在攝取時是否發生了計數器重置檢測時,它要麼必須執行另一次計數器重置檢測,要麼必須為第二個樣本返回UnknownCounterResetCounterResetHint

請注意,存在無法透過上述過程檢測到的計數器重置的可能性,即如果重置直方圖中的計數增加得足夠快,以至於計數器重置後的第一個樣本與計數器重置前的最後一個樣本相比,計數沒有減少。(這對於浮點計數器也是一個問題,實際上更容易發生。)藉助上述機制,即使在這種情況下,只要計數器重置透過其他方式檢測到,也可以儲存計數器重置資訊。然而,由於資料塊的插入和刪除、亂序樣本以及重疊塊(如上所述)導致的複雜性,如果需要進行第二輪計數器重置檢測,此資訊可能會丟失。(待辦事項:目前,此資訊可靠地丟失,參見上文待辦事項。)更安全地標記計數器重置的一種更好方法是透過起始時間戳(參見下一節)。

起始時間戳處理

OpenMetrics引入了針對計數器、摘要和經典計數器直方圖的所謂“建立時間戳”(created timestamps)。(這個術語可能縮寫自“created-at timestamp”。更合適的術語可能是“creation timestamp”或“reset timestamp”。)該術語後來被重新命名為“起始時間戳”(start timestamp)。

起始時間戳提供了指標最近建立或重置的時間。一份設計文件 描述了Prometheus如何處理起始時間戳。

起始時間戳對於原生直方圖也很有用。與為浮點計數器插入合成零樣本的方式相同,為計數器直方圖插入一個直方圖樣本的零值。直方圖的零值沒有填充的桶,並且觀測總和、觀測計數和零桶填充計數都為零。直方圖的模式、零桶寬度、自定義值以及浮點或整數型別與直接跟隨合成零樣本的樣本匹配(以避免觸發虛假計數器重置的檢測)。

合成零樣本的計數器重置資訊始終設定為CounterReset

Exemplar

原生直方圖的示例(exemplars)是作為整個直方圖樣本附加的,而不是附加到單個桶。 (另請參閱展示格式部分。)因此,單個原生直方圖樣本可以附加多個示例是允許的(實際上也是常見情況)。

示例可能在一次抓取到下一次抓取之間發生變化,也可能不發生變化。抓取器檢測未更改的示例,以避免儲存大量重複示例。然而,鑑於單個樣本可能包含許多示例,其中任何子集都可能與上次抓取的示例重複,因此重複檢測可能成本高昂。TSDB可以依賴這樣的假設:任何新示例的時間戳都比之前暴露的任何示例的時間戳更晚。(請記住,原生直方圖的示例必須具有時間戳。)這樣便可以高效地進行重複檢測:

  1. 新攝取的原生直方圖的示例按以下欄位排序:首先是時間戳,然後是值,最後是標籤。
  2. 示例按排序順序附加到示例儲存中。
  3. 如果示例在排序上位於最後一個成功附加的示例之前或與其相等(該示例可能來自上次對同一指標的抓取),則附加會失敗。
  4. 如果示例在排序上位於最後一個成功附加的示例之後,則附加會成功。

只有當攝取直方圖的所有示例都將排在最後一個成功附加的示例之前時,才將其視為亂序。這不會檢測到與較新示例混合或與最後一個成功附加示例的副本混合的亂序示例,這被認為是可接受的。

PromQL

本節描述PromQL如何處理原生直方圖。它側重於通用概念,而不是單個操作的每一個細節。有關後者的資訊,請參閱PromQL關於運算子函式的文件。

註釋

原生直方圖的引入會建立某些情況,其中PromQL表示式返回意外結果,最常見的情況是輸出向量中的某些或所有元素意外缺失。為了幫助使用者檢測和理解這些情況,對原生直方圖執行的操作通常會使用註釋。註釋可以有警告級別和資訊級別,並描述在評估過程中可能遇到的問題。警告級別用於標記最可能是使用者需要採取行動的實際問題的情況。資訊級別用於標記可能是有意為之,但仍然足夠不尋常而需要標記的情況。

整數直方圖與浮點直方圖

PromQL始終對浮點直方圖進行操作。儲存為整數直方圖的原生直方圖在從TSDB檢索時會自動轉換為浮點直方圖。

直方圖之間的相容性

當運算子或函式作用於兩個或多個原生直方圖時,所涉及的直方圖需要具有相同的模式、相同的零桶寬度以及(如果適用)相同的自定義值。在一定限制內,直方圖可以即時轉換以滿足這些相容性標準:

  • NHCB(模式-53)僅彼此相容。不同的自定義值需要透過以下方式轉換進行協調:
    • 識別所有原始NHCB共享的自定義值。這些是新的協調後的自定義值。
    • 透過將每個原始NHCB的桶合併到新自定義值所描述的統一桶集中,將其轉換為新的自定義值。
    • 請注意,原始NHCB可能不共享任何自定義值。在這種情況下,新的桶集將僅包含溢位桶,包含所有原始桶中的所有觀測值。
    • 任何需要協調自定義值的查詢都會被標記一個資訊級別的註釋。
  • 具有標準模式的直方圖始終可以透過降低具有更大模式(即更高解析度)的直方圖的解析度,將其轉換為最小(即最低解析度)的共同模式。這通常透過將相鄰桶合併到較小模式的較大桶中來實現。
  • 不同的零桶寬度透過擴充套件較小的零桶來處理,將任何填充的常規桶適當地合併到擴充套件的零桶中。如果最大共同寬度恰好位於任何填充桶的中間,則會進一步擴充套件以與該桶的邊界重合。(更多詳情請參見上文零桶部分。)

如果相容性問題阻止了操作,則結果中會新增一個警告級別的註釋。

計數器重置

計數器重置的定義如上文所述。從TSDB返回的計數器重置提示可以被考慮在內,以避免顯式計數器重置檢測,並正確處理透過常規過程無法檢測到的計數器重置。(這意味著這些計數器重置僅在盡力而為的基礎上被考慮。然而,對於TSDB本身也是如此,參見上文。)與經典直方圖和摘要的計數器重置處理的一個顯著區別是,觀測總和的減少本身並構成計數器重置。(例如,即使直方圖觀測到負值,計算原生直方圖的速率仍然能正確工作。)

請注意,子查詢返回的計數器直方圖的計數器重置提示不得被考慮在內以避免顯式計數器重置檢測,除非PromQL引擎能夠安全地檢測到從子查詢返回的連續計數器直方圖在TSDB中也是連續的。

量規直方圖與計數器直方圖

透過TSDB返回的計數器重置提示,PromQL能夠知道原生直方圖是量規直方圖還是計數器直方圖。為了反映PromQL對浮點樣本的處理方式(PromQL無法可靠地區分浮點計數器和量規),作用於計數器的函式仍將處理量規直方圖,反之亦然,但結果中會返回一個警告級別註釋。請注意,在這種情況下,必須對量規直方圖執行顯式計數器重置檢測,將其視為計數器直方圖。

桶內插值

在估計直方圖中的分位數或觀測值分數,或從直方圖中移除觀測值時,PromQL必須在桶內應用插值。在經典直方圖中,這種插值以線性方式進行。它基於觀測值在桶內均勻分佈的假設。實際上,這個假設可能大相徑庭。(例如,API端點可能對幾乎所有請求都以110毫秒的延遲響應。中位數延遲甚至90%分位數延遲都將接近110毫秒。如果一個經典直方圖的桶邊界在100毫秒和200毫秒之間,它將看到該範圍內的多數觀測值,並估計中位數為150毫秒,90%分位數為190毫秒。)最壞的情況是在桶的一端進行估計,而實際值在桶的另一端。因此,最大可能誤差是整個桶的寬度。不進行任何插值並使用桶內某個固定中點(例如算術平均值甚至調和平均值)將最小化最大可能誤差(在算術平均值的情況下,這將是桶寬度的一半),但實際上,線性插值產生的平均誤差更小。由於插值在經典直方圖多年的使用中效果良好,因此插值也適用於原生直方圖。

對於NHCB,PromQL應用與經典直方圖相同的線性插值方法,以保持結果一致。(NHCB的主要用例是經典直方圖的即插即用替代品。)這包括以下特殊情況:

  • 如果所有自定義值均為正,則最低桶的下限假定為零。(請注意,建議包含一個零的自定義值以避免這種啟發式方法。)
  • 如果至少一個自定義值為零或負,則最低桶的下限假定為-Inf。然而,為了插值目的,最低桶中的所有觀測值都假定等於該桶的上限。
  • 類似地,溢位桶中的所有觀測值都假定等於最後一個自定義值(即最高常規桶的上限)。
  • 對於沒有任何自定義值的NHCB,所有觀測值都假定為零。

對於標準指數模式,線性插值可能被視為不適用。雖然指數模式主要旨在最小化分位數估計的相對誤差,但它們也受益於桶的平衡使用,至少在某些觀測值範圍內。基本假設是,對於大多數實際發生的分佈,觀測值的密度往往在較小的觀測值處更高。因此,PromQL對標準模式使用指數外推法,該方法模擬了以下假設:當模式編號增加1(即解析度加倍)時將桶分成兩個,平均而言,兩個新桶中的填充計數將相似。更詳細的解釋可以在實現插值方法的PR 中找到。

零桶內的插值是一個特殊情況。零桶打破了指數分桶模式。因此,零桶內應用線性插值。此外,如果直方圖的所有填充常規桶都是正數,則假定零桶中的所有觀測值也都是正數,即插值是在零和零桶的上限之間進行的。如果直方圖的所有填充常規桶都是負數,則情況相反,即零桶內的插值是在零桶的下限和零之間進行的。

混合序列

如上所述,樣本型別和原生直方圖的特性都不是序列標識的一部分。因此,同一個序列可能包含不同樣本型別和特性的混合。

計數器直方圖和量規直方圖的混合不會阻止任何PromQL操作,但如果某些輸入樣本具有不適當的特性(參見上文),則結果會返回一個警告級別註釋。

浮點樣本和直方圖樣本的混合更成問題。許多作用於範圍向量的函式會從結果中移除輸入元素中包含浮點和直方圖混合的元素。如果發生這種情況,結果中會新增一個警告級別註釋。具體示例可在下文找到。

一元減號和負直方圖

一元減號可用於原生直方圖。它返回一個直方圖,其中所有桶的填充計數、觀測計數和觀測總和的符號都已反轉。計數器重置提示在任何情況下都設定為GaugeType。其他一切保持不變。強制使用GaugeType是必要的,因為顯式計數器重置檢測會因符號反轉而失效。

通常,具有負桶填充或負觀測計數的直方圖本身並沒有實際意義,它們只應作為其他表示式中的中間結果。在PromQL中,它們始終被視為量規直方圖。它們不能作為記錄規則的結果持久化。(評估為負直方圖的規則會導致錯誤。)在任何交換格式(展示格式、遠端寫入、OTLP)中都無法表示負直方圖。

二元運算子

大多數二元運算子不適用於兩個直方圖之間、直方圖與浮點數之間或直方圖與標量之間。如果運算子處理了這種不可能的組合,相應的元素將從輸出向量中移除,並且結果中會新增一個資訊級別註釋。(這種情況有點類似於標籤匹配,其中樣本型別扮演著類似於標籤的角色。因此,這種不匹配可能是已知且故意的,這就是註釋級別僅為資訊的原因。)

以下描述所有實際有效的操作。

加法和減法

加法(+)和減法(-)在兩個相容的直方圖之間有效。這些運算子會新增或減去所有匹配的桶填充計數、觀測計數和觀測總和。缺失的桶被假定為空,並相應處理。

通常,兩個運算元都應該是量規。加減計數器直方圖需要謹慎,但PromQL允許這樣做。將量規直方圖和計數器直方圖相加會得到一個量規直方圖。將兩個計數器直方圖相加會得到一個計數器直方圖。如果兩個運算元共享相同的計數器重置提示,則結果計數器直方圖會保留該計數器重置提示。否則,結果計數器重置提示將設定為UnknownCounterReset。減法的結果始終被標記為量規直方圖,因為它可能導致負直方圖,參見上文註釋。將兩個具有直接矛盾的計數器重置提示(即CounterResetNotCounterReset)的計數器直方圖相加或相減會觸發警告級別註釋。(待辦事項:如上文所述,TSDB目前不返回NotCounterReset,因此此註釋僅在涉及HistogramStatsIterator的特定情況下發生,該迭代器包括額外的計數器重置跟蹤。參見追蹤問題 。)

乘法

乘法(*)可以在浮點樣本或標量與直方圖之間以任意順序進行。它將所有桶的填充計數、觀測計數和觀測總和乘以浮點數(樣本或標量)。這會導致“縮放”甚至有時是負的直方圖,通常只在其他表示式中作為中間結果有用(另請參見上文註釋)。

乘法對計數器直方圖和量規直方圖都有效,並且它們各自的特性不會因操作而改變,但乘以負值總是會得到一個量規直方圖。

除法

除法(/)適用於左側為直方圖,右側為浮點樣本或標量的情況。它等效於乘以浮點數(樣本或標量)的倒數。除以零會導致一個沒有常規桶的直方圖,並且零桶填充計數、觀測計數和觀測總和都設定為+Inf-InfNaN,具體取決於它們在輸入直方圖中的值(分別為正、負或零/NaN)。

相等和不等

相等(==)和不等(!=)適用於兩個直方圖之間,無論是其過濾版本還是帶有bool修飾符的情況。它們比較模式、自定義值、零閾值、所有桶的填充計數以及觀測總和與計數。直方圖是計數器型別還是量規型別與比較無關。(計數器直方圖可能與量規直方圖相等。)

邏輯和集合運算子

邏輯/集合二元運算子(andorunless)即使涉及直方圖樣本也能按預期工作。它們只檢查向量元素的存在性,並且不會根據元素的樣本型別或特性(浮點數或直方圖、計數器或量規)改變其行為。

修剪運算子

“修剪上限”(</)和“修剪下限”(>/)運算子是專門為原生直方圖引入的。它們只適用於左側為直方圖,右側為浮點樣本或標量的情況。(在所有其他情況下,會返回一個資訊級別註釋。)

這些運算子分別從左側直方圖中移除大於或小於右側浮點值的觀測值,並返回結果直方圖。

只有當閾值與桶邊界重合時,移除才是精確的。否則,必須使用受影響桶內的插值,如上文所述,NHCB有以下例外:

  • 如果最低桶的下限為-Inf,則該桶中的所有觀測值都視為-Inf(而不是上限)。
  • 如果最高桶的上限為+Inf,則該桶中的所有觀測值都視為+Inf(而不是下限)。
  • 然而,在病態極端情況下,如果直方圖只有一個桶,其下限為-Inf且上限為+Inf,所有觀測值仍被視為零。

如果移除了任何觀測值,則結果直方圖中所有觀測值的總和將根據剩餘的桶進行估計。給定桶中每個觀測值的值估計如下:

  • 如果NHCB的最低桶上限為負或零。(儘管修剪時使用了不同的啟發式方法,但這再次遵循了原始的插值啟發式方法,如上所述)。
  • NHCB溢位桶的下限。(再次切換回原始插值啟發式方法。)
  • 標準指數直方圖的負溢位桶為-Inf。
  • 標準指數直方圖的正溢位桶為+Inf。
  • NHCB所有其他桶以及標準指數直方圖零桶的算術平均值,同時考慮以下特殊情況的已知啟發式方法:
    • 如果NHCB的最低桶上限為正,則其下限被視為零。
    • 如果直方圖沒有填充的負桶,則標準指數直方圖零桶的下限被視為零。
    • 如果直方圖沒有填充的正桶,則標準指數直方圖零桶的上限被視為零。
  • 標準指數直方圖所有其他桶的幾何平均值。

此外,對於僅被部分移除的桶(使用上述插值法)的(算術或幾何)平均值計算所使用的邊界,將按以下方式修改:相關的上限(對於</)或下限(對於>/)被視為等於作為第二個運算元提供的截斷閾值。

請注意,這種觀測總和的估計是不準確的(即使在截斷閾值與桶邊界重合的情況下),甚至可能產生明顯錯誤的結果。例如,在移除了一些正向觀測值後,估計的觀測總和可能大於原始直方圖中的觀測總和。估計算法可以細化以考慮這些情況,但為了便於理解,它被有意地保持簡單。通常,修剪操作產生的直方圖旨在用於分位數估計或過濾,其中觀測總和無論如何都是無關緊要的。修剪運算子也是histogram_fraction函式(下文解釋)的一個可行替代方案,適用於所需結果不是觀測值分數而是觀測值計數的情況。例如,使用histogram_count(h >/ 0.2 </ 0.5)代替histogram_fraction(0.2, 0.5, h) * histogram_count(h)更簡單,並且在h沒有觀測值的情況下也會得到0而不是NaN。(從空直方圖計算的histogram_fraction始終為NaN。)

修剪運算子會保留直方圖的計數器或量規特性。

聚合運算子

以下聚合運算子對浮點樣本和直方圖樣本的工作方式相同(原因在括號中說明):

  • group(此聚合的結果不依賴於樣本值。)
  • count(此聚合的結果不依賴於樣本值。)
  • count_values(Go的FloatHistogram.String方法生成的文字表示用作直方圖的值。)
  • limitk(取樣元素不變地返回。)
  • limit_ratio(取樣元素不變地返回。)

sum聚合運算子對原生直方圖的作用方式與上述+運算子的描述相同,透過對要聚合的直方圖進行求和。avg聚合運算子的工作方式相同,但將總和除以聚合直方圖的數量(與上述/運算子的描述相同)。

sumavg都會從輸出向量中移除需要聚合浮點樣本和直方圖樣本的元素。這種移除會透過警告級別註釋進行標記。

sumavg都應該只應用於量規直方圖。PromQL允許聚合計數器直方圖(甚至兩者的混合),但這需要謹慎以有意義的方式進行。量規與計數器特性以及結果計數器重置提示的影響源自上述+運算子的影響:

  • 如果所有聚合的直方圖共享相同的計數器重置提示,則結果保留該相同的計數器重置提示。
  • 如果聚合的直方圖中至少有一個量規直方圖,則結果為量規直方圖。
  • 在所有其他情況下,結果的計數器重置提示將設定為UnknownCounterReset
  • 在任何情況下,聚合直方圖中任何直接矛盾的計數器重置提示(即CounterResetNotCounterReset)都會觸發警告級別註釋。

所有其他聚合運算子都適用於原生直方圖。輸入向量中的直方圖會被簡單地忽略,並且每個被忽略的直方圖都會新增一個資訊級別註釋。

函式

以下函式透過將通常的浮點操作分別應用於匹配的桶(包括零桶)以及觀測值的總和和計數來操作原生直方圖的範圍向量,從而生成一個新的原生直方圖:

  • delta()(適用於量規直方圖。)
  • increase()(適用於計數器直方圖。)
  • rate()(適用於計數器直方圖。)
  • idelta()(適用於量規直方圖。)
  • irate()(適用於計數器直方圖。)

如上所述,這些函式應用於量規直方圖或計數器直方圖。然而,它們都適用於這兩種特性,但如果範圍向量中包含至少一個不適合特性的直方圖,則結果中會新增一個警告級別註釋。

delta()increase()rate()對於包含範圍內的浮點樣本和直方圖樣本混合的序列不返回結果。idelta()irate()對於範圍內最後兩個樣本是浮點樣本和直方圖樣本混合的序列不返回結果。在任何一種情況下,都會為因此缺失的每個輸出元素新增一個警告級別註釋。

所有這些函式都返回量規直方圖作為結果。

像往常一樣,這些函式會嘗試透過儘可能將模式轉換為通用模式來協調不同的模式。然而,應用於計數器(increase()rate()irate())的函式在第一個樣本和第二個樣本之間存在計數器重置時,不會對第一個樣本執行此轉換。在這種情況下,第一個樣本不包含在計算中,因此第一個樣本與其他樣本之間不相容的桶佈局會被默默地忽略。

為了防止外推到零以下,應用與浮點計數器 相同的啟發式方法,但僅基於觀測計數。因此,在某些情況下,單個桶仍可能外推到零以下。另一種選擇是找到最小的外推值,使得計數和任何桶都不會外推到零以下。然而,這不一定能產生更好的啟發式方法,同時會帶來顯著的複雜性成本。在常見且重要的情況下,如果範圍內的第一個樣本是來自建立時間戳的合成零樣本,則有限的外推實際上會非常精確,因為計數和所有桶在合成樣本的時間戳處都為零,這也是外推的限制點。請注意,經典直方圖將啟發式方法獨立應用於每個桶以及計數和總和(因為它們都是單獨的序列)。已知這會導致不一致。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觀測值的方式一致(在大多數實現中,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()應用於結果。例如,以下查詢計算原生直方圖中的觀測速率(在本例中對應於“每秒請求數”):

histogram_count(rate(http_request_duration_seconds[10m]))

請注意,當對histogram_sum()的結果使用子查詢時,原生直方圖的特殊計數器重置檢測不適用,即負觀測值可能導致虛假計數器重置。

histogram_avg()返回原生直方圖中觀測值的算術平均值。(這與將avg聚合運算子應用於多個原生直方圖有顯著不同。後者返回的是一個平均後的直方圖。)

類似地,histogram_stddev()histogram_stdvar()分別返回原生直方圖中觀測值的估計標準差或標準方差。對於此估計,桶中的所有觀測值都被假定為桶邊界平均值。對於零桶和具有自定義邊界的桶,使用算術平均值。對於標準指數桶,使用幾何平均值。

histogram_fraction(lower, upper, histogram)返回histogram中位於提供的邊界(標量值lowerupper)之間的觀測值的估計比例。估計的誤差取決於底層原生直方圖的解析度以及提供的邊界與直方圖中桶邊界的對齊程度。+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的最大值為0.1,因為histogram_fractionNaN觀測值視為在任何桶之外。如果例如histogram_quantile(0.5, histogram)返回任何實數y,那麼根據上述論點,我們應該找到某個數字x,使得y<=x並且histogram_fraction(-Inf, x, histogram)等於0.5,然而這對於任何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_with_nhcb 的替代 load 命令,它將經典直方圖轉換為 NHCB,並載入經典直方圖的浮點序列以及轉換後生成的 NHCB 序列。

雖然不特定於原生直方圖,但在其上下文中非常有用的是單元測試框架中的 expect 關鍵字,它可以定義關於資訊級和警告級註解的預期。

最佳化

與往常一樣,PromQL 實現可以應用任何其認為合適的最佳化,只要行為保持不變。解碼原生直方圖可能會非常昂貴,因為桶的數量可能很多。同樣,在 PromQL 引擎中深度複製直方圖樣本比複製簡單的浮點樣本要昂貴得多。這為最佳化創造了巨大的潛力,相比於總是解碼和複製所有內容的幼稚方法。

Prometheus 當前嘗試避免不必要的複製(TODO:但仍需實現像 CoW 這樣的正確方法,因為它會更清晰,bug 更少),並跳過在只需要觀測值的總和和計數這種特殊情況下桶的解碼。

Prometheus 查詢 API

查詢 API 文件中包含原生直方圖支援。本節重點介紹與原生直方圖相關的部分,並提供一些 API 文件中未包含的背景資訊。

即時查詢和範圍查詢

為了在即時(query 端點)和範圍(query_range 端點)查詢的 JSON 響應中返回原生直方圖,vectormatrix 結果型別都需要透過一個新的鍵進行擴充套件。

vector 結果型別在與現有 value 鍵相同的級別上獲得一個新的鍵 histogram。這兩個鍵是互斥的,即 vector 中的每個元素要麼有一個 value 鍵(用於浮點結果),要麼有一個 histogram 鍵(用於直方圖結果)。histogram 鍵的值結構與 value 鍵的值結構類似(一個包含兩個元素的陣列),不同之處在於表示浮點樣本值的字串被下面描述的特定直方圖物件替換。

matrix 結果型別在與現有 values 鍵相同的級別上獲得一個新的鍵 histograms。這些鍵是互斥的。一個序列可能同時包含浮點值和直方圖值,但對於給定的時間戳,必須只有一個樣本,要麼是浮點數,要麼是直方圖。histograms 鍵的值結構與 values 鍵的值結構類似(一個包含 n 個兩元素陣列的陣列),不同之處在於表示浮點樣本值的字串被下面描述的特定直方圖物件替換。

請注意,更好的鍵命名應該是 float/histogramfloats/histograms,因為浮點值和直方圖值都是值。當前的命名是歷史原因造成的。(過去,只有一種值型別,即浮點數,所以簡單地將鍵命名為 valuevalues 是顯而易見的選擇。)這裡的目的是不破壞那些不知道原生直方圖的現有消費者。

上面提到的直方圖物件具有以下結構

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

countsum 直接對應於同名的直方圖欄位。每個桶都明確地表示其邊界和計數,包括零桶。因此,跨度和 schema 不屬於響應的一部分,直方圖物件的結構不依賴於所使用的 schema。

<boundary_rule> 佔位符是一個介於 0 和 3 之間的整數,具有以下含義

  • 0:“左開”(左邊界不包含,右邊界包含)
  • 1:“右開”(左邊界包含,右邊界不包含)
  • 2:“兩邊開”(兩邊界均不包含)
  • 3:“兩邊閉”(兩邊界均包含)

對於標準 schema,正桶是“左開”,負桶是“右開”,而零桶(具有負左邊界和正右邊界)是“兩邊閉”。對於 NHCB,所有桶都是“左開”(反映了經典直方圖的行為)。未來的 schema 可能會使用不同的邊界規則。

元資料

對於 series 端點,包含原生直方圖的序列與只包含浮點數的傳統序列以相同的方式包含在內。該端點不提供任何關於包含哪些樣本型別的資訊(實際上,任何序列都可以包含其中一種或兩種樣本型別)。特別需要注意的是,如果目標以 request_duration_seconds 名稱暴露並作為原生直方圖攝取,則會產生名為 request_duration_seconds 的序列;但如果以經典直方圖暴露並攝取,則會導致一組名為 request_duration_seconds_sumrequest_duration_seconds_countrequest_duration_seconds_bucket 的序列。如果直方圖同時作為原生直方圖和經典直方圖攝取,則上述所有序列名稱都將由 series 端點返回。

目標和指標元資料(端點 targets/metadatametadata)的工作方式略有不同,因為它們作用於目標暴露的原始名稱。這意味著名為 request_duration_seconds 的經典直方圖將僅由這些元資料端點表示為 request_duration_seconds(而不是 request_duration_seconds_sumrequest_duration_seconds_countrequest_duration_seconds_bucket)。原生直方圖 request_duration_seconds 也將以此名稱表示。即使 request_duration_seconds 同時作為經典直方圖和原生直方圖攝取,也不會發生衝突,因為返回的元資料實際上是相同的(最值得注意的是返回的 type 將是 histogram)。換句話說,目前無法僅透過元資料端點區分原生直方圖和經典直方圖。需要透過 series 端點進行額外查詢。目前沒有計劃改變這一點,因為現有的元資料端點本來就嚴重受限(沒有歷史資訊,沒有規則建立的指標的元資料,處理不同目標之間衝突元資料的能力有限)。然而,計劃是全面改進 Prometheus 中的元資料處理。這些努力也將考慮到如何正確支援原生直方圖。(TODO:隨著進展進行更新。)

Prometheus UI

本節描述 Prometheus 自己的 UI 對直方圖的渲染。這可以作為第三方圖形前端的指導方針。

表格檢視中,直方圖資料點以條形圖的形式圖形化呈現,並附帶所有桶的文字表示,包括它們的下限和上限以及觀測值的計數和總和。條形圖中的每個條形代表一個桶。每個條形在 x 軸上的位置由對應桶的下限和上限決定。每個條形的面積與對應桶的總體數量成比例(這是渲染直方圖的一般核心原則)。

圖形直方圖允許選擇指數或線性 x 軸。前者是預設值。它非常適合標準 schema。(TODO:考慮將線性作為非指數 schema 的預設值。)方便的是,指數 schema 的所有常規桶在指數 x 軸上具有相同的寬度。這意味著 y 軸可以顯示實際的桶總體數量,而不會違反上述原則,即條形的面積(而不是高度)代表桶的總體數量。零桶是例外。從技術上講,它具有無限寬度。Prometheus 簡單地以與常規指數桶相同的寬度渲染它(這反過來意味著 x 軸在零點附近並非嚴格指數)。(TODO:如何為非指數 schema 進行渲染。)

使用線性 x 軸時,桶的寬度通常不同。因此,y 軸顯示桶的總體數量除以其寬度。Prometheus UI 不會在 y 軸上渲染值,因為它們對於人類來說無論如何都難以解釋。總體數量仍可在文字表示中檢視。

圖表檢視中,Prometheus 顯示一個熱力圖(TODO:尚未實現,見下文),可以將其視為一系列隨時間變化的直方圖,旋轉 90 度,並將桶的總體數量編碼為顏色而不是條形高度。將類似計數器的直方圖渲染為熱力圖的典型查詢是 rate 查詢。熱力圖是一種極其強大的表示方法,可以讓人類輕鬆發現分佈隨時間變化的特徵。

待辦事項熱力圖尚未實現。相反,UI 僅將觀測值的總和繪製為常規圖表。請參閱跟蹤問題 。同一個問題還討論瞭如何在表格檢視中處理範圍向量的渲染。

模板展開

原生直方圖在模板展開中起作用。它們以受開區間和閉區間數學符號啟發而成的文字表示形式呈現。(這由 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。

待辦事項遠端寫入的一個剩餘可能問題是,如果最初為同一個原生直方圖攝取了多個示例,但在不同的遠端寫入請求中傳送,該如何處理。

聯邦

只要聯邦抓取使用 protobuf 格式,原生直方圖的聯邦功能就會按預期工作。一旦 OpenMetrics 文字格式支援原生直方圖,理論上也可以透過 OpenMetrics 文字格式進行聯邦,但出於效率原因,仍然首選透過 protobuf 進行聯邦。

待辦事項一旦 OM 支援 NH,請更新。

NHCB 在透過聯邦端點暴露時被渲染為經典的浮點直方圖。抓取器可以選擇將其轉換回 NHCB,或將其作為經典直方圖攝取。然而,後者可能導致命名衝突。請注意,OpenMetrics v1 不支援經典的浮點直方圖。幸運的是,Prometheus 聯邦無論如何都不使用 OpenMetrics v1,而是使用 protobuf 格式或經典文字格式。

OTLP

Prometheus 內建的 OTLP 接收器利用上面描述的相容性,將傳入的 OTel 指數直方圖轉換為 Prometheus 原生直方圖。使用 schema(OTel 術語中的“scale”)大於 8 的直方圖的解析度將被降低以匹配 schema 8。(在極不可能使用小於 -4 的 schema 的情況下,攝取將失敗。)

顯式 OTel 直方圖等同於 Prometheus 的經典直方圖。因此,Prometheus 預設將它們轉換為經典直方圖,但可選地提供直接轉換為 NHCB。

Pushgateway

原生直方圖支援已逐步新增到Pushgateway 。在 v1.9 中實現了全面支援。Pushgateway 始終基於經典的 protobuf 格式作為其內部資料模型,這使得必要的更改變得容易(主要涉及 UI 方面)。組合直方圖(帶有經典桶和原生桶)可以推送,並將透過 /metrics 端點暴露。 (然而,可用於以 JSON 形式查詢已推送指標的查詢 API 將只能返回一種桶型別,如果存在原生桶,則優先返回原生桶。)

promtool

本節描述為支援原生直方圖而新增或更改的 promtool 命令。未明確提及的命令不直接與原生直方圖互動,也無需更改。

promtool query ... 命令與原生直方圖配合使用。請參閱查詢 API 文件以瞭解輸出格式。專門添加了一個新命令 promtool query analyze,用於分析查詢 API 返回的經典和原生直方圖使用模式。

透過 promtool test rules 進行的規則單元測試與原生直方圖配合使用,採用上面描述的格式。

promtool tsdb analyzepromtool tsdb list 與原生直方圖正常工作。前者的 --extended 輸出具有直方圖塊的特定部分。

promtool tsdb dump 使用原生直方圖的常規文字表示(由 Go 方法 FloatHistogram.String 生成)。

promtool tsdb create-blocks-from rules 與發出原生直方圖的規則配合使用。

promtool promql ... 命令支援為原生直方圖新增的所有 PromQL 功能。

儘管 promtool tsdb bench write 原則上可以包含原生直方圖,但目前不計劃支援。

以下命令依賴於 OpenMetrics 文字格式,因此只要 OpenMetrics 中沒有原生直方圖支援,就無法支援原生直方圖

  • promtool check metrics
  • promtool push metrics
  • promtool tsdb dump-openmetrics
  • promtool tsdb create-blocks-from openmetrics
待辦事項請隨著進展進行更新。請參閱跟蹤問題 

prom2json

prom2json 是一個小型工具,它抓取 Prometheus 的 /metrics 端點,將指標轉換為特製的 JSON 格式,並將其轉儲到標準輸出。這對於使用處理 JSON 的工具(例如 jq)進行進一步處理非常方便。

prom2json v1.4 添加了對原生直方圖的支援。如果暴露的直方圖中包含至少一個桶跨度,prom2json 將在 JSON 輸出中用原生直方圖的桶替換通常的經典桶,其格式靈感來源於Prometheus 查詢 API

遷移注意事項

從經典直方圖遷移到原生直方圖時,需要考慮三個重要的問題來源

  1. 查詢原生直方圖的工作方式與查詢經典直方圖不同。在大多數情況下,更改是最小且直接的,但存在棘手的邊緣情況,這使得難以執行可靠的自動轉換。
  2. 經典直方圖和原生直方圖不能相互聚合。在某個時間點從經典直方圖更改為原生直方圖會使得難以建立跨越轉換點的儀表盤,並且包含轉換點的範圍向量將不可避免地不完整(即,選擇經典直方圖的範圍向量將只包含範圍較早部分的資料點,而選擇原生直方圖的範圍向量將只包含範圍較晚部分的資料點)。
  3. 經典直方圖可能被定製為在感興趣點精確設定桶邊界。具有標準 schema 的原生直方圖可以具有高解析度,但不能在任意值處設定桶邊界。在這些情況下,原生直方圖的使用者體驗實際上可能會更差。

為了解決 (3),當然可以選擇不遷移相關的經典直方圖,並保持現狀。另一種選擇是保持儀器不變,但在攝取時將經典直方圖轉換為 NHCB。這利用了原生直方圖更高的儲存效能,但仍然需要像完全遷移到原生直方圖那樣解決 (1) 和 (2)(參見下文段落)。

解決 (1) 和 (2) 的保守方法是允許一個較長的過渡期,這代價是需要同時收集和儲存經典直方圖和原生直方圖一段時間。

第一步是更新儀器,使其同時暴露經典直方圖和原生直方圖。(如果計劃是堅持在儀器中使用經典直方圖並在抓取時將其轉換為 NHCB,則可以跳過此步驟。)

然後配置 Prometheus 以抓取經典直方圖和原生直方圖,參見上面關於同時抓取經典直方圖和原生直方圖的部分。(如果需要,還可以啟用將經典直方圖轉換為 NHCB。)

涉及經典直方圖的現有查詢將繼續工作,但從現在開始,使用者可以開始使用原生直方圖,並開始更改儀表盤、告警、記錄規則中的查詢等。如上所述,重要的是要注意具有較長範圍向量的查詢,例如 histogram_quantile(0.9, rate(rpc_duration_seconds[1d]))。此查詢計算過去一天的第 90 百分位延遲。然而,如果原生直方圖尚未收集至少一天,則查詢將僅覆蓋較短的時期。因此,只有在原生直方圖已收集至少 1 天后才應使用此查詢。對於顯示過去一個月每日第 90 百分位延遲的儀表盤,很可能會嘗試編寫一個在正確時刻從經典直方圖正確切換到原生直方圖的查詢。雖然這在原則上是可能的,但很棘手。如果可行,經典直方圖和原生直方圖並行收集的過渡期可以相當長,以最大限度地減少實現棘手切換的必要性。例如,一旦經典直方圖和原生直方圖並行收集了一個月,任何不檢視超過一個月的儀表盤都可以簡單地從經典直方圖查詢切換到原生直方圖查詢,而無需考慮正確的切換。

一旦確認所有查詢都已正確遷移,請將 Prometheus 配置為僅抓取原生直方圖(這是“正常”設定)。(也可以透過抓取配置中的 relabel 規則逐步移除經典直方圖。)如果一切仍然正常工作,就可以從儀器中移除經典直方圖了。

Grafana Mimir 文件包含一份詳細的遷移指南 ,其理念與本節所述相同。

本頁內容