:查询快了,Checkpoint 却超时:Paimon MOR、COW、MOW 到底在交换什么)
设想一次订单明细表的性能治理最初采用 MORFlink 写入很稳Spark 查询却被多组 Sorted Run 拖慢随后改成每次 Full Compaction查询快了Checkpoint 又开始逼近超时再切到 Deletion Vector读写看似都改善新数据的可见时间却开始受 Compaction 影响。三次调整都不是“配置没生效”恰恰是配置太忠实地生效了。MOR、COW、MOW 解决的是同一笔更新成本由谁承担而不是把这笔成本消灭。MOR、COW、MOW 没有消灭主键更新成本只是分别让读取、写入全量合并或写时旧行定位来付款。下文的三种路径统一固定在Apache Paimon 2.0.0源码基线为release-2.0.0/604e6d5e...不把后续版本行为倒灌进比较。先别问哪种最快先问成本准备由谁承担共同前提必须写进 POC同一数据量、主键与 Bucket相同更新比例、Checkpoint、对象存储及计算资源同时运行点查、非主键过滤和全表聚合。用户真实规模未提供因此这里只比较执行路径不宣布固定赢家。模式Paimon 2.0.0 配置写入路径读取路径主要风险MOR默认写 L0做 Minor Compaction多路归并同主键Sorted Run 多时读 CPU 与单 Bucket 长尾COWfull-compaction.delta-commits1每次提交同步 Full Compaction直接读最高层结果严重写放大、Checkpoint 超时MOWdeletion-vectors.enabledtrue查询旧行并写 Deletion Vector过滤旧物理行L0 可见依赖 Compaction异步时有延迟Table Mode 官方文档 对 2.0.0 的建议是普通deduplicate主键表优先评估 MOW但这不是无条件默认答案。更新率、Bucket 大小、查询过滤、对象存储延迟和可见性 SLA 都会改变结果。选择 MOR写入先省下来的成本查询时再付MOR 中不同 Sorted Run 的主键范围可以重叠。同一个 Bucket 的读取必须合并这些 Run且单棵 LSM Tree 的读取受单线程限制。非主键条件也不能在合并前随意过滤否则可能过滤掉新行、留下旧行得到错误结果。写入快 → L0/Sorted Run 增多 → 同主键分散在多个文件 → 读取多路归并 → 热 Bucket 决定整条查询尾延迟选择 COW查询变轻Checkpoint 开始承担重写full-compaction.delta-commits1意味着每个增量提交都触发 Full Compaction。它适合写入低、读取重且能接受提交开销的表不适合用来掩盖高频 Checkpoint 与 Bucket 设计问题。只调大 Checkpoint Timeout 会让事故晚一点暴露不会降低重写字节数。选择 MOW少做读时归并但要守住可见性边界Deletion Vector 标记旧数据文件中需要隐藏的行读取无需再把所有同主键记录做完整归并。但官方文档明确Deletion Vector 模式的 L0 文件只有 Compaction 后才可见。默认同步 Compaction 保护可见性开启异步后写入响应与查询可见之间可能出现间隔。验证时要记录四条曲线而不是只跑一次 SQL每次 Checkpoint 的耗时与超时比例写入与 Compaction 的对象存储读写字节$files中各 Bucket 的层级、文件数与大小哨兵主键从源端时间到批读首次可见的延迟。真正的对比不看单点要把四条曲线放到同一时间轴-- 观察文件层级与 Bucket 物理债务只读。SELECT*FROMorders_mor$files;SELECT*FROMorders_cow$files;SELECT*FROMorders_mow$files;-- 观察 APPEND/COMPACT 提交节奏只读。SELECTsnapshot_id,commit_kind,commit_time,total_record_countFROMorders_mow$snapshotsORDERBYsnapshot_id;MOR 重点看扫描 Sorted Run 和读 CPUCOW 重点看每次提交重写字节与 Checkpoint P99MOW 还必须记录哨兵行从源端到批读首次可见的时间。只跑一次查询会漏掉 Compaction 周期和异步可见性。决定切换前先让三张影子表跑完同一段流量用同一份更新流同时写三张影子表固定 Bucket、并行度、Checkpoint、对象存储和查询集合分别记录写入延迟、Compaction、查询扫描量和新数据可见时间。任何一个模式只有在业务查询与新鲜度 SLO 下获胜才有资格进入灰度而不是根据缩写或单次跑分决定。用 Java 同时观察三张影子表避免各说各话固定版本源码如何分流三种成本在release-2.0.0/604e6d5e...中paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java的newWrite()读取bucketMode()与deletionVectorsEnabled()选择普通 MergeTree 写入、Dynamic Bucket 或 Deletion Vector 写入分支paimon-core/src/main/java/org/apache/paimon/metastore/VisibilityWaitCallback.java的可见性等待又对 L0 Deletion Vector 文件做专门处理。对应源码KeyValueFileStore、VisibilityWaitCallback。因此 Java 同时查看三张影子表的 Snapshot、文件数和哨兵行可见时间MOR 的信号是读时多路合并COW 是频繁 Full Compaction 提交MOW 则要额外核对 L0 与 Deletion Vector 可见性。单看最终行数无法区分成本付在哪一段。以下只读程序按paimon-flink-1.20:2.0.0API 编写要求三张影子表已由相同负载生成本环境未运行全文示例。importorg.apache.flink.table.api.EnvironmentSettings;importorg.apache.flink.table.api.TableEnvironment;publicfinalclassTableModeProbe{publicstaticvoidmain(String[]args){if(args.length!1)thrownewIllegalArgumentException(warehouse is required);Stringwarehouseargs[0].replace(,);TableEnvironmenttTableEnvironment.create(EnvironmentSettings.newInstance().inBatchMode().build());t.executeSql(CREATE CATALOG p WITH (typepaimon,warehousewarehouse));t.executeSql(USE CATALOG p);for(Stringtable:newString[]{orders_mor,orders_cow,orders_mow}){System.out.println( table );t.executeSql(SELECT snapshot_id,commit_kind,commit_time FROM demo.table$snapshots ORDER BY snapshot_id).print();t.executeSql(SELECT COUNT(*) FROM demo.table).print();}}}代码不伪造性能数字只保证三张表经过同一观测入口。MOR 走 MergeTree 读时合并COW 由full-compaction.delta-commits1触发 Full CompactionMOW 进入 Deletion Vector 维护分支。失败信号是影子表缺少预期 Snapshot、行数不一致或 MOW 哨兵行超过可见性 SLO需要把 Java 输出时间与 Flink Checkpoint 和对象存储指标合并后才可选型。准备相同数据、相同更新比例和相同 Bucket将三张表分别设置为 MOR、COW、MOW。持续写入至少覆盖多个 Compaction 周期同时跑主键点查、非主键过滤和全表聚合。只有写入、读取、存储请求与新鲜度四项一起达标模式选择才成立。生产切换应使用影子表或隔离分区双写完成主键集合、最大业务版本与核心金额对账后再切读。若新鲜度超限、Checkpoint P99 持续增长或对象存储放大越过预算应立即停止直接修改唯一生产表缺少可靠回滚证据。选择 MOR、COW 或 MOW本质上是在决定谁为旧版本买单以及账单在什么时候到。官方资料Table ModePrimary Key Table CompactionPrimary Key Table