Doris存儲層設計介紹1——存儲結構設計解析

1 總體介紹

Doris是基於MPP架構的交互式SQL數據倉庫,主要用於解決了近實時的報表和多維分析。Doris高效的導入、查詢離不開其存儲結構精巧的設計。本文主要經過閱讀Doris BE模塊代碼,詳細分析了Doris BE模塊存儲層的實現原理,闡述和解密Doris高效的寫入、查詢能力背後的核心技術。其中包括Doris列存的設計、索引設計、數據讀寫流程、Compaction流程等功能。這裏會經過三篇文章來逐步進行介紹,分別爲《Doris存儲層設計介紹1——存儲結構設計解析》《Doris存儲層設計介紹2——寫入流程、刪除流程分析》《Doris存儲層設計介紹3——讀取、Compaction流程分析》html

Doris Gitee 鏡像: https://gitee.com/baidu/apache-doris
Doris 開發者郵件組:【 如何訂閱
Doris 微信公衆號:

本文爲第一篇《Doris存儲層設計介紹1——存儲結構設計解析》,文章介紹了Segment V2版本的存儲層結構,包括了有序存儲、稀疏索引、前綴索引、位圖索引、BloomFilter等豐富功能,能夠應對各類複雜的場景提供極速的查詢能力。git

2 設計目標

  • 批量導入,少許更新
  • 絕大多數的讀請求
  • 寬表場景,讀取大量行,少許列
  • 非事務場景
  • 良好的擴展性

3 存儲文件格式

3.1 存儲目錄結構

存儲層對存儲數據的管理經過storage_root_path路徑進行配置,路徑能夠是多個。存儲目錄下一層按照分桶進行組織,分桶目錄下存放具體的tablet,按照tablet_id命名子目錄。github

Segment文件存放在tablet_id目錄下按SchemaHash管理。Segment文件能夠有多個,通常按照大小進行分割,默認爲256MB。其中,Segment v2文件命名規則爲:${rowset_id}_${segment_id}.dat。具體存儲目錄存放格式以下圖所示:算法

存儲目錄文件格式

3.2 Segment v2文件結構

Segment總體的文件格式分爲數據區域,索引區域和footer三個部分,以下圖所示:sql

  • Data Region:用於存儲各個列的數據信息,這裏的數據是按需分page加載的
  • Index Region: Doris中將各個列的index數據統一存儲在Index Region,這裏的數據會按照列粒度進行加載,因此跟列的數據信息分開存儲
  • Footer信息
    • SegmentFooterPB:定義文件的元數據信息
    • 4個字節的FooterPB內容的checksum
    • 4個字節的FileFooterPB消息長度,用於讀取FileFooterPB
    • 8個字節的MAGIC CODE,之因此在末位存儲,是方便不一樣的場景進行文件類型的識別

下面分佈介紹各個部分的存儲格式的設計。apache

4 Footer信息

Footer信息段在文件的尾部,存儲了文件的總體結構,包括數據域的位置,索引域的位置等信息,其中有SegmentFooterPB,CheckSum,Length,MAGIC CODE 4個部分。微信

SegmentFooterPB數據結構以下:數據結構

SegmentFooterPB採用了PB格式進行存儲,主要包含了列的meta信息、索引的meta信息,Segment的short key索引信息、總行數。架構

4.1 列的meta信息

  • ColumnId:當前列在schema中的序號
  • UniqueId:全局惟一的id
  • Type:列的類型信息
  • Length:列的長度信息
  • Encoding:編碼格式
  • Compression:壓縮格式
  • Dict PagePointer:字典信息

4.2 列索引的meta信息

  • OrdinalIndex:存放列的稀疏索引meta信息。
  • ZoneMapIndex:存放ZoneMap索引的meta信息,內容包括了最大值、最小值、是否有空值、是否沒有非空值。SegmentZoneMap存放了全局的ZoneMap信息,PageZoneMaps則存放了每一個頁面的統計信息。
  • BitMapIndex:存放BitMap索引的meta信息,內容包括了BitMap類型,字典數據BitMap數據。
  • BloomFilterIndex:存放了BloomFilter索引信息。

爲了防止索引自己數據量過大,ZoneMapIndex、BitMapIndex、BloomFilterIndex採用了兩級的Page管理。對應了IndexColumnMeta的結構,當一個Page可以放下時,當前Page直接存放索引數據,即採用1級結構;當一個Page沒法放下時,索引數據寫入新的Page中,Root Page存儲數據Page的地址信息。性能

5 Ordinal Index(一級索引)

Ordinal Index索引提供了經過行號來查找Column Data Page數據頁的物理地址。Ordinal Index可以將按列存儲數據按行對齊,能夠理解爲一級索引。其餘索引查找數據時,都要經過Ordinal Index查找數據Page的位置。所以,這裏先介紹Ordinal Index索引。

在一個segment中,數據始終按照key(AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY)排序順序進行存儲,即key的排序決定了數據存儲的物理結構。肯定了列數據的物理結構順序,在寫入數據時,Column Data Page是由Ordinal index進行管理,Ordinal index記錄了每一個Column Data Page的位置offset、大小size和第一個數據項行號信息,即Ordinal。這樣每一個列具備按行信息進行快速掃描的能力。Ordinal index採用的稀疏索引結構,就像是一本書目錄,記錄了每一個章節對應的頁碼。

5.1 存儲結構

Ordinal index元信息存儲在SegmentFooterPB中的每一個列的OrdinalIndexMeta中。具體結構以下圖所示:

在OrdinalIndexMeta中存放了索引數據對應的root page地址,這裏作了一些優化,當數據僅有一個page時,這裏的地址能夠直接指向惟一的數據page;當一個page放不下時,指向OrdinalIndex類型的二級結構索引page,索引數據中每一個數據項對應了Column Data Page offset位置、size大小和ordinal行號信息。其中Ordinal index索引粒度與page粒度一致,默認64*1024字節。

六、列數據存儲

Column的data數據按照Page爲單位分塊存儲,每一個Page大小通常爲64*1024個字節。Page在存儲的位置和大小由ordinal index管理。

6.1 data page存儲結構

DataPage主要爲Data部分、Page Footer兩個部分。

Data部分存放了當前Page的列的數據。當容許存在Null值時,對空值單獨存放了Null值的Bitmap,由RLE格式編碼經過bool類型記錄Null值的行號。

Page Footer包含了Page類型Type、UncompressedSize未壓縮時的數據大小、FirstOrdinal當前Page第一行的RowId、NumValues爲當前Page的行數、NullMapSize對應了NullBitmap的大小。

6.2 數據壓縮

針對不一樣的字段類型採用了不一樣的編碼。默認狀況下,針對不一樣類型採用的對應關係以下:

TINYINT/SMALLINT/INT/BIGINT/LARGEINT BIT_SHUFFLE
FLOAT/DOUBLE/DECIMAL BIT_SHUFFLE
CHAR/VARCHAR DICT
BOOL RLE
DATE/DATETIME BIT_SHUFFLE
HLL/OBJECT PLAIN

默認採用LZ4F格式對數據進行壓縮。

七、Short Key Index索引

7.1 存儲結構

Short Key Index前綴索引,是在key(AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY)排序的基礎上,實現的一種根據給定前綴列,快速查詢數據的索引方式。這裏Short Key Index索引也採用了稀疏索引結構,在數據寫入過程當中,每隔必定行數,會生成一個索引項。這個行數爲索引粒度默認爲1024行,可配置。該過程以下圖所示:

其中,KeyBytes中存放了索引項數據,OffsetBytes存放了索引項在KeyBytes中的偏移。

7.2 索引生成規則

Short Key Index採用了前36 個字節,做爲這行數據的前綴索引。當遇到 VARCHAR 類型時,前綴索引會直接截斷。

7.3 應用案例

(1)如下表結構的前綴索引爲 user_id(8Byte) + age(4Bytes) + message(prefix 24 Bytes)。

ColumnName    Type
user_id BIGINT
age INT
message VARCHAR(100)
max_dwell_time    DATETIME
min_dwell_time DATATIME

(2)如下表結構的前綴索引爲 user_name(20 Bytes)。即便沒有達到 36 個字節,由於遇到 VARCHAR,因此直接截斷,再也不日後繼續。

Column Type
user_name VARCHAR(20)
age INT
message VARCHAR(100)
max_dwell_time DATETIME
min_dwell_time DATETIME

當咱們的查詢條件,是前綴索引的前綴時,能夠極大的加快查詢速度。好比在第一個例子中,咱們執行以下查詢:

SELECT * FROM table WHERE user_id=1829239 and age=20;

該查詢的效率會遠高於以下查詢:

SELECT * FROM table WHERE age=20;

因此在建表時,正確的選擇列順序,可以極大地提升查詢效率。

八、ZoneMap Index索引

ZoneMap索引存儲了Segment和每一個列對應每一個Page的統計信息。這些統計信息能夠幫助在查詢時提速,減小掃描數據量,統計信息包括了Min最大值、Max最小值、HashNull空值、HasNotNull不全爲空的信息。

8.1 存儲結構

ZoneMap索引存儲結構以下圖所示:

在SegmentFootPB結構中,每一列索引元數據ColumnIndexMeta中存放了當前列的ZoneMapIndex索引數據信息。ZoneMapIndex有兩個部分,SegmentZoneMap和PageZoneMaps。SegmentZoneMap存放了當前Segment全局的ZoneMap索引信息,PageZoneMaps存放了每一個Data Page的ZoneMap索引信息。

PageZoneMaps對應了索引數據存放的Page信息IndexedColumnMeta結構,目前實現上沒有進行壓縮,編碼方式也爲Plain。IndexedColumnMeta中的OrdinalIndexPage指向索引數據root page的偏移和大小,這裏一樣作了優化二級Page優化,當僅有一個DataPage時,OrdinalIndexMeta直接指向這個DataPage;有多個DataPage時,OrdinalIndexMeta先指向OrdinalIndexPage,OrdinalIndexPage是一個二級Page結構,裏面的數據項爲索引數據DataPage的地址偏移offset,大小Size和ordinal信息。

8.2 索引生成規則

Doris默認爲key列開啓ZoneMap索引;當表的模型爲DUPULCATE時,會全部字段開啓ZoneMap索引。在列數據寫入Page時,自動對數據進行比較,不斷維護當前Segment的ZoneMap和當前Page的ZoneMap索引信息。

8.3 應用案例

在數據查詢時,會根據範圍條件過濾的字段會按照ZoneMap統計信息選取掃描的數據範圍。例如在案例1中,對age字段進行過濾。查詢語句以下:

SELECT * FROM table WHERE age > 20 and age < 1000

在沒有命中Short Key Index的狀況下,會根據條件語句中age的查詢條件,利用ZoneMap索引找到應該掃描的數據ordinary範圍,減小要掃描的page數量。

九、BloomFilter

當一些字段不能利用Short Key Index而且字段存在區分度比較大時,Doris提供了BloomFilter索引。

9.一、存儲結構

BloomFilter的存儲結構以下圖所示:

BloomFilterIndex信息存放了生產的Hash策略、Hash算法和BloomFilter過對應的數據Page信息。Hash算法採用了HASH_MURMUR3,Hash策略採用了BlockSplitBloomFilter分塊實現策略,指望的誤判率fpp默認配置爲0.05。BloomFilter索引數據對應數據Page的存放與ZoneMapIndex相似,作了二級Page的優化,這裏再也不詳細闡述。

9.二、索引生成規則

BloomFilter按Page粒度生成,在數據寫入一個完整的Page時,Doris會根據Hash策略同時生成這個Page的BloomFilter索引數據。目前bloom過濾器不支持tinyint/hll/float/double類型,其餘類型均已支持。使用時須要在PROPERTIES中指定bloom_filter_columns要使用BloomFilter索引的字段。

9.3 應用案例

在數據查詢時,查詢條件在設置有bloom過濾器的字段進行過濾,當bloom過濾器沒有命中時表示該Page中沒有該數據,這樣能夠減小要掃描的page數量。

案例:table的schema以下:

ColumnName    Type
user_id    BIGINT
age INT
name VARCHAR(20)
city VARCHAR(200)
createtime DATETIME

這裏的查詢sql以下:

SELECT * FROM table WHERE name = '張三'

因爲name的區分度較大,爲了提高sql的查詢性能,對name數據增長了BloomFilter索引,PROPERTIES ( "bloom_filter_columns" = "name" )。在查詢時經過BloomFilter索引可以大量過濾掉Page。

十、Bitmap Index索引

Doris還提供了BitmapIndex用來加速數據的查詢。

10.一、存儲結構

Bitmap存儲格式以下:

BitmapIndex的meta信息一樣存放在SegmentFootPB中,BitmapIndex包含了三部分,BitMap的類型、字典信息DictColumn、位圖索引數據信息BitMapColumn。其中DictColumn、BitMapColumn都對應IndexedColumnData結構,分別存放了字典數據和索引數據的Page地址offset、大小size。這裏一樣作了二級page的優化,再也不具體闡述。

這裏與其餘索引存儲結構有差別的地方是DictColumn字典數據進行了LZ4F壓縮,在記錄二級Page偏移時存放的是Data Page中的第一個值。

10.二、索引生成規則

BitMap建立時須要經過 CREATE INDEX 進行建立。Bitmap的索引是整個Segment中的Column字段的索引,而不是爲每一個Page單獨生成一份。在寫入數據時,會維護一個map結構記錄下每一個key值對應的行號,並採用Roaring位圖對rowid進行編碼。主要結構以下:

生成索引數據時,首先寫入字典數據,將map結構的key值寫入到DictColumn中。而後,key對應Roaring編碼的rowid以字節方式將數據寫入到BitMapColumn中。

10.三、應用案例

在數據查詢時,對於區分度不大,列的基數比較小的數據列,能夠採用位圖索引進行優化。好比,性別,婚姻,地理信息等。

案例:table的schema以下:

ColumnName    Type
user_id    BIGINT
age INT
name VARCHAR(20)
city VARCHAR(200)
createtime DATETIME

這裏的查詢sql以下:

SELECT * FROM table WHERE city in ("北京", "上海")

因爲city的取值比較少,創建數據字典和位圖後,經過掃描位圖即可以快速查找出匹配行。而且位圖壓縮後,數據量自己較小,經過掃描較少數據變可以對整個列進行精確的匹配。

十一、索引的查詢流程

在查詢一個Segment中的數據時,根據執行的查詢條件,會對首先根據字段加索引的狀況對數據進行過濾。而後在進行讀取數據,總體的查詢流程以下:

  1. 首先,會按照Segment的行數構建一個row_bitmap,表示記錄那些數據須要進行讀取,沒有使用任何索引的狀況下,須要讀取全部數據。
  2. 當查詢條件中按前綴索引規則使用到了key時,會先進行ShortKey Index的過濾,能夠在ShortKey Index中匹配到的ordinal行號範圍,合入到row_bitmap中。
  3. 當查詢條件中列字段存在BitMap Index索引時,會按照BitMap索引直接查出符合條件的ordinal行號,與row_bitmap求交過濾。這裏的過濾是精確的,以後去掉該查詢條件,這個字段就不會再進行後面索引的過濾。
  4. 當查詢條件中列字段存在BloomFilter索引而且條件爲等值(eq,in,is)時,會按BloomFilter索引過濾,這裏會走完全部索引,過濾每個Page的BloomFilter,找出查詢條件能命中的全部Page。將索引信息中的ordinal行號範圍與row_bitmap求交過濾。
  5. 當查詢條件中列字段存在ZoneMap索引時,會按ZoneMap索引過濾,這裏一樣會走完全部索引,找出查詢條件能與ZoneMap有交集的全部Page。將索引信息中的ordinal行號範圍與row_bitmap求交過濾。
  6. 生成好row_bitmap以後,批量經過每一個Column的OrdinalIndex找到到具體的Data Page。
  7. 批量讀取每一列的Column Data Page的數據。在讀取時,對於有null值的page,根據null值位圖判斷當前行是不是null,若是爲null進行直接填充便可。

十二、總結

Doris目前採用了徹底的列存儲結構,並提供了豐富的索引應對不一樣查詢場景,爲Doris高效的寫入、查詢性能奠基了夯實的基礎。Doris存儲層設計靈活,將來還能夠進一步增長新的索引、強化數據刪除等功能。

相關文章
相關標籤/搜索