java 类的存储结构设计_Doris存储层设计介绍1——存储结构设计解析
1?整體介紹
Doris是基于MPP架構(gòu)的交互式SQL數(shù)據(jù)倉庫,主要用于解決了近實時的報表和多維分析。Doris高效的導(dǎo)入、查詢離不開其存儲結(jié)構(gòu)精巧的設(shè)計。本文主要通過閱讀Doris BE模塊代碼,詳細分析了Doris BE模塊存儲層的實現(xiàn)原理,闡述和解密Doris高效的寫入、查詢能力背后的核心技術(shù)。其中包括Doris列存的設(shè)計、索引設(shè)計、數(shù)據(jù)讀寫流程、Compaction流程、Tablet和Rowset的版本管理、數(shù)據(jù)備份等功能。這里會通過三篇文章來逐步進行介紹,分別為《Doris存儲層設(shè)計介紹1——存儲結(jié)構(gòu)設(shè)計解析》、《Doris存儲層設(shè)計介紹2——讀寫、compaction流程分析》、《Doris存儲層設(shè)計介紹3——Tablet管理、數(shù)據(jù)備份》。
Doris 微信公眾號:
本文為第一篇《Doris存儲層設(shè)計介紹1——存儲結(jié)構(gòu)設(shè)計解析》,文章介紹了Segment V2版本的存儲層結(jié)構(gòu),包括了有序存儲、稀疏索引、前綴索引、位圖索引、BloomFilter等豐富功能,可以應(yīng)對各種復(fù)雜的場景提供極速的查詢能力。
2?設(shè)計目標(biāo)
批量導(dǎo)入,少量更新
絕大多數(shù)的讀請求
寬表場景,讀取大量行,少量列
非事務(wù)場景
良好的擴展性
3?存儲文件格式
3.1?存儲目錄結(jié)構(gòu)
存儲層對存儲數(shù)據(jù)的管理通過storage_root_path路徑進行配置,路徑可以是多個。存儲目錄下一層按照分桶進行組織,分桶目錄下存放具體的tablet,按照tablet_id命名子目錄。
Segment文件存放在tablet_id目錄下按SchemaHash管理。Segment文件可以有多個,一般按照大小進行分割,默認為256MB。其中,Segment v2文件命名規(guī)則為:${rowset_id}_${segment_id}.dat。具體存儲目錄存放格式如下圖所示:
3.2 Segment v2文件結(jié)構(gòu)
Segment整體的文件格式分為數(shù)據(jù)區(qū)域,索引區(qū)域和footer三個部分,如下圖所示:
Data Region:用于存儲各個列的數(shù)據(jù)信息,這里的數(shù)據(jù)是按需分page加載的
Index Region: Doris中將各個列的index數(shù)據(jù)統(tǒng)一存儲在Index Region,這里的數(shù)據(jù)會按照列粒度進行加載,所以跟列的數(shù)據(jù)信息分開存儲
Footer信息
SegmentFooterPB:定義文件的元數(shù)據(jù)信息
4個字節(jié)的FooterPB內(nèi)容的checksum
4個字節(jié)的FileFooterPB消息長度,用于讀取FileFooterPB
8個字節(jié)的MAGIC CODE,之所以在末位存儲,是方便不同的場景進行文件類型的識別
下面分布介紹各個部分的存儲格式的設(shè)計。
4?Footer信息
Footer信息段在文件的尾部,存儲了文件的整體結(jié)構(gòu),包括數(shù)據(jù)域的位置,索引域的位置等信息,其中有SegmentFooterPB,CheckSum,Length,MAGIC CODE 4個部分。
SegmentFooterPB數(shù)據(jù)結(jié)構(gòu)如下:
SegmentFooterPB采用了PB格式進行存儲,主要包含了列的meta信息、索引的meta信息,Segment的short key索引信息、總行數(shù)。
4.1 列的meta信息
ColumnId:當(dāng)前列在schema中的序號
UniqueId:全局唯一的id
Type:列的類型信息
Length:列的長度信息
Encoding:編碼格式
Compression:壓縮格式
Dict PagePointer:字典信息
4.2 列索引的meta信息
OrdinalIndex:存放列的稀疏索引meta信息。
ZoneMapIndex:存放ZoneMap索引的meta信息,內(nèi)容包括了最大值、最小值、是否有空值、是否沒有非空值。SegmentZoneMap存放了全局的ZoneMap信息,PageZoneMaps則存放了每個頁面的統(tǒng)計信息。
BitMapIndex:存放BitMap索引的meta信息,內(nèi)容包括了BitMap類型,字典數(shù)據(jù)BitMap數(shù)據(jù)。
BloomFilterIndex:存放了BloomFilter索引信息。
為了防止索引本身數(shù)據(jù)量過大,ZoneMapIndex、BitMapIndex、BloomFilterIndex采用了兩級的Page管理。對應(yīng)了IndexColumnMeta的結(jié)構(gòu),當(dāng)一個Page能夠放下時,當(dāng)前Page直接存放索引數(shù)據(jù),即采用1級結(jié)構(gòu);當(dāng)一個Page無法放下時,索引數(shù)據(jù)寫入新的Page中,Root Page存儲數(shù)據(jù)Page的地址信息。
5?Ordinal Index(一級索引)
Ordinal Index索引提供了通過行號來查找Column Data Page數(shù)據(jù)頁的物理地址。Ordinal Index能夠?qū)戳写鎯?shù)據(jù)按行對齊,可以理解為一級索引。其他索引查找數(shù)據(jù)時,都要通過Ordinal Index查找數(shù)據(jù)Page的位置。因此,這里先介紹Ordinal Index索引。
在一個segment中,數(shù)據(jù)始終按照key(AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY)排序順序進行存儲,即key的排序決定了數(shù)據(jù)存儲的物理結(jié)構(gòu)。確定了列數(shù)據(jù)的物理結(jié)構(gòu)順序,在寫入數(shù)據(jù)時,Column Data Page是由Ordinal index進行管理,Ordinal index記錄了每個Column Data Page的位置offset、大小size和第一個數(shù)據(jù)項行號信息,即Ordinal。這樣每個列具有按行信息進行快速掃描的能力。Ordinal index采用的稀疏索引結(jié)構(gòu),就像是一本書目錄,記錄了每個章節(jié)對應(yīng)的頁碼。
5.1 存儲結(jié)構(gòu)
Ordinal index元信息存儲在SegmentFooterPB中的每個列的OrdinalIndexMeta中。具體結(jié)構(gòu)如下圖所示:
在OrdinalIndexMeta中存放了索引數(shù)據(jù)對應(yīng)的root page地址,這里做了一些優(yōu)化,當(dāng)數(shù)據(jù)僅有一個page時,這里的地址可以直接指向唯一的數(shù)據(jù)page;當(dāng)一個page放不下時,指向OrdinalIndex類型的二級結(jié)構(gòu)索引page,索引數(shù)據(jù)中每個數(shù)據(jù)項對應(yīng)了Column Data Page offset位置、size大小和ordinal行號信息。其中Ordinal index索引粒度與page粒度一致,默認64*1024字節(jié)。
6、列數(shù)據(jù)存儲
Column的data數(shù)據(jù)按照Page為單位分塊存儲,每個Page大小一般為64*1024個字節(jié)。Page在存儲的位置和大小由ordinal index管理。
6.1 data page存儲結(jié)構(gòu)
DataPage主要為Data部分、Page Footer兩個部分。
Data部分存放了當(dāng)前Page的列的數(shù)據(jù)。當(dāng)允許存在Null值時,對空值單獨存放了Null值的Bitmap,由RLE格式編碼通過bool類型記錄Null值的行號。
Page Footer包含了Page類型Type、UncompressedSize未壓縮時的數(shù)據(jù)大小、FirstOrdinal當(dāng)前Page第一行的RowId、NumValues為當(dāng)前Page的行數(shù)、NullMapSize對應(yīng)了NullBitmap的大小。
6.2 數(shù)據(jù)壓縮
針對不同的字段類型采用了不同的編碼。默認情況下,針對不同類型采用的對應(yīng)關(guān)系如下:
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格式對數(shù)據(jù)進行壓縮。
7、Short Key Index索引
7.1 存儲結(jié)構(gòu)
Short Key Index前綴索引,是在key(AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY)排序的基礎(chǔ)上,實現(xiàn)的一種根據(jù)給定前綴列,快速查詢數(shù)據(jù)的索引方式。這里Short Key Index索引也采用了稀疏索引結(jié)構(gòu),在數(shù)據(jù)寫入過程中,每隔一定行數(shù),會生成一個索引項。這個行數(shù)為索引粒度默認為1024行,可配置。該過程如下圖所示:
其中,KeyBytes中存放了索引項數(shù)據(jù),OffsetBytes存放了索引項在KeyBytes中的偏移。
7.2 索引生成規(guī)則
Short Key Index采用了前36 個字節(jié),作為這行數(shù)據(jù)的前綴索引。當(dāng)遇到 VARCHAR 類型時,前綴索引會直接截斷。
7.3 應(yīng)用案例
(1)以下表結(jié)構(gòu)的前綴索引為 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)以下表結(jié)構(gòu)的前綴索引為 user_name(20 Bytes)。即使沒有達到 36 個字節(jié),因為遇到 VARCHAR,所以直接截斷,不再往后繼續(xù)。
Column
Type
user_name
VARCHAR(20)
age
INT
message
VARCHAR(100)
max_dwell_time
DATETIME
min_dwell_time
DATETIME
當(dāng)我們的查詢條件,是前綴索引的前綴時,可以極大的加快查詢速度。比如在第一個例子中,我們執(zhí)行如下查詢:
SELECT * FROM table WHERE user_id=1829239 and age=20;
該查詢的效率會遠高于如下查詢:
SELECT * FROM table WHERE age=20;
所以在建表時,正確的選擇列順序,能夠極大地提高查詢效率。
8、ZoneMap Index索引
ZoneMap索引存儲了Segment和每個列對應(yīng)每個Page的統(tǒng)計信息。這些統(tǒng)計信息可以幫助在查詢時提速,減少掃描數(shù)據(jù)量,統(tǒng)計信息包括了Min最大值、Max最小值、HashNull空值、HasNotNull不全為空的信息。
8.1 存儲結(jié)構(gòu)
ZoneMap索引存儲結(jié)構(gòu)如下圖所示:
在SegmentFootPB結(jié)構(gòu)中,每一列索引元數(shù)據(jù)ColumnIndexMeta中存放了當(dāng)前列的ZoneMapIndex索引數(shù)據(jù)信息。ZoneMapIndex有兩個部分,SegmentZoneMap和PageZoneMaps。SegmentZoneMap存放了當(dāng)前Segment全局的ZoneMap索引信息,PageZoneMaps存放了每個Data Page的ZoneMap索引信息。
PageZoneMaps對應(yīng)了索引數(shù)據(jù)存放的Page信息IndexedColumnMeta結(jié)構(gòu),目前實現(xiàn)上沒有進行壓縮,編碼方式也為Plain。IndexedColumnMeta中的OrdinalIndexPage指向索引數(shù)據(jù)root page的偏移和大小,這里同樣做了優(yōu)化二級Page優(yōu)化,當(dāng)僅有一個DataPage時,OrdinalIndexMeta直接指向這個DataPage;有多個DataPage時,OrdinalIndexMeta先指向OrdinalIndexPage,OrdinalIndexPage是一個二級Page結(jié)構(gòu),里面的數(shù)據(jù)項為索引數(shù)據(jù)DataPage的地址偏移offset,大小Size和ordinal信息。
8.2 索引生成規(guī)則
Doris默認為key列開啟ZoneMap索引;當(dāng)表的模型為DUPULCATE時,會所有字段開啟ZoneMap索引。在列數(shù)據(jù)寫入Page時,自動對數(shù)據(jù)進行比較,不斷維護當(dāng)前Segment的ZoneMap和當(dāng)前Page的ZoneMap索引信息。
8.3 應(yīng)用案例
在數(shù)據(jù)查詢時,會根據(jù)范圍條件過濾的字段會按照ZoneMap統(tǒng)計信息選取掃描的數(shù)據(jù)范圍。例如在案例1中,對age字段進行過濾。查詢語句如下:
SELECT * FROM table WHERE age > 20 and age < 1000
在沒有命中Short Key Index的情況下,會根據(jù)條件語句中age的查詢條件,利用ZoneMap索引找到應(yīng)該掃描的數(shù)據(jù)ordinary范圍,減少要掃描的page數(shù)量。
9、BloomFilter
當(dāng)一些字段不能利用Short Key Index并且字段存在區(qū)分度比較大時,Doris提供了BloomFilter索引。
9.1、存儲結(jié)構(gòu)
BloomFilter的存儲結(jié)構(gòu)如下圖所示:
BloomFilterIndex信息存放了生產(chǎn)的Hash策略、Hash算法和BloomFilter過對應(yīng)的數(shù)據(jù)Page信息。Hash算法采用了HASH_MURMUR3,Hash策略采用了BlockSplitBloomFilter分塊實現(xiàn)策略,期望的誤判率fpp默認配置為0.05。BloomFilter索引數(shù)據(jù)對應(yīng)數(shù)據(jù)Page的存放與ZoneMapIndex類似,做了二級Page的優(yōu)化,這里不再詳細闡述。
9.2、索引生成規(guī)則
BloomFilter按Page粒度生成,在數(shù)據(jù)寫入一個完整的Page時,Doris會根據(jù)Hash策略同時生成這個Page的BloomFilter索引數(shù)據(jù)。目前bloom過濾器不支持tinyint/hll/float/double類型,其他類型均已支持。使用時需要在PROPERTIES中指定bloom_filter_columns要使用BloomFilter索引的字段。
9.3 應(yīng)用案例
在數(shù)據(jù)查詢時,查詢條件在設(shè)置有bloom過濾器的字段進行過濾,當(dāng)bloom過濾器沒有命中時表示該Page中沒有該數(shù)據(jù),這樣可以減少要掃描的page數(shù)量。
案例:table的schema如下:
ColumnName
Type
user_id
BIGINT
age
INT
name
VARCHAR(20)
city
VARCHAR(200)
createtime
DATETIME
這里的查詢sql如下:
SELECT * FROM table WHERE name = '張三'
由于name的區(qū)分度較大,為了提升sql的查詢性能,對name數(shù)據(jù)增加了BloomFilter索引,PROPERTIES ( "bloom_filter_columns" = "name" )。在查詢時通過BloomFilter索引能夠大量過濾掉Page。
10、Bitmap Index索引
Doris還提供了BitmapIndex用來加速數(shù)據(jù)的查詢。
10.1、存儲結(jié)構(gòu)
Bitmap存儲格式如下:
BitmapIndex的meta信息同樣存放在SegmentFootPB中,BitmapIndex包含了三部分,BitMap的類型、字典信息DictColumn、位圖索引數(shù)據(jù)信息BitMapColumn。其中DictColumn、BitMapColumn都對應(yīng)IndexedColumnData結(jié)構(gòu),分別存放了字典數(shù)據(jù)和索引數(shù)據(jù)的Page地址offset、大小size。這里同樣做了二級page的優(yōu)化,不再具體闡述。
這里與其他索引存儲結(jié)構(gòu)有差異的地方是DictColumn字典數(shù)據(jù)進行了LZ4F壓縮,在記錄二級Page偏移時存放的是Data Page中的第一個值。
10.2、索引生成規(guī)則
BitMap創(chuàng)建時需要通過 CREATE INDEX 進行創(chuàng)建。Bitmap的索引是整個Segment中的Column字段的索引,而不是為每個Page單獨生成一份。在寫入數(shù)據(jù)時,會維護一個map結(jié)構(gòu)記錄下每個key值對應(yīng)的行號,并采用Roaring位圖對rowid進行編碼。主要結(jié)構(gòu)如下:
生成索引數(shù)據(jù)時,首先寫入字典數(shù)據(jù),將map結(jié)構(gòu)的key值寫入到DictColumn中。然后,key對應(yīng)Roaring編碼的rowid以字節(jié)方式將數(shù)據(jù)寫入到BitMapColumn中。
10.3、應(yīng)用案例
在數(shù)據(jù)查詢時,對于區(qū)分度不大,列的基數(shù)比較小的數(shù)據(jù)列,可以采用位圖索引進行優(yōu)化。比如,性別,婚姻,地理信息等。
案例: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的取值比較少,建立數(shù)據(jù)字典和位圖后,通過掃描位圖便可以快速查找出匹配行。并且位圖壓縮后,數(shù)據(jù)量本身較小,通過掃描較少數(shù)據(jù)變能夠?qū)φ麄€列進行精確的匹配。
11、索引的查詢流程
在查詢一個Segment中的數(shù)據(jù)時,根據(jù)執(zhí)行的查詢條件,會對首先根據(jù)字段加索引的情況對數(shù)據(jù)進行過濾。然后在進行讀取數(shù)據(jù),整體的查詢流程如下:
首先,會按照Segment的行數(shù)構(gòu)建一個row_bitmap,表示記錄那些數(shù)據(jù)需要進行讀取,沒有使用任何索引的情況下,需要讀取所有數(shù)據(jù)。
當(dāng)查詢條件中按前綴索引規(guī)則使用到了key時,會先進行ShortKey Index的過濾,可以在ShortKey Index中匹配到的ordinal行號范圍,合入到row_bitmap中。
當(dāng)查詢條件中列字段存在BitMap Index索引時,會按照BitMap索引直接查出符合條件的ordinal行號,與row_bitmap求交過濾。這里的過濾是精確的,之后去掉該查詢條件,這個字段就不會再進行后面索引的過濾。
當(dāng)查詢條件中列字段存在BloomFilter索引并且條件為等值(eq,in,is)時,會按BloomFilter索引過濾,這里會走完所有索引,過濾每一個Page的BloomFilter,找出查詢條件能命中的所有Page。將索引信息中的ordinal行號范圍與row_bitmap求交過濾。
當(dāng)查詢條件中列字段存在ZoneMap索引時,會按ZoneMap索引過濾,這里同樣會走完所有索引,找出查詢條件能與ZoneMap有交集的所有Page。將索引信息中的ordinal行號范圍與row_bitmap求交過濾。
生成好row_bitmap之后,批量通過每個Column的OrdinalIndex找到到具體的Data Page。
批量讀取每一列的Column Data Page的數(shù)據(jù)。在讀取時,對于有null值的page,根據(jù)null值位圖判斷當(dāng)前行是否是null,如果為null進行直接填充即可。
12、總結(jié)
Doris目前采用了完全的列存儲結(jié)構(gòu),并提供了豐富的索引應(yīng)對不同查詢場景,為Doris高效的寫入、查詢性能奠定了夯實的基礎(chǔ)。Doris存儲層設(shè)計靈活,未來還可以進一步增加新的索引、強化數(shù)據(jù)刪除等功能。
總結(jié)
以上是生活随笔為你收集整理的java 类的存储结构设计_Doris存储层设计介绍1——存储结构设计解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我就想问16亿人去哪了??????
- 下一篇: java 默认数据库创建路径_无法创建数