基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

Paimon 的读时合并(Merge-on-Read)与写时合并(Merge-on-Write / Deletion Vectors)模式的区别

知识点图片

根据 Apache Paimon 的官方文档(Master 分支概念与 Table Mode 章节),主键表(Primary Key Table)主要通过 LSM 树(Log-Structured Merge-tree) 来组织和存储数据。针对数据的写入与更新,Paimon 提供了不同的处理模式,其中最核心的对比就是读时合并(Merge-on-Read,简称 MOR)写时合并(Merge-on-Write,此处特指基于 Deletion Vectors 的 MOW 模式)

以下根据 Paimon 文档对这两种模式进行详细剖析与对比:


一、 核心概念与工作原理

1. 读时合并(Merge-on-Read,MOR)— 默认模式

  • 写入阶段
    Paimon 的默认主键表模式。当有数据写入、更新或删除时,写入端(如 Flink / Spark 任务)只需将数据快速刷写(Flush)到 LSM 树的 L0(Level 0)文件中,并在后台触发 Minor Compaction(轻量级合并)以限制文件数量。
    • 特点:写入过程不作实时的主键去重或旧数据物理删除,只进行顺序追加和常规的 LSM 树分层。
  • 读取阶段
    在查询时,读取端(Reader)必须在内存中对所有相关的 Sorted Runs(有序文件组)进行多路归并(Multi-way Merge)。通过比较主键(Primary Key)及序列号(Sequence Number)等信息,过滤掉旧版本数据,仅输出最新的一行。

2. 写时合并(Merge-on-Write,MOW — 基于 Deletion Vectors)

  • 写入阶段
    需要通过配置 'deletion-vectors.enabled' = 'true' 来开启。在数据写入或更新时,Paimon 的 Writer 会主动去查询 LSM 树(进行主键检索 Lookup),找到该主键之前所在的数据文件和具体的行偏移量(Row Offset),并在对应的 Deletion Vector(删除向量,通常为 Bitmap 格式) 中标记这一行已被删除,随后产生一个单独的 Deletion File(删除文件)。
    • 特点:它不会像传统的 COW(Copy-on-Write)那样去重写整个数据文件,而是以轻量级的“逻辑标记”方式,在写入时就精确锁定了需要过滤的数据位置。
  • 读取阶段
    读取端(Reader)无需进行复杂的内存多路归并,只需直接加载数据文件,并利用对应的 Deletion Vector 进行行级过滤(Data + Filter with Deletion Vector),即可直接拼装出最终的正确数据。

二、 核心性能与特性对比

对比维度 读时合并 (Merge-on-Read / MOR) 写时合并 (Merge-on-Write / MOW with DV)
写入性能与开销 极高。写入操作极快,几乎没有 Lookup 检索和生成额外索引的负担。 中等(引入额外开销)。由于在写入时需要进行 LSM 树 Lookup 检索,并生成/写入对应的 Deletion File(删除向量文件),因此写入吞吐量会受到一定影响。
读取性能与开销 中等/偏低。Reader 需要进行内存中的多路归并及主键对比计算,CPU 和内存消耗较大,在大数据量下容易成为瓶颈。 极高。无需在内存中进行多路归并计算,只做基于 Bitmap 的行过滤,读取效率与普通的 AppendOnly 表或 COW 表相当。
并发读取能力 有限(受限于 Bucket 数量)。由于多路合并要求同一个 Bucket 内的所有有序运行文件(Sorted Runs)协同读取,单个 Bucket 在 MOR 模式下通常只能由单线程读取。如果 Bucket 的数据量过大,将严重限制并发度。 无限制。并发读取不再受 Bucket 数量的限制。不同的 Task 可以并发、多线程地读取同一个 Bucket 下的不同数据文件并应用 DV 进行过滤。
非主键列过滤下推 (Filter Pushdown) 不支持。如果对非主键列进行过滤下推(Data Skipping),可能会不小心过滤掉更新后的最新数据,从而错误地暴露旧数据。 完美支持。因为数据行是否存活在读取前就已经通过 Deletion Vector 唯一确定,非主键列上的过滤条件可以安全下推。
推荐适用场景 适合实时写入吞吐要求极高,但下游查询频率不高,或者只做增量流式消费(Streaming Read)的场景。 适合OLAP 分析、频繁交互式查询、高性能批处理查询的场景。

三、 Deletion Vectors (MOW) 的底层设计与机制

Paimon 文档中对 Deletion Vector 的底层机制也有深入的设计说明:

  1. 文件格式与存储 (Table Index)
    • Deletion Vector(删除文件)存储在每个 Bucket 目录下的 index 目录中。
    • 该文件是一个二进制文件,采用特定的格式记录:版本号 (1 byte) + <序列化长度, 序列化二进制体, 校验和>
  2. Bitmap 位图实现
    Paimon 提供了两种位图表示:
    • 32位位图 (默认):基于 RoaringBitmap(org.roaringbitmap.RoaringBitmap)。
    • 64位位图(配置 'deletion-vectors.bitmap64' = 'true'):针对 64 位整型位置进行了优化。此模式与 Apache Iceberg 规范兼容,如果希望外部引擎(如 Iceberg Reader)直接读取 Paimon 的 Deletion Vector 并达到高性能实时同步,可以启用该配置。
  3. L0 (Level 0) 文件过滤延迟
    由于 Level 0 文件在写入时还处于未完全整理状态,开启 MOW 时虽然会在写时生成 DV,但在利用 Time Travel(时间旅行)读取 APPEND 快照时,可能会遇到由于未完全完成过滤带来的微量数据延迟。
  4. 垃圾回收 (GC)
    当一个数据文件本身由于后续的 Compaction 被物理删除时,指向该数据文件的 Deletion Vector 也会被逻辑标记为无效,并在对应的 Metadata / Snapshot 过期后被彻底清理。

四、 如何在 DDL 中启用相关模式

在创建主键表时,可以通过配置项来灵活切换这两种模式:

  • 启用 读时合并(Merge-on-Read) (默认行为):

    sql
    CREATE TABLE my_table (
        id BIGINT PRIMARY KEY NOT ENFORCED,
        name STRING,
        dt STRING
    ) WITH (
        'bucket' = '4'
        -- 默认即为 MOR 模式
    );
  • 启用 写时合并(Merge-on-Write via Deletion Vectors)

    sql
    CREATE TABLE my_table (
        id BIGINT PRIMARY KEY NOT ENFORCED,
        name STRING,
        dt STRING
    ) WITH (
        'bucket' = '4',
        'deletion-vectors.enabled' = 'true',      -- 开启写时合并模式
        'deletion-vectors.bitmap64' = 'true'       -- (可选)开启 64 位位图,以获得更好的 Iceberg 兼容性
    );
  • (补充) 传统的写时复制(Copy-on-Write / COW)
    如果需要传统意义上完全同步合并的 COW(写完即产生完整合并后的新 Parquet/ORC 文件,代价极高),可以通过配置强制每次 commit 都进行 Full Compaction:
    'full-compaction.delta-commits' = '1'

00:00
00:00