influxdb 倒排索引介紹


背景 - 時序數據庫為什么需要倒排索引?

時序數據庫對監控的時間線存在多維度查詢,以及聚合查詢的需求。

打比方:

我們監控所有服務器的cpu信息,我們會存在幾種需求:

  • 指定服務器的機房來獲取對應服務器的cpu利用率。
  • 指定服務器的機型來獲取對應服務器的cpu利用率。

這種不同維度的查詢需求在基本上所有的時序數據庫中都會通過倒排索引來解決。

Influxdb自己實現了一套倒排索引,在1.5之前的版本實現比較粗糙,在1.5版本官方文檔正式release了tsi1的倒排索引實現,而且出了一份"吹牛逼"的報告(主要是1.5針對1.3的優化結果)。

本文分別對1.5版本的tsi1倒排實現和1.3版本的inmem倒排實現做些介紹。水平有限,一通"胡侃",歡迎指正。

名詞解釋:

mesurement: 等同於關系型數據庫中的 table
tag_key: 數據源的標簽key
tag_value: 數據源的標簽value, 表明這個數據源的一些屬性。
series(seriesKey): 也可稱之為數據源/時間線,在InfluxDB series由measurement name + tag_key + tag_value 組成。

inmem inverted Index實現

inmem既是將倒排索引數據全部放在內存中,帶來問題顯而易見:

  • 隨着監控時間線的不斷增加,既我們監控物件規模不斷增長,內存爆炸。
  • 當server啟動時需要從所有的tsm數據文件中讀取索引信息並load到內存中,這樣導致啟動時間非常長。

inmem inverted index關鍵數據結構:

{measurement -> tagKey -> tagValue -> [seriesIDs]} {seriesID -> seriesString}

源碼中結構比較復雜,這里省略。實際上inverted index就是按照hash結構存儲 series 到 SeriesIDs的映射關系。

tsi1 inverted Index實現

tsi1是Influxdb 1.5版本對inmem的重大改進,並且出了一份測試報告做了和release 1.3.x的性能對比,所以我理解1.5版本是tsi1的正式release版本。

tsi1為了解決上述inmem的兩個問題,又要保證倒排索引的吞吐滿足性能需求,總結設計思路主要如下:

數據落盤,只有部分熱數據緩存在內存中,依舊使用lsm tree思想實現快速寫入需求
使用Hash數據結構,滿足快速查詢的需求,設計上不考慮 <,>等范圍查詢場景。
將數據做partition,減少沖突粒度鎖粒度,能夠在單機層面上縱向擴展性能。
tsi1中將inmem中的兩份數據結構划分到兩個模塊中:

  1. {measurement -> tagKey -> tagValue -> [seriesIDs]} => tsi1 index
  2. {seriesID -> seriesKey} => SeriesFile
    SeriesFile
    首先介紹下SeriesFile模塊,作用是記錄{seriesID -> seriesKey},SeriesFIle設計:

將原先inmem index中的{seriesID -> seriesKey} 的結構落地到磁盤中,為了達到快速獲取Series的要求,SeriesFile核心的數據結構hashtable。
SeriesFile跨Shard,所有Shard共享一個Series file
SeriesFile partition設計,提升單Influxdb對Series的寫入效率,減少寫入沖突。
SeriesFile設計細節:

SeriesFile默認分為8個partition,每個partition 由兩部分組成
index file
segment file
index file則是記錄seriesID到offset的映射,offset是對應series在segment file中的位點。
segment file會記錄所有seriesKey,具體數據結構可見下方圖片

index file結構
index file由三部分組成,HDR, KeyIDMap, IdOffsetMap.

HDR主要記錄了一些元信息,存儲了index file中的KeyIDMap和IdOffsetMap在index file中的offset。

IdOffsetMap 記錄着ID到segment file offset 的映射關系,這個映射關系在disk按照Robin Hood HashTable存儲。

img

index 文件中記錄的offset為8byte, 如何映射到segment file ?

SegmentOffset 會將8 byte切分成3部分:

| 2 byte | 2 byte | 4 byte |

第一部分目前沒有意義的。 第二部分映射到segment 的id, 這樣計算segment最大數量為2的16次方。 第三部分映射到segment 文件中的offset。

傳入Series ID,讀取seriesKey流程:
  • get IDOffsetMap offset in Index file from HDR block .

  • get SegmentOffset from IDOffsetMap block.

  • get seriesKey from Segment file by SegmentOffset. 

seriesKey寫入流程:
  • 首先新的seriesKey數據新增時,會將seriesKey寫入到segment file中,但是不會馬上寫入到index file中,index file的記錄存放在內存中,既index file實際上在內存中和磁盤中各有部分數據。如果server重啟,直接通過重新load segment file,當前的index file會記錄上次落盤時對應的segment file position, 只需要將新增segment file的index 記錄load到內存中即可。
  • index file作為一個hashtable, 隨着規模的增長,hashtable必然要面臨着rehash的動作。那么這里玩法就是compact,當內存中的index達到一定閾值后,將內存中的記錄+原有的index file的hashtable聚合到一個新的文件中。
segment file結構

segment files 存儲了seriesKey,seriesKey的寫入分為兩種: insert和delete. 上層模塊可能調用插入seriesKey和delete seriesKey接口,所有的記錄順序寫入到segment files中。

這里設計上segment files是不會做compact的,可能在設計上認為時序場景基本不會出現delete series場景。

img

tsi1 index設計

總體上是將inmem中的 {measurement -> tagKey -> tagValue -> [seriesIDs]} 落盤,雖然落盤后用了一些技巧能夠快速定位到seriesIDs記錄,但是整體的數據結構和inmem一樣,使用hashtable,能夠O(1)的復雜度讀取到記錄。

tsi1總體設計:

  • tsi存儲引擎設計類似LSM Tree

  • 每個Shard有獨立的tsi1存儲,這樣的設計可以保證老的Shard中存在一些時間線能夠隨着Shard的過期自動過期刪除。同時這樣會帶來Shard rollOver時會一些性能損耗。

  • 和SeriesFile一樣,每個Shard的tsi分為多個partition寫入,提升單Influxdb對tsi的寫入效率,減少寫入沖突,增加吞吐量和減少延時。

  • 和其他的所有tsm, seriesFile等模塊類似,在工程實現上通過mmap 系統調用將磁盤文件映射到內存地址上,讓os來幫助我們做LRU(Least Recently Used),熱點數據cache在內存中。

三個主要模塊:

Log file, Index File, Manifest File.

Log file模塊

和所有的LSM 引擎類似,Log File模塊實際在運行中生效的是Log File對應在內存中的Cache

cache結構
  • 內存中數據結構類似於inmem inverted index結構,

  • [SeriesIDs] 在內存中按照BitMap結構存儲,節約內存消耗。

  • cache 中記錄 add series, 同樣也記錄 delete series。並且delete series記錄會隨着dump到磁盤中持久化。

  • 同tsm存儲時序數據一樣,當上層調用Log File接口來獲取 series時,會同時從cache和Index File讀取合並,返回給上層。

Log File結構

Log File 記錄着最近的series增加刪除記錄,數據寫入Cache同時順序寫入到日志文件中。文件后綴為 .tsl 文件格式如下圖:

  • LogEntry有多種type,根據Flag區分開,如最多的新增series flag, 在做redo行為時主要讀取SeriesID和measurementName。
  • LogEntry中的SeriesID結構在存儲時使用了簡單的壓縮編碼,SeriesID原本是8 byte的long整型數據,實際在存儲中使用了varint編碼,可以有效減少空間使用。 雖然varint做了編碼,而且字節不對齊對cpu不友好,但是在Influxdb場景下容量看起來更加受重視。

img

Index File

Index File可對照LSM Tree中的SSTable.

Index File的載體是一個文件,文件分為幾個部分:trailer, measurement block, tag block

trailer主要記錄measurement block, tag block的offset和size, 做一個路由作用。

measurement block

measurement block 作用是快速上層指定的measurement name 路由到tag block中,measurement block結構是一個hashtable, 保證O(1)復雜度。

tag block

tag block 主要存儲了tag_key -> tag_value -> [seriesIDs]的映射關系,上層模塊可以傳入tag_key + tag_value, 然后可以快速獲取到seriesIDs。
下圖是通過measurement + tag_key + tag_value 獲取對應的時間線(seriesIDs)的流程,圖中僅僅展示了相關的部分的數據結構。 由於我們的數據復雜讀很高,盡管我們使用了hashtable的數據結構,但是一次查詢的IO次數還是比較多的,當然如果我們大多數情況下獲取的都是熱數據,大多數的IO請求都能夠在cache層面命中。

img

Manifest File
Manifest File中記錄當前所有index File 和 Log File。
Manifest File主要作用:

  • 按照時間順序記錄所有的Index File, 先讀取新的index file,保證如果出現 series 寫入然后刪除的行為下,上層接口首先讀取到的是新的index file, 那么會首先讀到刪除的結果。
  • 保證在下次compact時順序,不會刪除應該存在的數據。

tsi測試報告
[官方測試報告](http://get.influxdata.com/rs/972-GDU-533/images/Index and TSM overview in InfluxDB 1.5.pdf?spm=ata.21736010.0.0.5d8e758cfKJVL1&file=Index and TSM overview in InfluxDB 1.5.pdf)

總結

  • tsi1的實現其實非常復雜,本文並沒有覆蓋如tsi1中為了兼容 series刪除場景,series數量的估計等細節,如有興趣可以自行讀源碼閱讀。
  • 需使用最新的release分支。tsi 存儲引擎在1.5版本上還是不夠成熟,本人在簡單使用tsi inverted index運行influxdb過程中發現多個問題,當然influxdb社區的活躍度是毋庸置疑的,而且有商業化公司在支撐Influxdb發展,質量上會越來越好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM