Prometheus是著名開源監控項目,其監控任務調度給具體的服務器,該服務器到目標上抓取監控數據,而後保存在本地的TSDB中。自定義強大的PromQL語言查詢實時和歷史時序數據,支持豐富的查詢組合。
Prometheus 1.0版本的TSDB(V2存儲引擎)基於LevelDB,而且使用了和Facebook Gorilla同樣的壓縮算法,可以將16個字節的數據點壓縮到平均1.37個字節。
Prometheus 2.0版本引入了全新的V3存儲引擎,提供了更高的寫入和查詢性能。本文主要分析該存儲引擎設計思路。nginx
Prometheus將Timeseries數據按2小時一個block進行存儲。每一個block由一個目錄組成,該目錄裏包含:一個或者多個chunk文件(保存timeseries數據)、一個metadata文件、一個index文件(經過metric name和labels查找timeseries數據在chunk文件的位置)。最新寫入的數據保存在內存block中,達到2小時後寫入磁盤。爲了防止程序崩潰致使數據丟失,實現了WAL(write-ahead-log)機制,將timeseries原始數據追加寫入log中進行持久化。刪除timeseries時,刪除條目會記錄在獨立的tombstone文件中,而不是當即從chunk文件刪除。
這些2小時的block會在後臺壓縮成更大的block,數據壓縮合併成更高level的block文件後刪除低level的block文件。這個和leveldb、rocksdb等LSM樹的思路一致。
這些設計和Gorilla的設計高度類似,因此Prometheus幾乎就是等於一個緩存TSDB。它本地存儲的特色決定了它不能用於long-term數據存儲,只能用於短時間窗口的timeseries數據保存和查詢,而且不具備高可用性(宕機會致使歷史數據沒法讀取)。
Prometheus本地存儲的侷限性,因此它提供了API接口用於和long-term存儲集成,將數據保存到遠程TSDB上。該API接口使用自定義的protocol buffer over HTTP而且並不穩定,後續考慮切換爲gRPC。算法
內存中的block數據未刷盤時,block目錄下面主要保存wal文件。數據庫
./data/01BKGV7JBM69T2G1BGBGM6KB12 ./data/01BKGV7JBM69T2G1BGBGM6KB12/meta.json ./data/01BKGV7JBM69T2G1BGBGM6KB12/wal/000002 ./data/01BKGV7JBM69T2G1BGBGM6KB12/wal/000001
持久化的block目錄下wal文件被刪除,timeseries數據保存在chunk文件裏。index用於索引timeseries在wal文件裏的位置。json
./data/01BKGV7JC0RY8A6MACW02A2PJD ./data/01BKGV7JC0RY8A6MACW02A2PJD/meta.json ./data/01BKGV7JC0RY8A6MACW02A2PJD/index ./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks ./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks/000001 ./data/01BKGV7JC0RY8A6MACW02A2PJD/tombstones
使用mmap讀取壓縮合並後的大文件(不佔用太多句柄),創建進程虛擬地址和文件偏移的映射關係,只有在查詢讀取對應的位置時纔將數據真正讀到物理內存。繞過文件系統page cache,減小了一次數據拷貝。查詢結束後,對應內存由Linux系統根據內存壓力狀況自動進行回收,在回收以前可用於下一次查詢命中。所以使用mmap自動管理查詢所需的的內存緩存,具備管理簡單,處理高效的優點。
從這裏也能夠看出,它並非徹底基於內存的TSDB,和Gorilla的區別在於查詢歷史數據須要讀取磁盤文件。緩存
Compaction主要操做包括合併block、刪除過時數據、重構chunk數據。其中合併多個block成爲更大的block,能夠有效減小block個數,當查詢覆蓋的時間範圍較長時,避免須要合併不少block的查詢結果。
爲提升刪除效率,刪除時序數據時,會記錄刪除的位置,只有block全部數據都須要刪除時,纔將block整個目錄刪除。所以block合併的大小也須要進行限制,避免保留了過多已刪除空間(額外的空間佔用)。比較好的方法是根據數據保留時長,按百分比(如10%)計算block的最大時長。bash
Inverted Index(倒排索引)基於其內容的子集提供數據項的快速查找。簡而言之,我能夠查看全部標籤爲app=「nginx」的數據,而沒必要遍歷每個timeseries,並檢查是否包含該標籤。
爲此,每一個時間序列key被分配一個惟一的ID,經過它能夠在恆定的時間內檢索,在這種狀況下,ID就是正向索引。
舉個栗子:如ID爲9,10,29的series包含label app="nginx",則lable "nginx"的倒排索引爲[9,10,29]用於快速查詢包含該label的series。服務器
在文章Writing a Time Series Database from Scratch裏,做者給出了benchmark測試結果爲Macbook Pro上寫入達到2000萬每秒。這個數據比Gorilla論文中的目標7億次寫入每分鐘(1000千多萬每秒)提供了更高的單機性能。app
Writing a Time Series Database from Scratch
Prometheus官方介紹
時間序列數據的存儲和計算 - 開源時序數據庫解析(四)性能