深入分析:Iceberg v3「删除向量(Deletion Vectors, DV)」如何缓解 CDC 场景写放大

1. CDC 写放大到底"放大"在哪里?

CDC(Change Data Capture)入湖常见形态是持续 UPDATE/DELETE/INSERT(尤其 Upsert)。在"不可变数据文件(Parquet/ORC)+ 表格式元数据"的体系里,写放大主要来自三层:

  1. 数据层写放大(Data write amplification)

    • 典型 Copy-on-Write(COW):更新/删除少量行也可能需要重写整批数据文件(把未变更行"搬运"到新文件),写入字节数远大于变更字节数。
  2. 删除表示层写放大(Delete representation amplification)

    • 典型 Merge-on-Read(MoR)在 Iceberg v2 使用 position delete files:频繁微批/流式更新会生成大量小 delete files("雪花"式堆积)。

    • 结果是:写入端要写很多小文件,后续还要 compaction/rewrite;同时元数据(manifest)也膨胀。

  3. 元数据与计划层写放大(Metadata / planning amplification)

    • 文件数上升 → manifest/manifest list 里要记录更多条目 → 写一次 commit 要更新更多元数据。

    • 读取/查询 planning 也会变慢(需要处理更多 delete 相关条目)。

你问的"CDC 场景下的写放大",通常说的核心矛盾是:

行级变更比例很小,但系统要付出"接近重写/重组整个文件集合"的写入成本

Iceberg v3 的 DV 主要瞄准的是:

  • MoR 路线上,把"删除表示层 + 元数据层"写放大压下去;

  • 并在一定条件下,间接减少"数据层"为了删除而触发的重写压力。


2. Iceberg v2 MoR(position delete files)为什么在 CDC 下容易爆?

Iceberg Spec 将行级删除分为两类:

  • Position deletes :按(data file path + row position)标记删除;在 v2 中由 position delete file 编码,在 v3+ 可以由 deletion vector 编码。

  • Equality deletes:按列值(如 id=5)标记删除。

来源:Iceberg Table Spec(Row-level deletes 段落)

在 CDC 场景(比如每分钟一批 Upsert)里,v2 position delete files 的典型路径是:

  1. 找到受影响的数据文件(可能靠分区裁剪/索引/元数据统计,但仍然会涉及一定范围的文件)

  2. 对每个被更新/删除的行:写入一条 position delete 记录

  3. 产生一个或多个 delete files,并在 commit 里把 delete file 作为"新文件"加入元数据

问题在于:

  • 流式/微批的每次提交变更量通常不大(几十、几百、几千行),delete file 很容易变成"小文件"

  • delete file 数量会随提交次数线性增长(甚至更快,因为一个批次可能影响多个数据文件/多个分区)。

  • delete file 还会被 manifest 跟踪,导致 manifest 条目膨胀

于是出现"写放大"链路:

小变更 → 产出大量小 delete files → 元数据膨胀 + 后续不得不做 delete file rewrite/compaction → 额外写入更多文件


3. Iceberg v3 删除向量(DV)的核心思路

3.1 DV 把"删除集合"从"多文件列表"变成"单文件位图"

Puffin 规范定义了 deletion-vector-v1 blob:

  • DV 是一个 bitmapbit 置位表示对应 row position 已删除

  • 支持 64-bit row position,但为了优化常见场景(大多数 position 在 32-bit 范围内),采用"按高 32 位分桶 + Roaring bitmap 存低 32 位"的组合结构。

来源:Puffin Spec -- deletion-vector-v1 blob type

这意味着:

  • 针对同一个 data file 的删除集合,不再需要大量"追加式 delete 文件";

  • 删除信息可以以非常紧凑的形式(Roaring bitmap)存储。

3.2 DV 存在 Puffin 文件里,并通过元数据指向它

Puffin 是"存放 Iceberg manifest 里放不下的信息(indexes/statistics等)"的文件格式;DV blob 的 metadata 强制包含:

  • referenced-data-file:DV 作用的数据文件 location

  • cardinality:删除的行数

来源:Puffin Spec -- deletion-vector-v1 blob type

2.3 规范层面"约束 DV 的形态"是关键(避免回到小文件地狱)

你给的文章提到 v3 还"强制要求"每个数据文件维护单一 DV(而不是多个 delete 文件散落)。

从工程效果看,这个约束的价值在于:

  • 让删除信息天然具备"聚合"形态(一个文件一个位图),而不是"每次提交一个小文件"的形态。

  • 删除集合的更新变成"更新位图",从文件数量增长转变为"位图内容变化"。


4. DV 如何解决 CDC 写放大:分解为 3 条机制

下面把"写放大"拆成三类,对应解释 DV 的收益边界。

4.1 机制 A:把 CDC 的"每批新增 delete file"写放大,变成"更新一个位图"

v2 position delete files:

  • 每个 micro-batch 都可能产生新的 delete file(文件数 ∝ 提交次数 × 影响文件数)

v3 DV:

  • 针对同一个 data file,删除集合以 bitmap 聚合。

  • micro-batch 的删除集合可以被"并入/合并"到该文件的 DV 表达里。

结果:

  • delete 表示层写放大显著下降:小文件数量下降、delete 文件 rewrite 压力下降。

  • 元数据写放大下降:manifest 里需要跟踪的 delete file 条目减少(至少从"多个小 delete file"变为"一个 DV 指向/更少的 delete artefact")。

4.2 机制 B:把"写入字节量"从 O(重写数据文件) 降到 O(变更行数)

CDC 的典型特征是:

  • 每个 batch 改动行数 k 很小

  • 但被命中的数据文件可能很大(比如 512MB/1GB 的 Parquet)

COW 路线的数据写放大:

  • 即使只删除 1 行,也要把该文件中未删除的 N-1 行重写到新文件

  • 写入字节量近似等于"被命中数据文件总大小"

DV 路线(MoR + DV)的数据写放大:

  • 删除只体现为在 bitmap 里置位若干 row positions

  • 写入字节量近似等于"bitmap 增量 + 少量元数据更新"

直观对比(量级估算):

  • 512MB Parquet 文件里删除 10,000 行:

    • COW:可能接近重写 512MB(甚至更多,含新文件、元数据)

    • DV:写入的是"10,000 个 position 的集合"经 Roaring 压缩后的 bitmap(通常远小于 512MB)

注意:DV 本身是不可变文件的一部分(Puffin/Blob 也是文件),所以更新 DV 往往意味着写一个新的 DV blob / Puffin 文件并更新引用。

但关键差别是:

  • 你写的是"删除集合的压缩表示",不是"原始数据的全部未变更行"。

4.3 机制 C:降低后续运维性写放大(compaction / rewrite delete files)

在 v2 position delete files 模式下,CDC 会触发两类"运维性写放大":

  1. rewrite_position_delete_files:为了合并大量小 delete files

  2. rewrite_data_files:为了清理高 delete ratio 的数据文件、回收空间、改善读性能

DV 主要影响第 1 类:

  • 当删除集合已被"单 DV 聚合"时,至少不会出现成千上万小 delete files 等待合并。

对第 2 类(rewrite_data_files)影响是间接的:

  • 如果删除越来越多,哪怕是 DV,读时也需要过滤更多行;

  • 迟早你仍可能需要 rewrite_data_files 来真正回收空间/降低删除比例。

也就是说:

DV 解决的是"删除表示导致的文件爆炸与元数据爆炸",不是"无限期不重写数据文件"。


5. 为什么 DV 对 CDC 特别有效:CDC 的"稀疏、持续、小批量"与位图压缩天然匹配

CDC 更新的典型模式:

  • 每批次删除/更新的行在全表中占比很小

  • 对单个 data file 来说,被删行 position 往往是稀疏集合

Roaring bitmap 就是为稀疏整数集设计的压缩结构(Puffin spec 的 DV blob 也明确采用 Roaring 的 portable format)。

来源:Puffin Spec -- deletion-vector-v1 uses Roaring bitmap portable format

因此在 CDC 下:

  • 你写入的 DV 往往能保持很小的体积

  • 且"累计很多批次"后仍然可控(直到删除比例很高才需要触发 rewrite_data_files)


6. DV 的边界与代价:它缓解写放大,但不是"免费午餐"

6.1 DV 不会消除"新增版本行"的写入

CDC 的 UPDATE 在湖格式里通常是:

  • DELETE old row(用 DV 标记)

  • INSERT new row(写到新的数据文件)

所以:

  • DV 能把"旧行删除"从重写大文件变成写位图

  • 但"新增行"仍然要落到新的数据文件(这部分写入是不可避免的)

6.2 DV 仍需要元数据更新与可能的 Puffin 文件重写

Puffin spec 说明 DV blob metadata 里 snapshot-id / sequence-number 在创建时可能未知,需要占位(例如 -1)并在后续由表提交关联。

来源:Puffin Spec -- snapshot-id/sequence-number handling

实际实现中,这意味着:

  • DV 的生成/提交要与 Iceberg snapshot/sequence number 生命周期协调

  • 更新 DV 也会产生新的 artefact(只是体积和数量更可控)

6.3 读放大可能下降,但也可能在"高删除比例"时上升

DV 读时需要:

  • 读取数据文件 + DV

  • 在执行层做"按 position 过滤"

当某些文件删除比例过高:

  • 读时仍要扫描大量被删行对应的 page/row group(取决于引擎实现)

  • 最终还是要通过 rewrite_data_files 把"活行"抽出来,真正降低 IO


7. 面向落地:CDC 场景用 DV 的实践建议(从"写放大"出发)

下面是"机制导向"的建议:目的不是泛泛谈参数,而是保证 DV 真正把写放大压住。

7.1 选择策略:CDC 以 MoR + DV 为主,COW 作为"最终整理"

  • 实时链路:MoR + DV(削减每批写放大)

  • 后台整理:定期 rewrite_data_files(回收空间、改善读性能)

7.2 控制"DV 增长阈值"触发数据重写

你可以用两个信号触发 rewrite_data_files:

  • delete ratio(某文件 DV cardinality / row count)

  • DV cardinality 绝对值(删除集合过大,影响 CPU/过滤)

Iceberg 的维护过程(如 rewrite_data_files)本身就有与 delete ratio 相关的选项(具体各引擎/版本略有差异)。

7.3 避免"DV 过于频繁地被更新"导致的写放大回弹

虽然 DV 很小,但如果你每秒都生成一个新 Puffin/DV artefact,同样会产生:

  • 小文件数量增多

  • 元数据提交频率过高

因此 CDC pipeline 一般需要:

  • 适当的 micro-batch 粒度(例如按分钟/按批量行数聚合)

  • 对同一 data file 的 delete positions 先在内存聚合,再落盘形成 DV

(这一点更偏实现/引擎策略,但原理上与"单 DV 聚合"是一致的。)


8. 总结:DV 对 CDC 写放大"解决了什么、没解决什么"

解决了:

  1. 把 v2 position delete files 在 CDC 下的"小文件爆炸"压到"单文件位图聚合"形态(减少 delete 表示层写放大)。

  2. 把"删除/更新旧行"的数据层写入从"重写大数据文件"降为"写压缩 bitmap + 少量元数据"。

  3. 降低了为了合并 delete files 而产生的运维性写放大(rewrite_position_delete_files/小文件合并压力)。

没有直接解决的:

  1. UPDATE 的"新增行写入"仍然存在(新版本行必须落新文件)。

  2. 高删除比例文件最终仍需要 rewrite_data_files 来回收空间与优化查询。

  3. DV 的收益高度依赖引擎实现质量(DV 的生成、提交、过滤优化、以及与 row group/page 的协同)。

相关推荐
Elastic 中国社区官方博客20 小时前
Elastic Security、Observability 和 Search 现在在你的 AI 工具中提供交互式 UI
大数据·运维·人工智能·elasticsearch·搜索引擎·安全威胁分析·可用性测试
TechubNews21 小时前
Base 发布首个独立 OP Stack 框架的网络升级 Azul,将是 L2 自主迭代的开端?
大数据·网络·人工智能·区块链·能源
金融小师妹1 天前
AI政策框架解析:凯文·沃什货币体系重构与美联储治理范式转型
大数据·人工智能·重构·逻辑回归
多年小白1 天前
中科院 Ouroboros 晶圆级存算一体芯片深度解析
大数据·网络·人工智能·科技·ai
SelectDB1 天前
从 T+1 到分钟级:金城银行基于 Apache Doris 构建高可靠、强一致的实时数据平台
大数据·数据库·数据分析
夜瞬1 天前
Git工作流程与常用指令——从本地开发到远程协作
大数据·git·elasticsearch
曾阿伦1 天前
Spark flatMapToPair算子卡顿优化
大数据·分布式·spark
阿里云大数据AI技术1 天前
阿里云 EMR Serverless Spark 发布 Agent Skill:让自然语言驱动 Spark 任务与资源管理
spark·agent
不一样的故事1261 天前
SVN 权限已赋予但客户端看不到服务端文件
大数据·网络·安全
甘露寺1 天前
【LangGraph 2026 核心原理解析】大模型 Tool Calling 机制与使用最佳实践全解
大数据·人工智能·python