Prometheus 2 中的 TSDB 分析

Prometheus 2 中的 TSDB 分析

Prometheus 2 中的時間序列資料庫(TSDB)是工程解決方案的一個很好的例子,它在資料累積速度、查詢執行和資源效率方面比 Prometheus 2 中的 v1 儲存有了重大改進。 我們在 Percona 監控和管理 (PMM) 中實作 Prometheus 2,我有機會了解 Prometheus 2 TSDB 的效能。 在這篇文章中,我將討論這些觀察的結果。

Prometheus 平均工作負載

對於那些習慣於處理通用資料庫的人來說,典型的 Prometheus 工作負載非常有趣。 資料累積的速度趨於穩定:通常您監控的服務發送大致相同數量的指標,並且基礎設施變化相對緩慢。
資訊請求可能來自各種來源。 其中一些(例如警報)也力求穩定且可預測的值。 其他情況(例如使用者請求)可能會導致突發,但大多數工作負載並非如此。

負載測試

在測試過程中,我專注於累積數據的能力。 我使用以下腳本在 Linode 服務上部署了用 Go 2.3.2(作為 PMM 1.10.1 的一部分)編譯的 Prometheus 1.14: 堆疊腳本。 對於最真實的負載生成,請使用此 堆疊腳本 我啟動了幾個具有真實負載的 MySQL 節點(Sysbench TPC-C 測試),每個節點模擬 10 個 Linux/MySQL 節點。
以下所有測試均在具有 32 個虛擬核心和 20 GB 記憶體的 Linode 伺服器上進行,運行 800 個負載模擬,監控 440 個 MySQL 實例。 或者,用 Prometheus 的話來說,380 個目標,每秒 1,7 次抓取,每秒 XNUMX 萬筆記錄,以及 XNUMX 萬個活動時間序列。

設計

傳統資料庫(包括 Prometheus 1.x 使用的方法)的常用方法是 記憶體限制。 如果不足以處理負載,您將遇到高延遲,並且某些請求將失敗。 Prometheus 2 中的記憶體使用可透過 key 配置 storage.tsdb.min-block-duration,它決定錄製內容在刷新到磁碟之前將在記憶體中保留多長時間(預設為 2 小時)。 所需的內存量取決於添加到網路傳入流中的時間序列、標籤和抓取的數量。 在磁碟空間方面,Prometheus 的目標是每筆記錄(樣本)使用 3 個位元組。 另一方面,記憶體要求要高得多。

儘管可以配置區塊大小,但不建議手動配置,因此您被迫為 Prometheus 提供工作負載所需的盡可能多的記憶體。
如果沒有足夠的記憶體來支援傳入的指標流,Prometheus 將出現記憶體不足或 OOM 殺手將會取得記憶體。
當 Prometheus 記憶體不足時添加交換來延遲崩潰並沒有真正的幫助,因為使用此功能會導致爆炸性的記憶體消耗。 我認為這與 Go、它的垃圾收集器以及它處理交換的方式有關。
另一個有趣的方法是將頭塊配置為在某個時間刷新到磁碟,而不是從進程開始時開始計數。

Prometheus 2 中的 TSDB 分析

從圖中可以看出,每兩小時就會刷新一次磁碟。 如果將 min-block-duration 參數更改為一小時,那麼這些重置將每小時發生一次,從半小時後開始。
如果您想在 Prometheus 安裝中使用此圖表和其他圖表,您可以使用此 儀表板。 它是為 PMM 設計的,但只需稍作修改,即可適合任何 Prometheus 安裝。
我們有一個稱為頭塊的活動塊,它儲存在記憶體中; 具有較舊資料的區塊可透過 mmap()。 這消除了單獨配置快取的需要,但也意味著如果要查詢早於頭塊可容納的數據,則需要為作業系統快取留出足夠的空間。
這也意味著 Prometheus 虛擬記憶體消耗看起來會相當高,這並不是什麼值得擔心的事情。

Prometheus 2 中的 TSDB 分析

另一個有趣的設計點是WAL(預寫日誌)的使用。 從儲存文件中可以看到,Prometheus 使用 WAL 來避免崩潰。 不幸的是,用於保證資料生存性的具體機制沒有很好的記錄。 Prometheus 版本 2.3.2 每 10 秒將 WAL 刷新到磁碟,且此選項不可由使用者配置。

壓實

Prometheus TSDB 的設計類似於 LSM(日誌結構化合併)儲存:頭塊定期刷新到磁碟,而壓縮機制將多個區塊組合在一起,以避免在查詢期間掃描太多區塊。 在這裡您可以看到我在一天負載後在測試系統上觀察到的區塊數。

Prometheus 2 中的 TSDB 分析

如果您想了解有關商店的更多信息,可以檢查 meta.json 文件,其中包含有關可用區塊及其形成方式的資訊。

{
       "ulid": "01CPZDPD1D9R019JS87TPV5MPE",
       "minTime": 1536472800000,
       "maxTime": 1536494400000,
       "stats": {
               "numSamples": 8292128378,
               "numSeries": 1673622,
               "numChunks": 69528220
       },
       "compaction": {
               "level": 2,
               "sources": [
                       "01CPYRY9MS465Y5ETM3SXFBV7X",
                       "01CPYZT0WRJ1JB1P0DP80VY5KJ",
                       "01CPZ6NR4Q3PDP3E57HEH760XS"
               ],
               "parents": [
                       {
                               "ulid": "01CPYRY9MS465Y5ETM3SXFBV7X",
                               "minTime": 1536472800000,
                               "maxTime": 1536480000000
                       },
                       {
                               "ulid": "01CPYZT0WRJ1JB1P0DP80VY5KJ",
                               "minTime": 1536480000000,
                               "maxTime": 1536487200000
                       },
                       {
                               "ulid": "01CPZ6NR4Q3PDP3E57HEH760XS",
                               "minTime": 1536487200000,
                               "maxTime": 1536494400000
                       }
               ]
       },
       "version": 1
}

Prometheus 中的壓縮與頭塊刷新到磁碟的時間有關。 此時,可以執行多次這樣的操作。

Prometheus 2 中的 TSDB 分析

看來壓縮不受任何限制,並且可能會在執行期間導致大量磁碟 I/O 峰值。

Prometheus 2 中的 TSDB 分析

CPU負載峰值

Prometheus 2 中的 TSDB 分析

當然,這對系統的速度造成了相當負面的影響,同時也對LSM儲存提出了嚴峻的挑戰:如何進行壓縮以支援高請求率而不造成太大的開銷?
壓縮過程中記憶體的使用看起來也很有趣。

Prometheus 2 中的 TSDB 分析

我們可以看到,在壓縮之後,大部分記憶體的狀態如何從「快取」更改為「空閒」:這意味著潛在有價值的資訊已從其中刪除。 好奇這裡是否使用它 fadvice() 或其他一些最小化技術,或者是因為快取已從壓縮過程中被破壞的區塊中釋放出來?

故障後恢復

從失敗中恢復需要時間,這是有充分理由的。 對於每秒一百萬筆記錄的傳入流,考慮到 SSD 驅動器,我必須等待大約 25 分鐘才能執行恢復。

level=info ts=2018-09-13T13:38:14.09650965Z caller=main.go:222 msg="Starting Prometheus" version="(version=2.3.2, branch=v2.3.2, revision=71af5e29e815795e9dd14742ee7725682fa14b7b)"
level=info ts=2018-09-13T13:38:14.096599879Z caller=main.go:223 build_context="(go=go1.10.1, user=Jenkins, date=20180725-08:58:13OURCE)"
level=info ts=2018-09-13T13:38:14.096624109Z caller=main.go:224 host_details="(Linux 4.15.0-32-generic #35-Ubuntu SMP Fri Aug 10 17:58:07 UTC 2018 x86_64 1bee9e9b78cf (none))"
level=info ts=2018-09-13T13:38:14.096641396Z caller=main.go:225 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2018-09-13T13:38:14.097715256Z caller=web.go:415 component=web msg="Start listening for connections" address=:9090
level=info ts=2018-09-13T13:38:14.097400393Z caller=main.go:533 msg="Starting TSDB ..."
level=info ts=2018-09-13T13:38:14.098718401Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536530400000 maxt=1536537600000 ulid=01CQ0FW3ME8Q5W2AN5F9CB7R0R
level=info ts=2018-09-13T13:38:14.100315658Z caller=web.go:467 component=web msg="router prefix" prefix=/prometheus
level=info ts=2018-09-13T13:38:14.101793727Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536732000000 maxt=1536753600000 ulid=01CQ78486TNX5QZTBF049PQHSM
level=info ts=2018-09-13T13:38:14.102267346Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536537600000 maxt=1536732000000 ulid=01CQ78DE7HSQK0C0F5AZ46YGF0
level=info ts=2018-09-13T13:38:14.102660295Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536775200000 maxt=1536782400000 ulid=01CQ7SAT4RM21Y0PT5GNSS146Q
level=info ts=2018-09-13T13:38:14.103075885Z caller=repair.go:39 component=tsdb msg="found healthy block" mint=1536753600000 maxt=1536775200000 ulid=01CQ7SV8WJ3C2W5S3RTAHC2GHB
level=error ts=2018-09-13T14:05:18.208469169Z caller=wal.go:275 component=tsdb msg="WAL corruption detected; truncating" err="unexpected CRC32 checksum d0465484, want 0" file=/opt/prometheus/data/.prom2-data/wal/007357 pos=15504363
level=info ts=2018-09-13T14:05:19.471459777Z caller=main.go:543 msg="TSDB started"
level=info ts=2018-09-13T14:05:19.471604598Z caller=main.go:603 msg="Loading configuration file" filename=/etc/prometheus.yml
level=info ts=2018-09-13T14:05:19.499156711Z caller=main.go:629 msg="Completed loading of configuration file" filename=/etc/prometheus.yml
level=info ts=2018-09-13T14:05:19.499228186Z caller=main.go:502 msg="Server is ready to receive web requests."

恢復過程的主要問題是記憶體消耗較高。 儘管在正常情況下伺服器可以在相同記憶體量下穩定工作,但如果崩潰則可能會因OOM而無法恢復。 我找到的唯一解決方案是停用資料收集,啟動伺服器,讓它恢復並在啟用收集的情況下重新啟動。

熱身

預熱期間要記住的另一個行為是啟動後低效能和高資源消耗之間的關係。 在某些(但不是全部)啟動過程中,我觀察到 CPU 和記憶體負載嚴重。

Prometheus 2 中的 TSDB 分析

Prometheus 2 中的 TSDB 分析

記憶體使用量的差距表明 Prometheus 無法從一開始就配置所有集合,並且一些資訊遺失。
我還沒有弄清楚CPU和記憶體負載高的確切原因。 我懷疑這是由於在頭部區塊中以高頻率創建了新的時間序列。

CPU負載激增

除了會產生相當高的 I/O 負載的壓縮之外,我還注意到 CPU 負載每兩分鐘就會出現嚴重的峰值。 當輸入流量較高時,突發時間較長,似乎是由 Go 的垃圾收集器引起的,至少有一些核心已滿載。

Prometheus 2 中的 TSDB 分析

Prometheus 2 中的 TSDB 分析

這些跳躍並不是那麼微不足道。 看來,當這些情況發生時,Prometheus 的內部入口點和指標將變得不可用,從而導致同一時間段內的資料缺口。

Prometheus 2 中的 TSDB 分析

您也可以注意到 Prometheus 匯出器關閉一秒鐘。

Prometheus 2 中的 TSDB 分析

我們可以注意到與垃圾收集 (GC) 的相關性。

Prometheus 2 中的 TSDB 分析

結論

Prometheus 2 中的 TSDB 速度很快,能夠使用相當普通的硬體處理數百萬個時間序列,同時每秒處理數千筆記錄。 CPU 和磁碟 I/O 利用率也令人印象深刻。 我的範例顯示每個使用的核心每秒最多可處理 200 個指標。

為了計劃擴展,您需要記住足夠的內存量,並且這必須是真實內存。 我觀察到傳入流每秒每 5 筆記錄使用的記憶體量約為 100 GB,加上作業系統緩存,佔用的記憶體約為 000 GB。

當然,要抑制 CPU 和磁碟 I/O 峰值還有很多工作要做,考慮到 TSDB Prometheus 2 與 InnoDB、TokuDB、RocksDB、WiredTiger 相比還很年輕,這並不奇怪,但它們都有相似的地方生命週期早期的問題。

來源: www.habr.com

添加評論