Paimon 删除向量和MOW
RowKind和DeletionVector是Paimon中两种不同的数据删除机制。RowKind作用于逻辑层,通过标记变更类型(增删改)配合主键表的LSM-Tree结构实现数据合并,但仅适用于主键表且存在读取开销。DeletionVector则采用物理层的位图标记方式,通过独立索引文件记录删除行位置,实现读时过滤,具有通用性高、I/O开销小的优势,适用于所有表类型。二者各有侧重,Deletion
RowKind
可以标记删除,但它和 DeletionVector
(删除向量)是为解决不同场景下的问题而设计的两种机制,它们工作在不同的层面。
简单来说:
RowKind
是“逻辑层”的变更指令,主要用于primary-key
表的 LSM-Tree 合并过程。DeletionVector
是“物理层”的读时过滤优化,用于在不重写数据文件的前提下,快速地“标记”某些行为无效,极大地提升了DELETE
/UPDATE
操作的性能,并使其能应用于所有表类型。
RowKind
是附加在每一行数据上的一个字段,有 +I
(INSERT), -U
(UPDATE_BEFORE), +U
(UPDATE_AFTER), -D
(DELETE) 四种类型。
- 工作机制:它完全依赖于
primary-key
表的 Merge-Tree (LSM) 结构和其合并引擎(Merge Engine)。- 当执行
DELETE FROM T WHERE pk = 1
时,Paimon 会写入一条(pk=1, ..., RowKind=-D)
的新记录。 - 这条
-D
记录和原来的数据记录可能存在于不同的数据文件(不同的 Level)。 - 只有在读取时或者后台 Compaction(合并)时,Paimon 的合并引擎才会将主键相同的一组记录(比如一个
+I
和一个-D
)放在一起进行计算,最终将这条记录“消除”,使其在查询结果中不可见。
- 当执行
- 局限性:
- 必须有主键和合并引擎:
RowKind
机制只在primary-key
表中生效,因为它需要根据主键来找到所有相关的记录进行合并。对于没有主键的 Append-Only 表,RowKind
毫无意义。 - 读时开销:在 Compaction 发生之前,读取数据需要同时读取多个层级的文件,并在内存中进行合并计算,以判断哪些数据是有效的,这会带来一定的读取开销。
- Append-Only 表的难题:对于普通的 Append-Only 表,如果你想删除某一行,是没有办法的。因为没有主键和合并引擎,你无法通过写入
-D
记录来抵消已有的数据。传统的做法是找出包含要删除数据的整个文件,重写这个文件,把要删除的数据剔除掉,这个代价极其高昂。
- 必须有主键和合并引擎:
DeletionVector
的出现:解决性能和普适性问题
DeletionVector
的出现就是为了解决上述 RowKind
的局限性,尤其是非主键表。它是一种Merge-on-Write (MOW) 思想的实现。
-
工作机制:
- 定位:当执行
DELETE FROM T WHERE ...
时,Paimon 首先会扫描数据,找到满足条件的行所在的物理位置(即哪个数据文件,以及是文件中的第几行)。 - 标记:Paimon 不会重写数据文件,也不会写入
-D
记录。取而代之,它会维护一个位图(Bitmap),这个位图就是删除向量。如果文件file_A
的第 100 行和第 200 行被删除了,Paimon 就会在这个文件的删除向量中,将第 100 和 200 位标记为1
。 - 持久化:这个删除向量(位图)会作为一个非常小的独立索引文件(
DELETION_VECTORS_INDEX
)被保存下来。 - 读时过滤:当查询数据时,Paimon 会先检查是否存在删除向量索引。如果存在,就会在读取数据文件之前,先加载这个位图。当读取
file_A
时,它会根据位图直接跳过第 100 行和第 200 行的解析,就像它们不存在一样。
- 定位:当执行
-
优势:
- 普适性:它不依赖主键和合并引擎,因此可以用于所有表类型,包括 Append-Only 表。这使得 Append-Only 表也具备了高效
DELETE
/UPDATE
的能力。 - 高性能:
DELETE
或UPDATE
操作的开销非常小,因为它只涉及读数据和写一个极小的索引文件,避免了重写庞大数据文件带来的巨大 I/O 开销。 - 读取高效:在读取时,只是多了一步加载位图的操作,然后就可以直接跳过无效数据,避免了
RowKind
机制中复杂的内存合并计算。
- 普适性:它不依赖主键和合并引擎,因此可以用于所有表类型,包括 Append-Only 表。这使得 Append-Only 表也具备了高效
结论:
DeletionVector
并不是要替代 RowKind
,而是对 Paimon 更新删除能力的一个巨大增强和优化。它将昂贵的“写时复制/合并”(Copy-on-Write / Merge-on-Read)操作,变成了一个轻量级的“写时标记”(Merge-on-Write),极大地提升了 DELETE
/UPDATE
的性能和适用范围,是 Paimon 实现高性能实时湖仓的关键技术之一。
三种工作方式
Paimon 的主键表主要有三种工作模式:
-
MOR (Merge-on-Read) 读时合并:
- 写入: 数据直接以增量文件(L0 文件)的形式写入,写入速度非常快。
- 读取: 读取时,需要将所有相关的数据文件(包括存量文件和增量文件)进行归并排序,合并具有相同主键的记录,以获取最新结果。这个“Merge”操作发生在读取时。
- 优缺点: 写入性能好,但读取性能较差。
-
COW (Copy-on-Write) 写时复制:
- 写入: 每当有数据写入时,Paimon 会找到受影响的旧数据文件,将旧数据和新数据一起合并,然后重写成一个全新的文件。这个过程类似物理上的“复制-合并-写入”。
- 读取: 读取时非常快,因为所有数据都已经是合并好的,不需要做额外的合并操作。
- 优缺点: 读取性能好,但写入时因为重写整个文件,写放大严重,写入性能较差。
-
MOW (Merge-on-Write) 写时合并: 这是介于 MOR 和 COW 之间的一种模式。它的“Merge”操作体现在写入数据时的一个逻辑合并过程:
- 写入: 当一条更新或删除的记录到来时,Paimon 需要找到这条记录的旧版本。它会查询现有的 LSM 树结构,定位到旧记录所在的文件和行号。
- 然后,它并不重写旧的数据文件,而是生成一个删除向量,标记旧记录为已删除。
- 最后,如果是更新操作,它会将新记录写入一个新的文件中。
这个“查找旧数据 -> 标记删除 -> 写入新数据”的过程,就是一种在**写入(Write)阶段完成的合并(Merge)**操作。它不是像 COW 那样进行物理文件合并,而是通过删除向量来完成逻辑上的合并。
- 读取: 读取时,应用删除向量过滤掉已删除的行,性能接近 COW,好于 MOR。
- 优缺点: 写入性能比 COW 好(避免了重写文件),读取性能比 MOR 好(避免了读时归并),实现了性能上的平衡。
Paimon MOW L0不可见
MOW 模式下,L0 文件在异步压缩完成前对读取不可见,这是为了在保证高性能的同时,提供最终一致性的读取视图,避免复杂的并发控制。
根本原因:一致性(Consistency)与性能的权衡
MOW 的核心是 删除向量(Deletion Vectors, DV)。DV 是一个外部索引,它必须与对应的数据文件保持严格一致,才能正确工作。
1. L0 文件的特性
- 未压缩(Uncompacted):L0 文件是内存直接刷盘的结果,包含最新的变更。
- 可能存在重叠:多个 L0 文件的主键范围可能重叠,包含相同主键的多版本数据。
2. MOW 的挑战
- 假设一个 L0 文件
F1
包含记录(key=1, value=A)
。 - 随后一个更新操作将
key=1
的值改为B
,并写入新的 L0 文件F2
。 - MOW 机制会为
F1
中的key=1
生成一个删除向量(DV),标记该记录为逻辑删除。 - 此时,如果允许读取
F1
,查询需要:- 读取
F1
,发现key=1
的值是A
。 - 加载
F1
的 DV,发现key=1
已被删除。 - 转而从
F2
中读取key=1
的新值B
。
- 读取
问题在于:这个过程需要全局的、精确的版本控制来协调所有 L0 文件和其对应的 DV。在分布式、多版本的数据湖中,实现这种强一致的实时视图极其复杂且开销巨大。
Paimon 的解决方案:同步压缩作为屏障
为了避免上述复杂性,Paimon MOW 模式采用了一种更简单、更高效的策略:
-
写入时:
- 新数据写入 L0 文件,并为其生成删除向量。
- 但这些 L0 文件被标记为“未完成”或“未压缩”状态。
-
压缩时(Compaction):
- 一个同步的压缩作业会立即处理这些新的 L0 文件。
- 它将 L0 文件与之前的文件合并,并应用删除向量,生成一个新的、已压缩的高层文件(如 L1)。
- 这个新文件是“干净”的,不依赖外部 DV 索引,所有删除已被物理处理。
-
可见性:
- 只有经过压缩处理后的文件才会被提交并对外可见。
- L0 文件在压缩完成前,对读取者是不可见的。
这就相当于设置了一个“屏障”(Barrier):所有写入必须先通过压缩“关卡”整理好,才能被读取。这保证了读取者总能看到一个自洽的、不依赖外部元数据的数据视图。
两种配置与影响
这种机制有两种配置,对应不同的权衡:
配置 | 工作机制 | 优点 | 缺点 |
---|---|---|---|
同步压缩(默认) | L0 写入后,立即触发同步压缩,完成后数据才可见。 | 强一致性:读取总能获得最新已提交的数据。 | 写入延迟增加:每次提交都要等待压缩完成。 |
异步压缩 | L0 写入后先可见,压缩在后台异步进行。 | 写入延迟低。 | 最终一致性:读取可能短暂看到旧数据(L0未压缩)和新数据(已压缩)的混合视图,直到压缩完成。 |
因此,默认配置下,MOW 模式的 L0 文件在同步压缩完成前对读取不可见。如果启用异步压缩,L0 文件是可见的,但会带来短暂的数据不一致风险。
对比 MOR 和 COW
为了更好地理解,我们可以对比一下:
- MOR (Merge-On-Read):
- L0 对读取可见。
- 读取时进行昂贵的多路归并和主键合并来解决冲突。读慢。
- COW (Copy-On-Write):
- 没有 L0 的概念,因为写入时就完成了全量合并。写慢。
- MOW (Merge-On-Write):
- 取了一个中间值:在写入后、读取前,通过一个快速的、同步的轻量级压缩来解决冲突。用轻微的写入延迟换取了极高的读取性能。
结论
MOW 模式下对 L0 文件可见性的限制,是 Paimon 在设计上的一种明智取舍。它通过同步压缩屏障的机制,避免了在读取时进行复杂的、全局的一致性协调,从而在保证数据正确性的前提下,同时获得了优异的写入和读取性能。这种设计使得 MOW 成为生产环境中兼顾吞吐量与延迟的推荐选择。
更多推荐
所有评论(0)