深入分析Druid存儲結構

導讀: Apache Druid是一款優秀的OLAP引擎,衆所周知數據存儲格式對一款存儲系統來說是最核心的組件,Druid的數據格式是自定義的,以此保證了在海量數據下的亞秒級查詢。本文深入分析Druid V1版本數據存儲格式,包括索引結構和數據在磁盤中的存儲方式。在閱讀本文之前希望您對Druid和數據存儲有簡單瞭解。

Druid的存儲方式是列式的,每個列爲一個邏輯文件,列與列之間的數據格式是相對獨立的。與傳統OLAP系統一樣,Druid的列分爲維度與度量兩種,其中維度列因爲需要被檢檢索,所以設計了索引,維度列的數據格式也是Druid數據結構的核心;相對的度量列只需要存儲行值就可以。爲了方便闡述數據格式,本文以一個廣告效果分析作爲例子進行分析,圖1中是樣例數據,請一定注意它是聚合後的數據,而不是原始數據。

01 維度數據結構

上文提到維度是Druid存儲結構的核心,並且各個維度是相對獨立存儲的,所以我們可以通過分析單個維度的數據結構,來窺探Druid的存儲結構。圖2展示了"city"維度和兩個度量的邏輯存儲結構,整體上Druid維度的索引包含三部分:字典、編碼後的維度值、倒排索引,接下來詳細分析這三部分。

02 字典

字典是將列的所有值去重,然後按照字典順序排序的值組成的數組,雖然字典中只存儲了排序後的維度值,但是它還隱含了另一個信息,那就是每個維度值的編碼值,編碼值就等於數組的下標。字典的設計目的有兩個:一是維度值可以使用編碼後的整數表示,而不是實際的值,編碼值一般可以節約存儲空間;二是編碼後的整數是定長的,磁盤中定長存儲可以省去定位單個值的offset length等索引信息的開銷,最終還是能節省存儲空間。

圖3展示了Druid字典的邏輯結構和物理結構,Druid字典採用了線性的數組結構。因爲字典中的值是不定長的,所以物理結構中有一段index部分,其中記錄了每個值的offset;data部分每個值的頭部記錄了該值的長度。這樣的設計才能定位到任意一個行的值。

03 編碼後的維度值

Druid是一個預聚合的方案,但是其聚合不是按照一個維度的group-by聚合,而是按照所有維度的group-by聚合,對於圖1中的數據已經是按照聚合過了。可以看出對於單一維度而言,編碼過後的維度值依然可能重複,所以每個維度的行信息不能用字典代替,而需要額外存儲。

編碼後的維度值都是一個個的整數。爲了保證單一值在磁盤中能快速定位,在整個維度範圍內這些整數需要是定長的,因爲定長元素組成的數組可以通過計算直接定位到某一個元素。同時爲了節約存儲空間這些整數不一定需要用4個字節表示,它的長度取決於該維度在單一數據文件內的唯一值的數量,Druid採用了採用了變長整數編碼的方式,具體如下:

1 – 2^8-1 => 1 byte
2^8 - 2^16-1 => 2 bytes
2^16 - 2^24-1 => 3 bytes
2^24 - 2^32-1 => 4 bytes
2^32 - 2^40-1 => 5 bytes
...

以圖1中"city"維度爲例,它包含唯一值3個,所以每個值用1個字節表示。

圖4展示了編碼後維度值的邏輯結構和物理結構,在邏輯上整個維度是一個線性的結構,但是在物理存儲上數據結構中包含了offset索引和元素length部分,這很明顯是存儲非定長數據的。原來Druid將整個線性結構首先劃分成了一個個分組,每個分組大小不超過64KB,而分組又進行了壓縮,壓縮後的分組已經是非定長的了,所以站在整個數據結構的角度,需要按照非定長數據的格式進行存儲。

將整個整數數組進行分組壓縮的設計思路,其背後的考量點主要是:一是對於磁盤存儲壓縮是有必要的,因爲能減小空間佔用和傳輸消耗;二是分組也是有必要的,因爲絕大多數讀取數據的場景不會涉及到所有的分組,而是部分分組,分組後一次查詢只涉及到了少數分組,對於查詢速度的提升有極大幫助。

04 倒排索引

最後是倒排索引部分,對於字典中的每個元素,Druid都會生成一個Bitmap,其中1表示該bit下標對應的行的值是對應字典元素的值,反之不是。

Bitmap數據是基於聚合後的數據的,所以它的長度和原始數據的行數是沒有關係的。從圖5中"Beijing"對應的Bitmap可以看出,它基於圖1中的聚合後的數據,而不是原始數據,所以Bitmap的長度是4。

Druid的反向索引採用的是Bitmap的方案,因爲字典中每個元素對應的Bitmap的長度都是一樣的,所以物理存儲上可以採用定長的方式?其實不是的,出於節省存儲空間的考慮,Druid將每個Bitmap進行了壓縮,一般Bitmap數據結構的壓縮比例是比較大的,所以壓縮的是有必要的。因爲壓縮後數據長度不相同了,所以存儲上需要按照非定長數據進行存儲。

05 數組

Druid是支持數組數據類型維度的,對於數組數據類型Druid如何存儲呢?整體上數組的存儲方式還是字典、編碼後的維度值、倒排索引三個部分。其中字典和倒排索引部分是跟單值類型的維度的存儲方式沒有任何區別。

但是在編碼後的維度值部分是有區別的,對於單值維度這部分的邏輯結構是一個線性列表 ( 這裏暫時不考慮分組 ),但是對於數組類型的維度,它其實是一個二層的層次結構,外層是一個非定長的線性列表,線性列表的每個元素也就是內層,是一個定長的線性列表。對於整個數據結構來說,在物理結構上依然可以進行分組和壓縮。

06 存儲結構小結

對於物理結構來說其元素是否定長,對其存儲方式起到決定作用,圖6總結了定長和非定長的存儲模式,請注意這裏沒有考慮分組和壓縮。

Druid對線性非定長存儲結構有着大量的應用,它遵循圖6的總結,只是在元數據部分稍有不同,現總結如下:

version:佔用 1byte
allowReverseLookup:1byte ,是否允許反向查找,指根據Value反查index, 用於Dictionary字典查找
numBytesUsed :4bytes ,所佔字節數
numElements:4bytes,元素的總數量

07 如何使用

最後簡單分析下Druid在查詢中如何使用到以上數據結構,爲了聚焦問題,假設查詢只命中了一個數據文件,這樣可以忽略多個數據文件的結果合併等問題。我們以下面簡單查詢爲例:

select city, sum(click_cnt) from table_t where category=0 or category=1 group by city

圖8展示了查詢流程,其中第1步和第6步用到了字典結構,第3步用到了倒排索引數據結構,第4步用到了編碼後的維度值數據結構。

今天的分享就到這裏,謝謝大家。

作者介紹

吳建超,9年程序員一枚,目前專注於大數據處理和數據庫技術。

本文來自 DataFunTalk

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzU1NTMyOTI4Mw==&mid=2247502681&idx=1&sn=55d19b7abfe74fe238a5aaaa3c83f660&chksm=fbd77935cca0f023d84731abf83bf736aecb7d3470db1248ef2c20c565f3addaeba20ea7dd79&scene=27#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章