1 總體介紹
Doris是基於MPP架構的交互式SQL數據倉庫,主要用於解決了近實時的報表和多維分析。Doris高效的導入、查詢離不開其存儲結構精巧的設計。本文主要經過閱讀Doris BE模塊代碼,詳細分析了Doris BE模塊存儲層的實現原理,闡述和解密Doris高效的寫入、查詢能力背後的核心技術。其中包括Doris列存的設計、索引設計、數據讀寫流程、Compaction流程等功能。這裏會經過三篇文章來逐步進行介紹,分別爲《Doris存儲層設計介紹1——存儲結構設計解析》、《Doris存儲層設計介紹2——寫入流程、刪除流程分析》、《Doris存儲層設計介紹3——讀取、Compaction流程分析》。html
本文爲第二篇《Doris存儲層設計介紹2——寫入流程、刪除流程分析》,文章詳細介紹了數據寫入過程當中Doris系統內部實現流程,以及Doris對數據按條件刪除和按key批量刪除的實現流程。前端
2 名稱解釋
-
FE:Frontend,即 Doris 的前端節點。主要負責接收和返回客戶端請求、元數據以及集羣管理、查詢計劃生成等工做。git
-
BE:Backend,即 Doris 的後端節點。主要負責數據存儲與管理、查詢計劃執行等工做。github
-
Tablet:Tablet是一張表實際的物理存儲單元,一張表按照分區和分桶後在BE構成分佈式存儲層中以Tablet爲單位進行存儲,每一個Tablet包括元信息及若干個連續的RowSet。apache
-
Rowset:Rowset是Tablet中一次數據變動的數據集合,數據變動包括了數據導入、刪除、更新等。Rowset按版本信息進行記錄。每次變動會生成一個版本。後端
-
Version:由Start、End兩個屬性構成,維護數據變動的記錄信息。一般用來表示Rowset的版本範圍,在一次新導入後生成一個Start,End相等的Rowset,在Compaction後生成一個帶範圍的Rowset版本。微信
-
Segment:表示Rowset中的數據分段。多個Segment構成一個Rowset。架構
-
Compaction:連續版本的Rowset合併的過程成稱爲Compaction,合併過程當中會對數據進行壓縮操做。併發
3 寫入流程
Doris針對不一樣場景支持了多種形式的數據寫入方式,其中包括了從其餘存儲源導入Broker Load、http同步數據導入Stream Load、例行的Routine Load導入和Insert Into寫入等。同時導入流程會涉及FE模塊(主要負責導入規劃生成和導入任務的調度工做)、BE模塊(主要負責數據的 ETL 和存儲)、Broker模塊(提供 Doris 讀取遠端存儲系統中文件的能力)。其中Broker模塊僅在Broker Load類型的導入中應用。異步
下面以Stream Load寫入爲例子,描述了Doris的總體的數據寫入流程以下圖所示:
流程描述以下:
- FE接收用戶的寫入請求,並隨機選出BE做爲Coordinator BE。將用戶的請求重定向到這個BE上。
- Coordinator BE負責接收用戶的數據寫入請求,同時請求FE生成執行計劃並對調度、管理導入任務LoadJob和導入事務。
- Coordinator BE調度執行導入計劃,執行對數據校驗、清理以後。
- 數據寫入到BE的存儲層中。在這個過程當中會先寫入到內存中,寫滿必定數據後按照存儲層的數據格式寫入到物理磁盤上。
本文主要介紹數據寫入到BE存儲層的詳細流程。其他流程不在詳細描述。·
3.1 數據分發流程
數據在通過清洗過濾後,會經過Open/AddBatch請求分批量的將數據發送給存儲層的BE節點上。在一個BE上支持多個LoadJob任務同時併發寫入執行。LoadChannelMgr負責管理了這些任務,並對數據進行分發。數據分發和寫入過程以下圖所示:
- 每次導入任務LoadJob會創建一個LoadChannel來執行,LoadChannel維護了一次導入的通道,LoadChannel能夠將數據分批量寫入操做直到導入完成。
- LoadChannel會建立一個TabletsChannel執行具體的導入操做。一個TabletsChannel對應多個Tablet。一次數據批量寫入操做中,TabletsChannel將數據分發給對應Tablet,由DeltaWriter將數據寫入到Tablet,便開始了真正的寫入操做。
3.2 DeltaWriter 與 Memtable
DeltaWriter主要負責不斷接收新寫入的批量數據,完成單個Tablet的數據寫入。因爲新增的數據能夠是增量Delta部分,所以叫作DeltaWriter。
DeltaWriter數據寫入採用了類LSM樹的結構,將數據先寫到Memtable中,當Memtable數據寫滿後,會異步flush生成一個Segment進行持久化,同時生成一個新的Memtable繼續接收新增數據導入,這個flush操做由MemtableFlushExecutor執行器完成。
Memtable中採用了跳錶的結構對數據進行排序,排序規則使用了按照schema的key的順序依次對字段進行比較。這樣保證了寫入的每個寫入Segment中的數據是有序的。若是當前模型爲非DUP模型(AGG模型和UNIQUE模型)時,還會對相同key的數據進行聚合。
3.3 物理寫入
3.3.1 RowsetWriter 各個模塊設計
在物理存儲層面的寫入,由RowsetWriter完成。RowsetWriter中又分爲SegmentWriter、ColumnWriter、PageBuilder、IndexBuilder等子模塊。
- 其中RowsetWriter從總體上完成一次導入LoadJob任務的寫入,一次導入LoadJob任務會生成一個Rowset,一個Rowset表示一次導入成功生效的數據版本。實現上由RowsetWriter負責完成Rowset的寫入。
- SegmentWriter負責實現Segment的寫入。一個Rowset能夠由多個Segment文件組成。
- ColumnWriter被包含在SegmentWriter中,Segment的文件是徹底的列存儲結構,Segment中包含了各個列和相關的索引數據,每一個列的寫入由ColumnWriter負責寫入。
- 在文件存儲格式中,數據和索引都是按Page進行組織,ColumnWriter中又包含了生成數據Page的PageBuilder和生成索引Page的IndexBuilder來完成Page的寫入。
- 最後,FileWritableBlock來負責具體的文件的讀寫。文件的存儲格式能夠參見《Doris存儲層設計介紹1——存儲結構設計解析》文檔。
3.3.2 RowsetWriter 寫入流程
總體的物理寫入的以下圖所示:
物理寫入流程的詳細描述:
- 當一個Memtable寫滿時(默認爲100M),將Memtable的數據會flush到磁盤上,這時Memtable內的數據是按key有序的。而後逐行寫入到RowsetWriter中。
- RowsetWriter將數據一樣逐行寫入到SegmentWriter中,RowsetWriter會維護當前正在寫入的SegmentWriter以及要寫入的文件塊列表。每完成寫入一個Segment會增長一個文件塊對應。
- SegmentWriter將數據按行寫入到各個ColumnWriter的中,同時寫入ShortKeyIndexBuilder。ShortKeyIndexBuilder主要負責生成ShortKeyIndex的索引Page頁。具體的ShortKeyIndex索引格式能夠參見《Doris存儲層設計介紹1——存儲結構設計解析》文檔。
- ColumnWriter將數據分別寫入PageBuilder和各個IndexBuilder,PageBuilder用來生成ColumnData數據的PageBuilder,各個IndexBuilder包括了(OrdinalIndexBuilder生成OrdinalIndex行號稀疏索引的Page格式、ZoneMapIndexBuilder生成ZoneMapIndex索引的Page格式、BitMapIndexBuilder生成BitMapIndex索引的Page格式、BloomFilterIndexBuilder生成BloomFilterIndex索引的Page格式)。具體參考Doris存儲文件格式解析。
- 添加完數據後,RowsetWriter執行flush操做。
- SegmentWriter的flush操做,將數據和索引寫入到磁盤。其中對磁盤的讀寫由FileWritableBlock完成。
- ColumnWriter將各自數據、索引生成的Page順序寫入到文件中。
- SegmentWriter生成SegmentFooter信息,SegmentFooter記錄了Segment文件的原數據信息。完成寫入操做後,RowsetWriter會再開起新的SegmentWriter,將下一個Memtable寫入新的Segment,直到導入完成。
3.4 Rowset 發佈
在數據導入完成時,DeltaWriter會將新生成的Rowset進行發佈。發佈即將這個版本的Rowset設置爲可見狀態,表示導入數據已經生效可以被查詢。而版本信息表示Rowset生效的次序,一次導入會生成一個Rowset,每次導入成功會按序增長版本。整個發佈過程以下:
- DeltaWriter統計當前RowsetMeta元數據信息,包括行數、字節數、時間、Segment數量。
- 保存到RowsetMeta中,向FE提交導入事務。當前導入事務由FE開啓,用來保證一次導入在各個BE節點的數據的同時生效。
- 在FE協調好以後,由FE統一下發Publish任務使導入的Rowset版本生效。任務中指定了發佈的生效version版本信息。以後BE存儲層纔會將這個版本的Rowset設置爲可見。
- Rowset加入到BE存儲層的Tablet進行管理。
四、刪除流程
目前Delete有兩種實現,一種普通的刪除類型爲DELETE,一種爲LOAD_DELETE。
4.1 DELETE 執行流程
DELETE的支持通常的刪除操做,實現較爲簡單,DELETE模式下沒有對數據進行實際刪除操做,而是對數據刪除條件進行了記錄。存儲在Meta信息中。當執行Base Compaction時刪除條件會一塊兒被合入到Base版本中。Base版本爲Tablet從[0-x]的第一個Rowset數據版本。具體流程以下:
- 刪除時由FE直接下發刪除命令和刪除條件。
- BE在本地啓動一個EngineBatchLoadTask任務,生成新版本的Rowset,並記錄刪除條件信息。這個刪除記錄的Rowset與寫入過程的略有不一樣,該Rowset僅記錄了刪除條件信息,沒有實際的數據。
- FE一樣發佈生效版本。其中會將Rowset加入到Tablet中,保存TabletMeta信息。
4.2 LOAD_DELETE 執行流程
LOAD_DELETE支持了在UNIQUE KEY模型下,實現了經過批量導入要刪除的key對數據進行刪除,可以支持大量數據刪除能力。總體思路是在數據記錄中加入刪除狀態標識,在Compaction流程中會對刪除的key進行壓縮。Compaction主要負責將多個Rowset版本進行合併,Compaction流程會在後續的文章中進行詳細介紹。
目前LOAD_DELETE功能正在研發中,近期的Doris版本會進行發佈。
五、總結
本文詳細介紹了Doris系統底層存儲層的寫入流程、刪除流程。首先,Doris總體的寫入流程進行了描述,而後,詳細分析了Doris的類LSM存儲結構的設計、內存部分數據分發和物理寫入流程、Rowset版本發佈生效等流程,最後介紹了Doris支持的兩種數據刪除方式。寫一篇會介紹《Doris存儲層設計介紹3——讀取流程、Compaction流程分析》。