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)放在一起进行计算,最终将这条记录“消除”,使其在查询结果中不可见。
  • 局限性
    1. 必须有主键和合并引擎RowKind 机制只在 primary-key 表中生效,因为它需要根据主键来找到所有相关的记录进行合并。对于没有主键的 Append-Only 表,RowKind 毫无意义。
    2. 读时开销:在 Compaction 发生之前,读取数据需要同时读取多个层级的文件,并在内存中进行合并计算,以判断哪些数据是有效的,这会带来一定的读取开销。
    3. Append-Only 表的难题:对于普通的 Append-Only 表,如果你想删除某一行,是没有办法的。因为没有主键和合并引擎,你无法通过写入 -D 记录来抵消已有的数据。传统的做法是找出包含要删除数据的整个文件,重写这个文件,把要删除的数据剔除掉,这个代价极其高昂。

DeletionVector 的出现:解决性能和普适性问题

DeletionVector 的出现就是为了解决上述 RowKind 的局限性,尤其是非主键表。它是一种Merge-on-Write (MOW) 思想的实现。

  • 工作机制

    1. 定位:当执行 DELETE FROM T WHERE ... 时,Paimon 首先会扫描数据,找到满足条件的行所在的物理位置(即哪个数据文件,以及是文件中的第几行)。
    2. 标记:Paimon 不会重写数据文件,也不会写入 -D 记录。取而代之,它会维护一个位图(Bitmap),这个位图就是删除向量。如果文件 file_A 的第 100 行和第 200 行被删除了,Paimon 就会在这个文件的删除向量中,将第 100 和 200 位标记为 1
    3. 持久化:这个删除向量(位图)会作为一个非常小的独立索引文件DELETION_VECTORS_INDEX)被保存下来。
    4. 读时过滤:当查询数据时,Paimon 会先检查是否存在删除向量索引。如果存在,就会在读取数据文件之前,先加载这个位图。当读取 file_A 时,它会根据位图直接跳过第 100 行和第 200 行的解析,就像它们不存在一样。
  • 优势

    1. 普适性:它不依赖主键和合并引擎,因此可以用于所有表类型,包括 Append-Only 表。这使得 Append-Only 表也具备了高效 DELETE/UPDATE 的能力。
    2. 高性能DELETE 或 UPDATE 操作的开销非常小,因为它只涉及读数据和写一个极小的索引文件,避免了重写庞大数据文件带来的巨大 I/O 开销。
    3. 读取高效:在读取时,只是多了一步加载位图的操作,然后就可以直接跳过无效数据,避免了 RowKind 机制中复杂的内存合并计算。

结论

DeletionVector 并不是要替代 RowKind,而是对 Paimon 更新删除能力的一个巨大增强和优化。它将昂贵的“写时复制/合并”(Copy-on-Write / Merge-on-Read)操作,变成了一个轻量级的“写时标记”(Merge-on-Write),极大地提升了 DELETE/UPDATE 的性能和适用范围,是 Paimon 实现高性能实时湖仓的关键技术之一。

三种工作方式

Paimon 的主键表主要有三种工作模式:

  1. MOR (Merge-on-Read) 读时合并:

    • 写入: 数据直接以增量文件(L0 文件)的形式写入,写入速度非常快。
    • 读取: 读取时,需要将所有相关的数据文件(包括存量文件和增量文件)进行归并排序,合并具有相同主键的记录,以获取最新结果。这个“Merge”操作发生在读取时
    • 优缺点: 写入性能好,但读取性能较差。
  2. COW (Copy-on-Write) 写时复制:

    • 写入: 每当有数据写入时,Paimon 会找到受影响的旧数据文件,将旧数据和新数据一起合并,然后重写成一个全新的文件。这个过程类似物理上的“复制-合并-写入”。
    • 读取: 读取时非常快,因为所有数据都已经是合并好的,不需要做额外的合并操作。
    • 优缺点: 读取性能好,但写入时因为重写整个文件,写放大严重,写入性能较差。
  3. 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,查询需要:
    1. 读取 F1,发现 key=1 的值是 A
    2. 加载 F1 的 DV,发现 key=1 已被删除。
    3. 转而从 F2 中读取 key=1 的新值 B

​问题在于​​:这个过程需要​​全局的、精确的版本控制​​来协调所有 L0 文件和其对应的 DV。在分布式、多版本的数据湖中,实现这种强一致的实时视图​​极其复杂且开销巨大​​。


Paimon 的解决方案:同步压缩作为屏障

为了避免上述复杂性,Paimon MOW 模式采用了一种更简单、更高效的策略:

  1. ​写入时​​:

    • 新数据写入 L0 文件,并为其生成删除向量。
    • ​但这些 L0 文件被标记为“未完成”或“未压缩”状态​​。
  2. ​压缩时(Compaction)​​:

    • 一个​​同步的压缩作业​​会立即处理这些新的 L0 文件。
    • 它将 L0 文件与之前的文件​​合并​​,并​​应用删除向量​​,生成一个新的、已压缩的高层文件(如 L1)。
    • 这个新文件是“干净”的,不依赖外部 DV 索引,所有删除已被物理处理。
  3. ​可见性​​:

    • ​只有经过压缩处理后的文件​​才会被提交并对外可见。
    • 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 成为生产环境中​​兼顾吞吐量与延迟的推荐选择​​。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐