淺析下開源時(shí)序數(shù)據(jù)庫VictoriaMetrics的存儲(chǔ)機(jī)制

VictoriaMetrics是一個(gè)快速高效且可擴(kuò)展的監(jiān)控解決方案和時(shí)序數(shù)據(jù)庫,可以作為Prometheus的長(zhǎng)期遠(yuǎn)端存儲(chǔ),具備的特性有:

- 支持prometheus查詢api,同時(shí)實(shí)現(xiàn)了一個(gè)metricsql 查詢語言

- 支持全局查詢視圖,支持多prometheus 實(shí)例寫數(shù)據(jù)到VictoriaMetrics,然后提供一個(gè)統(tǒng)一的查詢

- 支持集群

- 高性能

- 支持多種協(xié)議,包括influxdb line協(xié)議,prometheus metrics,graphite ,prometheus遠(yuǎn)端寫api,opentsdb http協(xié)議等

- 高壓縮比

本文主要分析下VictoriaMetrics的存儲(chǔ)機(jī)制,包括整體架構(gòu)、數(shù)據(jù)模型、磁盤目錄、文件格式等部分,對(duì)應(yīng)的源碼版本號(hào)為v1.45.0。

整體架構(gòu)

VictoriaMetrics的集群主要由vmstorage、vminsert、vmselect等三部分組成,其中,vmstorage 負(fù)責(zé)提供數(shù)據(jù)存儲(chǔ)服務(wù); vminsert 是數(shù)據(jù)存儲(chǔ) vmstorage 的代理,使用一致性hash算法進(jìn)行寫入分片; vmselect 負(fù)責(zé)數(shù)據(jù)查詢,根據(jù)輸入的查詢條件從 vmstorage 中查詢數(shù)據(jù)。

VictoriaMetrics的這個(gè)三個(gè)組件每個(gè)組件都可以單獨(dú)進(jìn)行擴(kuò)展,并運(yùn)行在大多數(shù)合適軟件上。vmstorage采用shared-nothing架構(gòu),優(yōu)點(diǎn)是 vmstorage的節(jié)點(diǎn)相互之間無感知,相互之間無需通信,不共享任何數(shù)據(jù),增加了集群的可用性、簡(jiǎn)化了集群的運(yùn)維和集群的擴(kuò)展。

另外,VictoriaMetrics集群支持多個(gè)隔離的租戶特性,又名命名空間。租戶通過accountID (或者accountID:projectID) 來標(biāo)識(shí),這些ID放在請(qǐng)求URL中。有關(guān)VictoriaMetrics租戶的一些事實(shí):

- 每個(gè)accountID和projectID由[0 .. 2 ^ 32)范圍內(nèi)的任意32位整數(shù)標(biāo)識(shí)。如果缺少projectID,則會(huì)將其自動(dòng)分配為0。預(yù)計(jì)有關(guān)租戶的其他信息(例如身份驗(yàn)證令牌,租戶名稱,限制,計(jì)費(fèi)等)將存儲(chǔ)在單獨(dú)的關(guān)系數(shù)據(jù)庫中。此數(shù)據(jù)庫必須由位于VictoriaMetrics群集前面的單獨(dú)服務(wù)(例如vmauth)管理。

- 將第一個(gè)數(shù)據(jù)點(diǎn)寫入給定租戶時(shí),將自動(dòng)創(chuàng)建租戶。

- 所有租戶的數(shù)據(jù)平均分布在可用的vmstorage節(jié)點(diǎn)之間。當(dāng)不同的租戶具有不同的數(shù)據(jù)量和不同的查詢負(fù)載時(shí),這可以保證在vmstorage節(jié)點(diǎn)之間平均負(fù)載。

整體上來說,VictoriaMetrics支持多租戶,但租戶的信息需要使用額外的關(guān)系型數(shù)據(jù)庫來存儲(chǔ),且VictoriaMetrics不支持在單個(gè)請(qǐng)求中查詢多個(gè)租戶。

數(shù)據(jù)模型

開門見山,VictoriaMetrics采用的數(shù)據(jù)模型是單值模型,且只支持浮點(diǎn)數(shù)指標(biāo)。那么到底什么是單值模型呢?目前,常見的時(shí)序數(shù)據(jù)庫的數(shù)據(jù)模型,主要分成單值模型和多值模型。這里簡(jiǎn)單說明下單值模型和多值模型,整體上,可以認(rèn)為單值模型是多值模型的一個(gè)特例。

單值模型是根據(jù)業(yè)務(wù)指標(biāo)數(shù)據(jù)建模,按照單個(gè)指標(biāo)的細(xì)粒度進(jìn)行數(shù)據(jù)使用和邏輯存儲(chǔ),如下圖所示,一行數(shù)據(jù)只有一個(gè)指標(biāo)值,即value列。目前采用單值模型的時(shí)序數(shù)據(jù)庫,有OpenTSDB、KairosDB、Prometheus等。

多值的模型則是針對(duì)數(shù)據(jù)源建模,我們每一行數(shù)據(jù)針對(duì)的是一個(gè)數(shù)據(jù)源,它的被測(cè)量的多個(gè)指標(biāo)在同一列上,如下圖所示,一行數(shù)據(jù)有多個(gè)指標(biāo)值,即有cpu和io兩列。目前采用多值模型的時(shí)序數(shù)據(jù)庫,有InfluxDB、TimescaleDB等。

磁盤目錄

VictoriaMetrics的根目錄下主要包括4個(gè)目錄或文件,如下圖所示。其中,最主要的是數(shù)據(jù)目錄data和索引目錄indexdb,flock.lock文件為文件鎖文件,用于VictoriaMetrics進(jìn)程鎖住文件,不允許別的進(jìn)程進(jìn)行修改目錄或文件。

數(shù)據(jù)目錄

數(shù)據(jù)目錄data的具體結(jié)構(gòu),如下圖所示,在圖中使用紅色文字,對(duì)主要目錄或文件做了簡(jiǎn)單說明,其中最主要的是big目錄和small目錄,這兩個(gè)目錄的結(jié)構(gòu)是一樣的。其中,在VictoriaMetrics中,使用table來表示的數(shù)據(jù)或者索引的根目錄,而實(shí)際上VictoriaMetrics中沒有實(shí)際的表table級(jí)別目錄。

在small目錄下,以月為單位不斷生成partition目錄,比如上圖中的2020_11目錄,對(duì)應(yīng)的實(shí)現(xiàn)在源碼lib/storage/partition.go中。partition目錄包括part目錄、臨時(shí)目錄tmp、 事務(wù)目錄txn等三個(gè)目錄。

內(nèi)存中的數(shù)據(jù)每刷盤一次就會(huì)生成一個(gè)part目錄,如上圖中的"708_354_20201103102134.255_20201103102149.255_1643F83394CA24A7",目錄名中的708表示這個(gè)目錄下包含的數(shù)據(jù)行數(shù)rowsCount, 目錄名中的354表示這個(gè)目錄中包含的數(shù)據(jù)塊數(shù)blocksCount, 20201103102134.255表示目錄中包含的數(shù)據(jù)的最小時(shí)間戳,20201103102149.255表示目錄中包含的數(shù)據(jù)的最大時(shí)間戳,1643F83394CA24A7是生成這個(gè)目錄時(shí)的系統(tǒng)納秒時(shí)間戳的16進(jìn)制表示,對(duì)應(yīng)的實(shí)現(xiàn)邏輯在源碼lib/storage/part.go中;

看到這里,可能會(huì)有一些疑問?比如為何要分成big和small目錄, 或者說big目錄和small中的數(shù)據(jù)關(guān)系是什么? 這個(gè)需要從VictoriaMetrics的compaction機(jī)制講起。

在VictoriaMetrics中,small目錄和big目錄都會(huì)周期性檢查,是否需要做part的合并。VictoriaMetrics默認(rèn)每個(gè)10ms檢查一次partition目錄下的part是否需要做merge。如果檢查出有滿足merge條件的parts,則這些parts合并為一個(gè)part。如果沒有滿足條件的part進(jìn)行merge,則以10ms為基進(jìn)行指數(shù)休眠,最大休眠時(shí)間為10s。

VictoriaMetrics在寫數(shù)據(jù)時(shí),先寫入在small目錄下的相應(yīng)partition目錄下面的,small目錄下的每個(gè)partition最多256個(gè)part。VictoriaMetrics在Compaction時(shí),默認(rèn)一次最多合并15個(gè)part,且small目錄下的part最多包含1000W行數(shù)據(jù),即1000W個(gè)數(shù)據(jù)點(diǎn)。因此,當(dāng)一次待合并的parts中包含的行數(shù)超過1000W行時(shí),其合并的輸出路徑為big目錄下的同名partition目錄下。

因此,big目錄下的數(shù)據(jù)由small目錄下的數(shù)據(jù)在后臺(tái)compaction時(shí)合并生成的。 那么為什么要分成big目錄和small目錄呢?

這個(gè)主要是從磁盤空間占用上來考慮的。時(shí)序數(shù)據(jù)經(jīng)常讀取最近寫入的數(shù)據(jù),較少讀歷史數(shù)據(jù)。而且,時(shí)序數(shù)據(jù)的數(shù)據(jù)量比較大,通常會(huì)使用壓縮算法進(jìn)行壓縮。

數(shù)據(jù)進(jìn)行壓縮后,讀取時(shí)需要解壓,采用不同級(jí)別的壓縮壓縮算法其解壓速度不一樣,通常壓縮級(jí)別越高,其解壓速度越慢。在VictoriaMetrics在時(shí)序壓縮的基礎(chǔ)上,又采用了ZSTD這個(gè)通用壓縮算法進(jìn)一步壓縮了數(shù)據(jù),以提高壓縮率。在small目錄中的part數(shù)據(jù),采用的是低級(jí)別的ZSTD,而big目錄下的數(shù)據(jù),采用的是高級(jí)別的ZSTD。

因此,VictoriaMetrics分成small目錄和big目錄,主要是兼顧近期數(shù)據(jù)的讀取和歷史數(shù)據(jù)的壓縮率。

索引目錄

索引目錄indexdb的具體結(jié)構(gòu),如下圖所示,在圖中使用紅色文字,對(duì)主要目錄或文件做了簡(jiǎn)單說明。與數(shù)據(jù)目錄不同的是,indexdb目錄下由多個(gè)table目錄,每個(gè)table目錄代表一個(gè)完整的自治的索引,每個(gè)table目錄下,又有多個(gè)不同的part目錄,part命名方式比較簡(jiǎn)單,有文件包含的item數(shù)itemsCount和block數(shù)blocksCount, 以及根據(jù)系統(tǒng)納秒時(shí)間戳自增生成的mergeIdx的16進(jìn)制表示。

indexdb下面的形如"1643F4F397B53DEE"是怎么生成的,或者什么時(shí)候切換新的目錄寫索引的呢?VictoriaMetrics會(huì)根據(jù)用戶設(shè)置的數(shù)據(jù)保留周期retention來定期滾動(dòng)索引目錄,當(dāng)前一個(gè)索引目錄的保留時(shí)間到了,就會(huì)切換一個(gè)新的目錄,重新生成索引。

文件格式

在介紹具體的文件格式之前,不得不提下VictoriaMetrics對(duì)于寫入數(shù)據(jù)的處理過程。下圖是VictoriaMetrics支持的Prometheus協(xié)議的一個(gè)寫入示例。

VictoriaMetrics在接受到寫入請(qǐng)求時(shí),會(huì)對(duì)請(qǐng)求中包含的時(shí)序數(shù)據(jù)做轉(zhuǎn)換處理,如下圖所示。首先先根據(jù)包含metric和labels的MetricName生成一個(gè)唯一標(biāo)識(shí)TSID,然后metric + labels + TSID作為索引index, TSID + timestamp + value作為數(shù)據(jù)data,最后索引index和數(shù)據(jù)data分別進(jìn)行存儲(chǔ)和檢索。


因此,VictoriaMetrics的數(shù)據(jù)整體上分成索引和數(shù)據(jù)兩個(gè)部分,因此文件格式整體上會(huì)有兩個(gè)部分。其中,索引部分主要是用于支持按照label或者tag進(jìn)行多維檢索。與大多數(shù)時(shí)序數(shù)據(jù)庫的數(shù)據(jù)組織方式一樣,比如InfluxDB、Prometheus、OpenTSDB等,VictoriaMetrics也是按時(shí)間線來組織數(shù)據(jù)的,即數(shù)據(jù)存儲(chǔ)時(shí),先將數(shù)據(jù)按TSID進(jìn)行分組,然后每個(gè)TSID的包含的數(shù)據(jù)點(diǎn)各自使用列式壓縮存儲(chǔ)。

索引文件

VictoriaMetrics每次內(nèi)存Flush或者后臺(tái)Merge時(shí)生成的索引part,主要包含metaindex.bin、index.bin、lens.bin、items.bin等4個(gè)文件。這四個(gè)文件的關(guān)系如下圖所示, metaindex.bin文件通過metaindex_row索引index.bin文件,index.bin文件通過indexBlock中的blockHeader同時(shí)索引lens.bin文件和items.bin文件。

metaindex.bin文件中,包含一系列的metaindex_row, 每個(gè)metaindex_row中包含最小項(xiàng)firstItem、索引塊包含的塊頭部數(shù)blockHeadersCount、索引塊偏移indexBlockOffset、索引塊大小indexBlockSize。

- metaindex_row在文件中的位置按照firstItem的大小的字典序排序存儲(chǔ),以支持二分檢索;

- metaindex.bin文件使用ZSTD進(jìn)行壓縮;

- metaindex.bin文件中的內(nèi)容在part打開時(shí),會(huì)全部讀出加載至內(nèi)存中,以加速查詢過濾;

- metaindex_row包含的firstItem為其索引的IndexBlock中所有blockHeader中的字典序最小的firstItem;

- 查找時(shí)根據(jù)firstItem進(jìn)行二分檢索;

index.bin文件中,包含一系列的indexBlock, 每個(gè)indexBlock又包含一系列blockHeader,每個(gè)blockHeader的包含item的公共前綴commonPrefix、最小項(xiàng)firstItem、itemsData的序列化類型marshalType、itemsData包含的item數(shù)、item塊的偏移itemsBlockOffset等內(nèi)容。

- 每個(gè)indexBlock使用ZSTD壓縮算法進(jìn)行壓縮;

- 在indexBlock中查找時(shí),根據(jù)firstItem進(jìn)行二分檢索blockHeader;

items.bin文件中,包含一系列的itemsData, 每個(gè)itemsData又包含一系列的item。

- itemsData會(huì)根據(jù)情況是否使用ZTSD壓縮,當(dāng)item個(gè)數(shù)小于2時(shí),或者itemsData的長(zhǎng)度小于64字節(jié)時(shí),不壓縮;當(dāng)itemsData使用ZSTD壓縮后的大小,大于原始itemsData的0.9倍時(shí),則不壓縮,否則使用ZSTD算法進(jìn)行壓縮。

- 每個(gè)item在存儲(chǔ)時(shí),去掉了blockHeader中的公共前綴commonPrefix以提高壓縮率。

lens.bin文件中,包含一系列的lensData, 每個(gè)lensData又包含一系列8字節(jié)的長(zhǎng)度len, 長(zhǎng)度len標(biāo)識(shí)items.bin文件中對(duì)應(yīng)item的長(zhǎng)度。在讀取或者需要解析itemsData中的item時(shí),先要讀取對(duì)應(yīng)的lensData中對(duì)應(yīng)的長(zhǎng)度len。 當(dāng)itemsData進(jìn)行壓縮時(shí),lensData會(huì)先使用異或算法進(jìn)行壓縮,然后再使用ZSTD算法進(jìn)一步壓縮。

VictoriaMetrics索引文件都是圍繞著item來組織的,那么item的結(jié)構(gòu)是什么樣子的?或者item的種類有哪些? 在VictoriaMetrics中item的整體上是一個(gè)KeyValue結(jié)構(gòu)的字節(jié)數(shù)組,共計(jì)有7種類型,每種類型的item通過固定前綴來區(qū)分,前綴類型如下圖所示。

VictoriaMetrics是怎么基于item支持tag多維檢索的呢? 這里就不得不提下VictoriaMetrics的TSID的生成過程。

VictoriaMetrics的MetricName的結(jié)構(gòu)如下所示,包含MetricGroup字節(jié)數(shù)組和Tag數(shù)組,其中,MetricGroup是可選的,每個(gè)Tag由Key和Value等字節(jié)數(shù)組構(gòu)成。

VictoriaMetrics的TSID的結(jié)構(gòu)如下所示,包含MetricGroupID,JobID,InstanceID,MetricID等四個(gè)字段,其中除了MetricID外,其他三個(gè)字段都是可選的。這個(gè)幾個(gè)ID的生成方法如下:

- metricGroupID根據(jù)MetricName中的MetricGroup使用xxhash的sum64算法生成。

- JobID和InstanceID分別由MetricName中的tag的第一個(gè)tag和第二個(gè)tag使用xxhash的sum64算法生成。為什么使用第一個(gè)tag和第二個(gè)tag?這是因?yàn)閂ictoriaMetrics在寫入時(shí),將寫入請(qǐng)求中的JobID和InstanceID放在了Tag數(shù)組的第一個(gè)和第二個(gè)位置。

- MetricID,使用VictoriaMetrics進(jìn)程啟動(dòng)時(shí)的系統(tǒng)納秒時(shí)間戳自增生成。

因?yàn)門SID中除了MetricID外,其他字段都是可選的,因此TSID中可以始終作為有效信息的只有metricID,因此VictoriaMetrics的在構(gòu)建tag到TSID的字典過程中,是直接存儲(chǔ)的tag到metricID的字典。

以寫入http_requests_total{status="200", method="GET"}為例,則MetricName為http_requests_total{status="200", method="GET"}, 假設(shè)生成的TSID為{metricGroupID=0, jobID=0, instanceID=0, metricID=51106185174286},則VictoriaMetrics在寫入時(shí)就構(gòu)建了如下幾種類型的索引item,其他類型的索引item是在后臺(tái)或者查詢時(shí)構(gòu)建的。

- metricName -> TSID, 即http_requests_total{status="200", method="GET"} -> {metricGroupID=0, jobID=0, instanceID=0, metricID=51106185174286}

- metricID -> metricName,即51106185174286 -> http_requests_total{status="200", method="GET"}

- metricID -> TSID,即51106185174286 -> {metricGroupID=0, jobID=0, instanceID=0, metricID=51106185174286}

- tag -> metricID,即 status="200" -> 51106185174286,? method="GET"? -> 51106185174286, "" = http_requests_total -> 51106185174286

有了這些索引的item后,就可以支持基于tag的多維檢索了,在當(dāng)給定查詢條件http_requests_total{status="200"}時(shí),VictoriaMetrics先根據(jù)給定的tag條件,找出每個(gè)tag的metricID列表,然后求所有tag的metricID列表的交集,然后根據(jù)交集中的metricID,再到索引文件中檢索出TSID,根據(jù)TSID就可以到數(shù)據(jù)文件中查詢數(shù)據(jù)了,在返回結(jié)果之前,再根據(jù)TSID中的metricID,到索引文件中檢索出對(duì)應(yīng)的寫入時(shí)的原始MetircName。

但是由于VictoriaMetrics的tag到metricID的字典,沒有將相同tag的所有metricID放在一起存儲(chǔ),在檢索時(shí),一個(gè)tag可能需要查詢多次才能得到完整的metricID列表。另外查詢出metricID后,還要再到索引文件中去檢索TSID才能去數(shù)據(jù)文件查詢數(shù)據(jù),又增加了一次IO開銷。這樣來看的話,VictoriaMetrics的索引文件在檢索時(shí),如果命中的時(shí)間線比較多的情況下,其IO開銷會(huì)比較大,查詢延遲也會(huì)比較高。

數(shù)據(jù)文件

VictoriaMetrics每次內(nèi)存Flush或者后臺(tái)Merge時(shí)生成的數(shù)據(jù)part,包含metaindex.bin、index.bin、timestamps.bin、values.bin等4個(gè)文件。這四個(gè)文件的關(guān)系如下所示。metaindex.bin文件索引index.bin文件,index.bin文件同時(shí)索引timestamps.bin和values.bin文件。

metaindex.bin文件中,包含一系列的metaindex_row, 每個(gè)metaindex_row中包含時(shí)間線標(biāo)識(shí)TSID、最小時(shí)間戳minTimestamp、最大時(shí)間戳maxTimestamp、索引塊偏移indexBlockOffset、索引塊大小indexBlockSize、索引塊包含的塊頭部數(shù)blockHeadersCount。

- metaindex_row在文件中的位置按照TSID的大小的字典序排序存儲(chǔ);

- metaindex.bin文件使用ZSTD進(jìn)行壓縮;

- metaindex.bin文件中的內(nèi)容在part打開時(shí),會(huì)全部讀出加載至內(nèi)存中,以加速查詢過濾;

- metaindex_row包含時(shí)間線標(biāo)識(shí)TSID為其索引的IndexBlock中所有blockHeader中的最小時(shí)間標(biāo)識(shí)TSID;

- metaindex_row包含最小時(shí)間戳minTimestamp為其索引的IndexBlock中所有blockHeader中的最小時(shí)間戳minTimestamp;

- metaindex_row包含最大時(shí)間戳maxTimestamp為其索引的IndexBlock中所有blockHeader中的最大時(shí)間戳maxTimestamp;

- 查找時(shí)根據(jù)TSID進(jìn)行二分檢索;

index.bin文件中,包含一系列的indexBlock, 每個(gè)indexBlock又包含一系列blockHeader,每個(gè)blockHeader的包含時(shí)間線標(biāo)識(shí)TSID、最小時(shí)間戳minTimestamp、最大時(shí)間戳maxTimestamp、第一個(gè)指標(biāo)值firstValue、時(shí)間戳數(shù)據(jù)塊偏移timestampsBlockOffset、指標(biāo)值數(shù)據(jù)塊偏移valuesBlockOffset等內(nèi)容。

- 每個(gè)indexBlock使用ZSTD壓縮算法進(jìn)行壓縮;

- 查找時(shí),線性遍歷blockHeader查找TSID;

timestamps.bin文件中,包含一系列時(shí)間線的時(shí)間戳壓縮塊timestampsData;? values.bin文件中,包含的一系列時(shí)間線的指標(biāo)值壓縮塊valuesData。 其中,timestampsData和values.data會(huì)根據(jù)時(shí)序數(shù)據(jù)特征進(jìn)行壓縮,整體上的壓縮思路是:先做時(shí)序壓縮,然后在做通用壓縮。比如,先做delta-of-delta計(jì)算或者異或計(jì)算,然后根據(jù)情況做zig-zag,最后再根據(jù)情況做一次ZSTD壓縮,VictoriaMetrics支持的壓縮算法或者類型主要有6種,如下圖所示,壓縮編碼源碼在lib/encoding/encoding.go文件中。

VictoriaMetrics文檔中提到在生產(chǎn)環(huán)境中,每個(gè)數(shù)據(jù)點(diǎn)(8字節(jié)時(shí)間戳 + 8字節(jié)value共計(jì)16字節(jié))壓縮后小于1個(gè)字節(jié),最高可達(dá) 0.4字節(jié),如下所示。

VictoriaMetrics總結(jié)

VictoriaMetrics的整體的存儲(chǔ)設(shè)計(jì)還是不錯(cuò)的,比如數(shù)據(jù)時(shí)間分區(qū)、數(shù)據(jù)壓縮率高、豐富的生態(tài)協(xié)議等。但VictoriaMetrics的標(biāo)簽索引、數(shù)據(jù)可靠性、支持的數(shù)據(jù)類型等方面還存在一些不足。比如,標(biāo)簽索引查詢多次IO,可能在時(shí)間線數(shù)量非常多的場(chǎng)景下,其檢索效率會(huì)比較低,且沒有WAL,在寫入時(shí)可能會(huì)存在數(shù)據(jù)丟失的風(fēng)險(xiǎn)。目前只支持浮點(diǎn)數(shù)類型,不支持布爾、字符串、字節(jié)數(shù)組等類型。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容