基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

讲讲 Doris 中主键模型的写时合并(Merge-on-Write, MoW)

在 Apache Doris 中,主键模型(Unique Key Model) 主要用于满足需要更新和去重的业务场景(例如 CDC 数据同步、订单状态更新等)。

在 Doris 1.2 版本之前,主键模型默认采用的是 读时合并(Merge-on-Read, MoR) 机制;而从 1.2 版本开始引入了 写时合并(Merge-on-Write, MoW),并在 Doris 2.0 版本中将其作为主键模型的默认实现。

MoW 的引入是 Doris 在实时更新场景下的一次重大架构升级。下面我将为你详细剖析 Doris 中主键模型的写时合并(MoW)机制。


1. 为什么需要写时合并(MoW)?

要理解 MoW,首先要了解之前的 读时合并(MoR) 的痛点。

  • 读时合并(MoR)的原理:写入数据时,Doris 只是简单地将新数据(无论是插入还是更新)追加写入到底层。当用户发起查询(Read)时,系统需要把所有历史版本的数据读取出来,在内存中根据主键进行比较,保留版本号最大的那一行,然后再进行聚合、过滤等操作。
  • MoR 的痛点
    1. 查询性能差:读取时需要做大量的比较和合并操作,极其消耗 CPU。
    2. 谓词下推失效:因为必须先合并才能知道哪行是最终数据,很多索引(如 ZoneMap)和条件过滤(WHERE)无法下推到存储层,导致扫描了大量无用数据。

为了解决查询慢的问题,Doris 引入了 写时合并(MoW)核心思想是:把合并的代价从“查询端”转移到“写入端”,从而换取极致的查询性能。


2. 写时合并(MoW)的核心机制

MoW 的底层实现主要依赖两个关键技术:Delete Bitmap(删除位图)Primary Key Index(主键索引)

A. 核心数据结构:Delete Bitmap

在 MoW 模式下,对于每一个数据分片(Tablet)中的每一个数据文件(Segment),Doris 都会维护一个与之对应的 Delete Bitmap。

  • 作用:这个 Bitmap 记录了当前数据文件中,哪些行(Row ID)已经被标记为失效/删除(即被后来的新数据覆盖了)。
  • Bitmap 中的位如果为 1,表示该行已失效;为 0 表示该行有效。

B. 写入流程(Write)

当新的一批数据(Rowset)写入 Doris 时,主要发生以下步骤:

  1. 追加写入:新数据首先依然是顺序追加写入到新的 Segment 文件中。
  2. 主键冲突检测:系统利用主键索引(Primary Key Index) 去历史的数据文件中查找,看这些新写入的主键是否在以前出现过。
  3. 更新 Delete Bitmap:如果在历史文件(旧 Segment)中找到了相同的主键,系统就会定位到那条旧数据的 Row ID,并将对应的历史 Delete Bitmap 中的该位设置为 1(标记为删除)。
  4. 可见性:当写入事务提交时,新的数据文件和修改后的 Delete Bitmap 会同时生效。

C. 查询流程(Read)

因为写入时已经做好了标记,查询时的流程变得极其简单和高效:

  1. 读取数据文件(Segment)。
  2. 读取对应的 Delete Bitmap。
  3. 按位过滤:直接把 Delete Bitmap 中为 1 的行过滤掉。
  4. 剩下的数据就是最终的有效数据,直接返回。

查询优势:完全不需要在内存中做主键比较和合并!这使得 MoW 模式下的主键表,查询性能几乎逼近明细模型(Duplicate Key),并且完美支持所有的谓词下推和二级索引。

D. Compaction(后台合并)

随着不断写入,被标记为删除的历史数据会越来越多,占用磁盘空间。Doris 的后台会定期触发 Compaction 任务:

  • 读取多个小文件。
  • 根据 Delete Bitmap 物理剔除掉那些被标记为删除的行。
  • 生成一个干净的大文件,并释放磁盘空间。

3. MoW 的优缺点分析

优点(极大的提升):

  1. 查询性能极速提升:相比于 MoR 模式,MoW 模式下的查询性能通常有 3倍到 10倍 的提升(甚至更高)。
  2. 全面支持谓词下推:WHERE 条件可以推到存储层,利用 ZoneMap、BloomFilter 等索引直接过滤数据,大幅减少 IO。
  3. 更稳定的查询延迟:MoR 模式下,如果后台 Compaction 不及时,查询会非常慢(因为积累的版本多)。MoW 模式下,查询性能对 Compaction 的依赖大大降低,延迟非常稳定。

缺点(不可避免的代价):

  1. 写入性能略有损耗:因为写入时需要查询主键索引并更新 Delete Bitmap,所以写入速度会比 MoR 略慢,CPU 和内存消耗在写入端会略高。
  2. 内存开销增加:为了保证写入速度,Doris 会将主键索引缓存在内存中(通常是 Block 级别的索引或者持久化的 B+ 树),这会占用一定的节点内存。

(注:Doris 社区经过多次优化,目前 MoW 的写入性能损耗已经控制在非常小的范围内,完全能满足绝大多数高并发实时写入的要求。)


4. 如何在 Doris 中使用 MoW?

在 Doris 2.0 及更高版本中,创建 Unique Key 表时,默认就已经开启了 MoW

如果你使用的是 Doris 1.2 版本,或者需要显式声明,可以通过 PROPERTIES 中的 enable_unique_key_merge_on_write 参数来控制:

sql
CREATE TABLE users (
    user_id INT,
    user_name VARCHAR(50),
    age INT,
    update_time DATETIME
)
UNIQUE KEY(user_id) -- 声明为主键模型
DISTRIBUTED BY HASH(user_id) BUCKETS 10
PROPERTIES (
    "enable_unique_key_merge_on_write" = "true" -- 开启写时合并 (2.0+ 默认开启)
);

总结

Doris 的写时合并(Merge-on-Write)是典型的“空间和写入时间换取读取时间”的设计。通过引入主键索引和 Delete Bitmap,它成功地将复杂的合并操作从用户查询的关键路径上剥离,放到了数据摄入(Ingestion)阶段。这使得 Doris 在面对 MySQL/Oracle CDC 实时同步、实时大屏更新等场景时,依然能够提供毫秒级的极速分析体验。

00:00
00:00