Doris存储层设计介绍2——写入流程、删除流程分析

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批量删除的实现流程。前端

Doris Gitee 镜像: https://gitee.com/baidu/apache-doris
Doris 开发者邮件组:【 如何订阅
Doris 微信公众号:

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,合并过程当中会对数据进行压缩操做。并发

写入流程

Doris针对不一样场景支持了多种形式的数据写入方式,其中包括了从其余存储源导入Broker Load、http同步数据导入Stream Load、例行的Routine Load导入和Insert Into写入等。同时导入流程会涉及FE模块(主要负责导入规划生成和导入任务的调度工做)、BE模块(主要负责数据的 ETL 和存储)、Broker模块(提供 Doris 读取远端存储系统中文件的能力)。其中Broker模块仅在Broker Load类型的导入中应用。异步

下面以Stream Load写入为例子,描述了Doris的总体的数据写入流程以下图所示:

 流程描述以下:

  1. FE接收用户的写入请求,并随机选出BE做为Coordinator BE。将用户的请求重定向到这个BE上。
  2. Coordinator BE负责接收用户的数据写入请求,同时请求FE生成执行计划并对调度、管理导入任务LoadJob和导入事务。
  3. Coordinator BE调度执行导入计划,执行对数据校验、清理以后。
  4. 数据写入到BE的存储层中。在这个过程当中会先写入到内存中,写满必定数据后按照存储层的数据格式写入到物理磁盘上。

本文主要介绍数据写入到BE存储层的详细流程。其他流程不在详细描述。·

3.1 数据分发流程

数据在通过清洗过滤后,会经过Open/AddBatch请求分批量的将数据发送给存储层的BE节点上。在一个BE上支持多个LoadJob任务同时并发写入执行。LoadChannelMgr负责管理了这些任务,并对数据进行分发。数据分发和写入过程以下图所示:

  1. 每次导入任务LoadJob会创建一个LoadChannel来执行,LoadChannel维护了一次导入的通道,LoadChannel能够将数据分批量写入操做直到导入完成。
  2. 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等子模块。

  1. 其中RowsetWriter从总体上完成一次导入LoadJob任务的写入,一次导入LoadJob任务会生成一个Rowset,一个Rowset表示一次导入成功生效的数据版本。实现上由RowsetWriter负责完成Rowset的写入。
  2. SegmentWriter负责实现Segment的写入。一个Rowset能够由多个Segment文件组成。
  3. ColumnWriter被包含在SegmentWriter中,Segment的文件是彻底的列存储结构,Segment中包含了各个列和相关的索引数据,每一个列的写入由ColumnWriter负责写入。
  4. 在文件存储格式中,数据和索引都是按Page进行组织,ColumnWriter中又包含了生成数据Page的PageBuilder和生成索引Page的IndexBuilder来完成Page的写入。
  5. 最后,FileWritableBlock来负责具体的文件的读写。文件的存储格式能够参见《Doris存储层设计介绍1——存储结构设计解析》文档。

3.3.2 RowsetWriter 写入流程

总体的物理写入的以下图所示:

物理写入流程的详细描述:

  1. 当一个Memtable写满时(默认为100M),将Memtable的数据会flush到磁盘上,这时Memtable内的数据是按key有序的。而后逐行写入到RowsetWriter中。
  2. RowsetWriter将数据一样逐行写入到SegmentWriter中,RowsetWriter会维护当前正在写入的SegmentWriter以及要写入的文件块列表。每完成写入一个Segment会增长一个文件块对应。
  3. SegmentWriter将数据按行写入到各个ColumnWriter的中,同时写入ShortKeyIndexBuilder。ShortKeyIndexBuilder主要负责生成ShortKeyIndex的索引Page页。具体的ShortKeyIndex索引格式能够参见《Doris存储层设计介绍1——存储结构设计解析》文档。
  4. ColumnWriter将数据分别写入PageBuilder和各个IndexBuilder,PageBuilder用来生成ColumnData数据的PageBuilder,各个IndexBuilder包括了(OrdinalIndexBuilder生成OrdinalIndex行号稀疏索引的Page格式、ZoneMapIndexBuilder生成ZoneMapIndex索引的Page格式、BitMapIndexBuilder生成BitMapIndex索引的Page格式、BloomFilterIndexBuilder生成BloomFilterIndex索引的Page格式)。具体参考Doris存储文件格式解析。
  5. 添加完数据后,RowsetWriter执行flush操做。
  6. SegmentWriter的flush操做,将数据和索引写入到磁盘。其中对磁盘的读写由FileWritableBlock完成。
  7. ColumnWriter将各自数据、索引生成的Page顺序写入到文件中。
  8. SegmentWriter生成SegmentFooter信息,SegmentFooter记录了Segment文件的原数据信息。完成写入操做后,RowsetWriter会再开起新的SegmentWriter,将下一个Memtable写入新的Segment,直到导入完成。

3.4 Rowset 发布

在数据导入完成时,DeltaWriter会将新生成的Rowset进行发布。发布即将这个版本的Rowset设置为可见状态,表示导入数据已经生效可以被查询。而版本信息表示Rowset生效的次序,一次导入会生成一个Rowset,每次导入成功会按序增长版本。整个发布过程以下:

  1. DeltaWriter统计当前RowsetMeta元数据信息,包括行数、字节数、时间、Segment数量。
  2. 保存到RowsetMeta中,向FE提交导入事务。当前导入事务由FE开启,用来保证一次导入在各个BE节点的数据的同时生效。
  3. 在FE协调好以后,由FE统一下发Publish任务使导入的Rowset版本生效。任务中指定了发布的生效version版本信息。以后BE存储层才会将这个版本的Rowset设置为可见。
  4. Rowset加入到BE存储层的Tablet进行管理。

四、删除流程

目前Delete有两种实现,一种普通的删除类型为DELETE,一种为LOAD_DELETE。

4.1 DELETE 执行流程

DELETE的支持通常的删除操做,实现较为简单,DELETE模式下没有对数据进行实际删除操做,而是对数据删除条件进行了记录。存储在Meta信息中。当执行Base Compaction时删除条件会一块儿被合入到Base版本中。Base版本为Tablet从[0-x]的第一个Rowset数据版本。具体流程以下:

  1. 删除时由FE直接下发删除命令和删除条件。
  2. BE在本地启动一个EngineBatchLoadTask任务,生成新版本的Rowset,并记录删除条件信息。这个删除记录的Rowset与写入过程的略有不一样,该Rowset仅记录了删除条件信息,没有实际的数据。
  3. 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流程分析》。

相关文章
相关标签/搜索