到这里,三种开放表格式的内核都拆过了:第 8–11 章 的 Iceberg 快照树与提交协议、第 12 章 的 Delta 事务日志、第 13 章 的 Hudi timeline 与索引。这一章不再讲单个格式怎么工作,而是回答两个工程问题:
- 它们到底差在哪? 把元数据模型、行级更新、并发、引擎生态四个维度并排,每个维度标清对比口径。
- 能不能不二选一? 讲 UniForm 和 XTable 两条互通路线,以及一棵选型决策树。
底线先摆明:不做「谁更好」的排名。三家是同一道题(对象存储上的 ACID 表)的不同解法,差异来自不同的设计出发点,选型取决于你的写入模式、引擎栈和更新频率,而不是某个通用分数。
版本锚定:Iceberg 表规范 V2 主线(V3 特性标注)、Delta Lake 3.x、Apache Hudi 1.x;互通方案以 Delta UniForm 官方文档与 Apache XTable(Incubating)官方文档为准。下面凡涉及具体行为均标注来源,不写未经核对的「谁比谁快多少」。
一、对照前先对齐口径
跨格式对比最容易翻车的地方,是拿不同口径的东西硬比。先把几个反复出现的概念对齐,后面的表格才有意义。
- 「行级更新」比的是机制,不是「能不能」。三家都能 update/delete,区别在于落地方式(重写文件 vs 标记删除 vs base+log)和由此带来的读写放大。
- 「并发」比的是冲突检测的粒度和原子提交的来源,不是「支不支持并发」。三家都支持并发写,区别在于检测逻辑和对外部协调(catalog/锁)的依赖。
- 「引擎生态」比的是「哪些引擎能读、能写、能写到什么程度」,且随版本快速变化。这里只给结构性判断(设计偏向),不给「某引擎某版本支持某特性」的快照式清单——那种清单几个月就过时,要查时以各引擎当前 connector 文档为准。
- 版本边界要写清。Iceberg V2 和 V3、Delta 开不开 deletion vector、Hudi CoW 还是 MoR,会显著改变结论。脱离版本谈「Iceberg 怎样、Hudi 怎样」往往是错的。
记住这四条,下面四维对照表里的每个格子才是可核对的,而不是营销话术。
二、四维对照
2.1 元数据模型
口径:「表当前由哪些文件组成」这个事实存在哪、怎么更新、怎么加速读取。
| Iceberg | Delta | Hudi | |
|---|---|---|---|
| 真相载体 | 不可变 snapshot 树:metadata.json →
manifest list → manifest → data file |
有序事务日志 _delta_log:JSON commit +
Parquet checkpoint |
timeline(.hoodie/):instant 三态 + file
group/file slice |
| 一次提交 | 写新 manifest + 新 metadata.json,catalog
swap 指针 |
追加一个版本号递增的 JSON commit(put-if-absent) | 在 timeline 写一个 instant(commit/deltacommit),状态置 COMPLETED |
| 加速全量读 | manifest list 直接列出快照的 manifest | checkpoint 压平日志 + _last_checkpoint
指针 |
metadata 表 files 分区 + LSM timeline
history |
| 文件级裁剪 | manifest 里 lower/upper bound、null count | add.stats(min/max/null,默认前 32
列) |
metadata 表 column_stats 分区 |
| 分区 | 隐藏分区(partition spec,查询不写分区谓词也能裁,第 9 章) | Hive 目录式分区(分区列可见) | Hive 目录式分区 + file group |
三者的共性抽象是同一个(第 7 章):不可变数据文件 + 可变元数据指针 + 不 LIST 目录的 planning。差异在元数据怎么组织:Iceberg 是树、Delta 是日志、Hudi 是 timeline+file group。Iceberg 的隐藏分区是它独有的一招,省去「分区列写错就不裁剪」的坑。
2.2 行级更新
口径:update/delete/upsert 的落地机制与读写放大。
| Iceberg | Delta | Hudi | |
|---|---|---|---|
| copy-on-write | 支持(重写文件) | 支持(默认,重写文件) | CoW 表类型(重写 base 文件) |
| merge-on-read | V2 position/equality delete;V3 deletion vector(第 10 章) | deletion vector(特性 deletionVectors,第 12 章
第七节) |
MoR 表类型:base + log file + compaction |
| 主键 upsert | 无内建主键索引,靠 MERGE / equality delete | 无内建主键索引,靠 MERGE | record key + 多种索引(bloom/simple/RLI/bucket),upsert 是一等公民 |
| 写放大(删少量行) | CoW 高;MoR 低 | CoW 高;DV 低 | CoW O(file_groups_written);MoR
O(records_changed) |
| 读放大 | MoR 需合并 delete | DV 需读时过滤 | MoR O(records_changed);read-optimized 为
0 |
结论性判断:行级更新这件事,三家都从「重写整文件」走向了「标记/增量」(Iceberg DV、Delta DV、Hudi MoR log),收敛方向一致。真正的分野在 upsert:Hudi 把主键索引做进了表格式本身,对「按主键高频更新」结构性领先;Iceberg/Delta 做 upsert 要靠 MERGE,缺少内建的「key→file」索引,大表随机更新代价更高(第 13 章 第六节)。
2.3 并发控制
口径:冲突检测粒度 + 原子提交来源 + 对外部协调的依赖。
| Iceberg | Delta | Hudi | |
|---|---|---|---|
| 模型 | 乐观并发(OCC),快照隔离/串行化 | 乐观并发(OCC),Serializable/WriteSerializable |
OCC(写者间)+ 表服务非阻塞;1.x 有 NBCC |
| 原子提交来源 | catalog 对元数据指针 CAS(DB 锁 / REST 后端 / 条件写,第 11 章) | put-if-absent 抢版本号文件(HDFS rename / S3 条件写 / coordinator) | 时间线 instant 原子落盘 + 外部锁提供者(ZK/HMS/DynamoDB) |
| 冲突检测 | 比对快照间的文件级变更 | 重放并比对并发 commit 的 action | 比对重叠 file group |
| 后台服务并发 | compaction/expire 作为新快照提交 | OPTIMIZE 作为新 commit | compaction/clustering/cleaning 基于 instant 计划异步执行,尽量不阻塞写 |
三家都是 OCC,都把「原子性」外包给某种存储/catalog 原语。差异:Iceberg 把原子点收敛到 catalog(所以 第 15 章 catalog 之争对 Iceberg 尤其重要);Delta 把它放在日志文件的 put-if-absent;Hudi 因为要让后台表服务和写入并发,在 timeline 三态上做了更多文章(NBCC)。
2.4 引擎生态
口径:设计偏向决定的引擎中立性(不给版本快照清单)。
| Iceberg | Delta | Hudi | |
|---|---|---|---|
| 设计偏向 | 引擎中立,规范 + REST Catalog 规范公开 | 起于 Spark/Databricks,协议公开,delta-kernel 推动可移植 | 自带写入栈(Hudi Streamer),偏流式 upsert |
| 读支持广度 | 最广(Trino/Spark/Flink/DuckDB/CH 等) | 较广,UniForm 进一步补互通 | 读支持增长中,写仍偏自有栈 |
| 写支持 | 多引擎可写 | 多引擎可写(kernel) | 写最佳路径是 Hudi 自有 writer(Spark/Flink) |
| 内建入湖工具 | 无(依赖外部框架) | 无(依赖外部框架) | Hudi Streamer(Kafka/DFS/JDBC,continuous 模式) |
结构性判断:Iceberg 在「引擎中立性」上设计最彻底,2024–2026 事实上的收敛点;Delta 通过 delta-kernel 和 UniForm 在补中立性;Hudi 的写最佳体验在自有栈,读侧靠 metadata 表能力增长。这不是优劣,是定位:Iceberg 赌「多引擎共享一张表」,Hudi 赌「把入湖 upsert 一条龙做厚」。
flowchart TB
PROB["对象存储上的 ACID 表"] --> ICE["Iceberg: 快照树 + 引擎中立"]
PROB --> DEL["Delta: 事务日志 + Spark 生态/kernel"]
PROB --> HUD["Hudi: timeline+索引 + upsert/流式"]
ICE -. 互通 .- UNI["UniForm / XTable"]
DEL -. 互通 .- UNI
HUD -. 互通 .- UNI
三、互通路线一:Delta UniForm
「选型」之外还有第三条路:让一份数据同时被多种格式的客户端读。因为三家底层都是 Parquet 数据文件 + 一层元数据,理论上可以「数据只存一份,元数据生成多套」。UniForm 走的就是这条路。
3.1 UniForm 是什么
Delta Universal Format(UniForm)让 Delta 表能被 Iceberg 和 Hudi 客户端读取(官方文档 Universal Format (UniForm))。它利用「Delta/Iceberg/Hudi 都由 Parquet 数据文件 + 元数据层组成」这一事实:写仍然是写 Delta,UniForm 在 Delta 提交后异步生成对应的 Iceberg/Hudi 元数据,于是 Iceberg/Hudi 客户端能把这张 Delta 表当成自己格式的表来读。数据文件只有一份。
3.2 怎么开
启用 Iceberg / Hudi 互通的表属性(文档原文):
-- Iceberg
CREATE TABLE T(c1 INT) USING DELTA TBLPROPERTIES(
'delta.enableIcebergCompatV2' = 'true',
'delta.universalFormat.enabledFormats' = 'iceberg');
-- Hudi(预览)
ALTER TABLE T SET TBLPROPERTIES ('delta.universalFormat.enabledFormats' = 'hudi');
-- 两者都开
ALTER TABLE T SET TBLPROPERTIES(
'delta.enableIcebergCompatV2' = 'true',
'delta.universalFormat.enabledFormats' = 'iceberg,hudi');要求(文档 Requirements):
- UniForm Iceberg:表必须开 column
mapping(呼应 第 12 章
第十节——Iceberg 靠 field
ID);
minReaderVersion>=2、minWriterVersion>=7;写入用 Delta 3.1+;用 Hive Metastore 作 catalog。 - UniForm Hudi(预览):写入用 Delta 3.2+。
- 已有表可用
ALTER TABLE升级,或REORG TABLE ... APPLY (UPGRADE UNIFORM(...))重写数据文件升级。
3.3 元数据何时生成、怎么追踪
- 异步:Delta 写事务完成后,用同一计算资源异步生成 Iceberg/Hudi 元数据(文档 When does UniForm generate metadata?)。因此 Delta 写开销「可忽略」,但 Iceberg/Hudi 视图有延迟。
- 会合并提交:Iceberg/Hudi 写延迟可能远高于 Delta,频繁提交的 Delta 表可能把多次 Delta commit 打包成一次 Iceberg/Hudi commit。
- 单进程串行:每种格式同一集群同时只有一个元数据生成进程在跑,避免级联延迟。
- 状态追踪:UniForm 在生成的 Iceberg/Hudi
元数据里写
converted_delta_version和converted_delta_timestamp,告诉你「Iceberg 视图对应到 Delta 的哪一版」。
Iceberg 元数据 JSON 路径形如
<table-path>/metadata/v<version>-uuid.metadata.json,BigQuery
等用 metadata JSON 路径注册外部 Iceberg
表的客户端可以直接指。
3.4 限制
文档明确列出(Limitations),这些是工程上最容易踩的:
- 开了 deletion vector 的 Delta 表不能用
UniForm——DV 是 Delta 特有的 MoR
表示,转不过去。要用 UniForm 就得放弃 DV(或先
REORG ... PURGE物化)。 - Iceberg/Hudi 客户端只能读,不能写 UniForm 表。写必须回到 Delta。
- 不支持
VOID类型;Change Data Feed、Delta Sharing这些 Delta 特性在 Iceberg 侧不可用。 - Iceberg/Hudi 视图有转换延迟,不是强一致实时。
flowchart LR
W[Delta writer] -->|写| DLOG[_delta_log]
DLOG -->|提交后异步| GEN[UniForm 生成元数据]
GEN --> IMETA["metadata/v..-uuid.metadata.json (Iceberg)"]
GEN --> HMETA["Hudi 元数据"]
IR[Iceberg reader] -->|只读| IMETA
HR[Hudi reader] -->|只读| HMETA
DATA[(同一份 Parquet)] --- DLOG
DATA --- IMETA
DATA --- HMETA
一句话定位 UniForm:「写 Delta,让别人用 Iceberg/Hudi 读」,单向、异步、有限制,但数据不复制。
四、互通路线二:Apache XTable
UniForm 是「Delta 主动生成别的格式」,绑定在 Delta 写入栈上。Apache XTable(Incubating) 走另一条路:做一个独立的元数据转换工具,在任意两种格式之间转换元数据,不绑定某个写入引擎。
4.1 XTable 是什么
XTable 是一个独立工具(官方文档 Creating your first interoperable table):给定一张某格式的源表,它读源表的元数据,生成目标格式的元数据文件,指向同一批底层数据文件,从而让源表能被目标格式的引擎查询——不复制、不移动数据,并尽量保留 commit 历史以支持时间旅行。
和 UniForm 的关键区别:
- UniForm:Delta → (Iceberg/Hudi),由 Delta writer 在写时异步触发,绑定 Delta。
- XTable:任意 → 任意(Hudi/Delta/Iceberg 互转),由独立的 bundled jar 事后运行,不绑定写入栈。
4.2 怎么用
XTable 跑一次 sync(文档示例)。先写一张源表(这里是 Hudi):
hudi_options = {
'hoodie.table.name': "people",
'hoodie.datasource.write.partitionpath.field': 'city',
'hoodie.datasource.write.hive_style_partitioning': 'true'}
df.write.format("hudi").options(**hudi_options).save("file:///tmp/hudi-dataset/people")写一个转换配置 my_config.yaml:
sourceFormat: HUDI
targetFormats:
- DELTA
- ICEBERG
datasets:
-
tableBasePath: file:///tmp/hudi-dataset/people
tableName: people
partitionSpec: city:VALUE运行 bundled jar:
java -jar xtable-utilities/target/xtable-utilities_2.12-0.2.0-SNAPSHOT-bundled.jar \
--datasetConfig my_config.yaml跑完,源表目录里会多出 Delta、Iceberg 的元数据(schema、commit 历史、分区、列统计),三种格式的引擎都能查同一份数据。配合 catalog 注册(文档 Registering your interoperable tables across multiple catalogs)可让目标表在多个 catalog 里可见。
4.3 边界
- XTable 是周期性运行的转换,不是实时;目标格式的视图新鲜度取决于你多久跑一次 sync。
- 转换的是元数据,数据文件必须是目标格式也能读的(都基于 Parquet 时最顺);某些源特有特性(如 Hudi 的复杂 MoR 状态、Delta DV)能否完整转换有限制,要查当前版本。
- 它是 Apache Incubating 项目,API/坐标仍在演进(上面的 jar 版本号会变)。
flowchart LR
SRC["源表 (Hudi/Delta/Iceberg)"] --> X["XTable sync (独立 jar)"]
X --> T1[Delta 元数据]
X --> T2[Iceberg 元数据]
X --> T3[Hudi 元数据]
T1 & T2 & T3 --- D[(同一份 Parquet 数据)]
五、UniForm vs XTable 怎么选
两条互通路线不是替代关系,定位不同:
| UniForm | XTable | |
|---|---|---|
| 触发方 | Delta writer 写时异步 | 独立工具事后运行 |
| 源格式 | 只能 Delta | Hudi/Delta/Iceberg 任意 |
| 目标格式 | Iceberg、Hudi(读) | Delta/Iceberg/Hudi 任意 |
| 绑定 | 绑 Delta 写入栈 | 不绑写入引擎 |
| 新鲜度 | 异步、可能合并提交 | 取决于 sync 频率 |
| 典型场景 | 主用 Delta,想让 Iceberg/Hudi 引擎也能读 | 已有某格式表,想低成本暴露成另一格式 |
共同的硬约束要记牢:互通的是「读」——别指望让两个不同格式的 writer 同时写同一份数据,那会打架。互通方案解决的是「写一份、多格式读」,不是「多格式写」。还有一条:高级特性(DV、复杂 MoR、CDF 等)常常转不过去,用互通往往意味着退回到「特性的最大公约数」。
六、选型决策树
不做排名,给一棵按「写入模式 → 引擎栈 → 更新频率」展开的决策树。每个分支给倾向,不给绝对答案——真要定还得按 第 20 章 的口径做 POC。
START
├─ 主负载是「高频按主键 upsert / CDC 入湖」吗?
│ ├─ 是 → 倾向 Hudi(record index + MoR + Hudi Streamer,第 13 章)
│ │ └─ 但下游引擎不支持 Hudi 写/读?→ 看是否用 XTable 暴露成 Iceberg
│ └─ 否 → 继续
├─ 引擎栈是否以 Spark/Databricks 为中心?
│ ├─ 是 → 倾向 Delta(生态成熟;需被 Iceberg/Hudi 读则开 UniForm)
│ └─ 否 → 继续
├─ 是否要「多引擎(Trino/Flink/DuckDB/CH…)共享同一张表」、引擎中立优先?
│ ├─ 是 → 倾向 Iceberg(规范最完整、收敛点、隐藏分区,第 8–11 章)
│ └─ 否 → 继续
├─ 更新频率高但不是主键 upsert(偶发删改 + 大量读)?
│ ├─ 是 → Iceberg(V3 DV) 或 Delta(DV) 都行,按引擎栈定
│ └─ 否(基本只追加 + 快照读)→ 三家都可,按团队已有技能/catalog 定
END
把判断拆成几条可执行的提问:
- 写入模式:是「批量灌 + 快照读」,还是「持续 upsert + 增量消费」?后者强烈指向 Hudi;前者三家都行。
- 引擎栈:你的查询/计算引擎是什么?引擎中立优先选 Iceberg;Spark 为中心 Delta 顺手;要 Hudi 的 upsert 又要别的引擎读,考虑 XTable/UniForm 互通。
- 更新频率与方式:随机主键更新(Hudi 强)、偶发删改(DV 类即可)、纯追加(无所谓)。
- catalog 与治理:Iceberg 的 catalog 生态(REST/Polaris/Unity/Nessie)最丰富,这点 第 15 章 单独讲;catalog 选择会反过来约束格式选择。
- lock-in 与退路:选定后想换怎么办?互通方案(UniForm/XTable)能降低 lock-in,但要接受「最大公约数」的特性退化。
flowchart TD
A{高频主键 upsert / CDC?} -->|是| H[Hudi]
A -->|否| B{Spark/Databricks 中心?}
B -->|是| D[Delta]
B -->|否| C{多引擎中立优先?}
C -->|是| I[Iceberg]
C -->|否| E{偶发删改 + 大量读?}
E -->|是| DV[Iceberg DV 或 Delta DV]
E -->|否| ANY[三家皆可, 按技能/catalog]
H -.下游要别的格式.-> X[XTable/UniForm 互通]
D -.要被 Iceberg/Hudi 读.-> U[UniForm]
七、schema 演进与时间旅行对照
四维对照之外,还有两个工程上天天用到、却最容易踩坑的维度:schema 怎么演进、历史怎么回看。
7.1 schema 演进
口径:能否安全地增/删/改名/重排/拓宽列而不重写历史数据。
| Iceberg | Delta | Hudi | |
|---|---|---|---|
| 演进依据 | field ID(不靠位置,第 16 章) | schema 字符串;安全改名/删列需开 column mapping(第 12 章 第十节) | Avro schema 演进 |
| 加列 | 安全,老文件读补 null | 安全 | 安全 |
| 删列/改名 | 安全(按 ID) | 需 column mapping(name/id
模式) |
支持完整 schema 演进 |
| 类型拓宽 | 规范允许的拓宽(如 int→long) | 支持部分 | 支持部分 |
| 重写数据 | 不需要 | 不需要(开 column mapping) | 不需要 |
共性:三家都做到「演进不重写历史」,这正是相对 Hive 表的核心进步。差异在机制:Iceberg 从第一天就用 field ID,删列改名最干净;Delta 默认按物理列名,要安全改名/删列必须开 column mapping(这也是 UniForm Iceberg 要求开 column mapping 的根因,第三节);Hudi 走 Avro schema 演进。
7.2 时间旅行
口径:怎么定位历史版本、能回溯多远、受什么约束。
| Iceberg | Delta | Hudi | |
|---|---|---|---|
| 定位方式 | snapshot id 或时间戳 | 版本号或时间戳 | as.of.instant(时间点) |
| 历史保留 | snapshot 保留 + expire | 日志保留 + VACUUM(第 12 章
第十一节) |
cleaner 策略(第 13 章 第五节) |
| 回溯下限 | expire snapshots 后不可读 | VACUUM 删文件 / 日志过期后不可读 | cleaner 清理后不可读 |
| 增量读 | incremental scan(快照差) | Change Data Feed | incremental query(一等公民,含 CDC 格式) |
共性:三家都支持「按版本/时间读历史」,且都受「保留策略 vs 存储成本」的同一约束——保留越久越能回溯,但过期文件越多。差异在增量读:Hudi 的 incremental query 是设计核心,Delta 用 CDF、Iceberg 用快照差实现类似能力,但 Hudi 在「自某 instant 以来变了哪些记录」这件事上做得最顺(第 13 章第七节)。运维上三个清理操作(expire / VACUUM / clean)是同一类,调不好都会「时间旅行回不去」或「孤儿文件堆积」(第 20 章)。
八、统计信息与文件裁剪对照
「不读数据就裁文件」是三家共同的性能基石,但统计信息存在哪、有多丰富不同。
| Iceberg | Delta | Hudi | |
|---|---|---|---|
| 基础列统计 | manifest 里 lower/upper bound、null/value count | add.stats(min/max/null,默认前 32
列) |
metadata 表 column_stats 分区 |
| 进阶统计 | Puffin 文件里的 NDV sketch(如 Theta)等(第 17 章) | 文件级 stats 为主 | metadata 表多模索引(bloom/record/expr/secondary,第 13 章第八节) |
| 裁剪层级 | 分区裁剪 → manifest 裁剪 → 文件裁剪 → Parquet page 裁剪 | 日志 stats 文件裁剪 → Parquet page 裁剪 | metadata 裁剪 + 索引定位 → Parquet page 裁剪 |
| 统计何时算 | 写入时写进 manifest | 写入时写进 add |
写入时维护进 metadata 表 |
三家都把「文件级裁剪」做进了元数据,最后都落到 Parquet 内部的 row group/page 裁剪(第 2 章),这条两级裁剪链路是 第 18 章 评估引擎读湖能力的核心。差异:Iceberg 有 Puffin 承载 NDV 这类高级统计,对优化器估基数有用;Hudi 的 metadata 多模索引把统计和索引统一在一张内部表里,还能加二级索引/表达式索引,把「裁剪」扩展到非主键列。注意所有格式都受「stats 只覆盖部分列」的现实约束(Delta 默认前 32 列),高选择性过滤列要排前面。
九、迁移:格式之间怎么搬
选型不是一锤子买卖,要考虑「搬进来」和「搬出去」两个方向。
9.1 从 Hive 表迁入
三家都提供「就地迁移」(in-place,不重写数据,只生成元数据指向已有 Parquet),代价低但要求数据文件本就兼容:
| 目标 | 典型路径 |
|---|---|
| → Iceberg | add_files / migrate
过程,把已有 Parquet 纳入新表元数据(第
20 章) |
| → Delta | CONVERT TO DELTA,对已有 Parquet 目录生成
_delta_log |
| → Hudi | bootstrap(可只生成元数据指向老文件,或全量重写) |
就地迁移的风险:老文件的 schema/分区布局必须和新表一致;迁移后老的写入路径(直接往目录扔文件)必须停掉,否则绕过元数据写入会让表「分裂」。
9.2 格式之间互迁
| 方式 | 重写数据? | 单/双向 | 说明 |
|---|---|---|---|
| UniForm | 否 | Delta→(Iceberg/Hudi),只读 | 第三节;不能与 DV 共存 |
| XTable | 否 | 任意互转 | 第四节;周期性 sync |
| 全量重写(CTAS) | 是 | 任意 | 读源表写新格式表,代价最高但最干净,特性不受互通限制 |
判断原则:只要「读侧多格式」就够,优先 UniForm/XTable(不重写数据);要彻底换格式、且要用目标格式的全部特性,才做全量重写。互通方案省钱省时,但前面说过——会退化到特性的最大公约数。
flowchart LR
HIVE[Hive 目录表] -->|add_files/CONVERT/bootstrap| LF[湖表格式]
LF -->|UniForm/XTable, 不重写| OTHER[另一格式只读视图]
LF -->|CTAS 全量重写| NEW[另一格式完整表]
十、互通与选型的常见误区
把工程上反复出现的误区集中澄清,每条都对应前面的证据:
| 误区 | 澄清 |
|---|---|
| 「选了某格式就被锁死」 | 互通(UniForm/XTable)能让数据被多格式读,lock-in 比想象的低;但要接受特性最大公约数 |
| 「Hudi 总是更快」 | 只在「高频主键 upsert / 增量消费」上结构性领先;纯追加 + 快照读,三家差异不大,Iceberg/Delta 更简单(第二节) |
| 「UniForm 是双向互通」 | 错。Iceberg/Hudi 客户端只能读 UniForm 表,写必须回 Delta(第三节限制) |
| 「开了 DV 还能用 UniForm」 | 不行。UniForm 与 deletion vector 互斥,要互通就放弃 DV
或先 REORG PURGE(第三节) |
| 「互通=实时一致」 | UniForm 异步、可能合并提交;XTable 周期 sync。都有延迟(第三、四节) |
| 「多个格式可以同时写一份数据」 | 不要。互通解决「写一份多格式读」,多 writer 跨格式写同一数据会冲突(第五节) |
| 「列统计自动覆盖所有列」 | Delta 默认前 32 列;高选择性过滤列要排前面,否则裁剪退化(第八节) |
| 「换格式必须重写数据」 | 不一定。读侧多格式用 UniForm/XTable 不重写;只有要全部特性才全量重写(第九节) |
这些误区的共同根源,是把「互通」当成「免费的银弹」。互通是有边界的工程手段:它降低 lock-in、支持多格式读,但不消除格式差异,也不让你同时享受所有格式的高级特性。
十一、把 08–13 串起来
这一章是第三、四部分的收口,回看前面每一章在「对照」里的位置:
- 第 7 章 给了共性抽象:不可变数据 + 可变元数据指针 + 原子提交。三家都没跳出这个框,差异都是这个框内的工程选择。
- 第 8 章 的 Iceberg 元数据树 = 本章 2.1 的「snapshot 树」列;第 9 章 的隐藏分区 = Iceberg 在分区维度的独有优势。
- 第 10 章 的 position/equality delete 与 V3 DV = 本章 2.2 的 Iceberg 行级更新机制。
- 第 11 章 的提交协议 = 本章 2.3 的「原子提交来源」,也是为什么 catalog(第 15 章)对 Iceberg 这么关键。
- 第 12 章 的事务日志、DV、liquid clustering、table features = 本章 Delta 那一列,以及 UniForm 需要 column mapping 的原因。
- 第 13 章 的 timeline、file group、索引体系 = 本章「Hudi upsert 结构性领先」的依据。
往后:catalog(第 15 章)决定这些格式怎么被统一管理和并发提交;查询引擎(第 18 章)决定本章「引擎生态」一列的实际落地;选型迁移运维(第 20 章)把本章决策树落到 POC 与迁移路径上。
十二、小结
- 三家是同一道题的不同解法,对照要对齐口径:元数据模型(树/日志/timeline)、行级更新(CoW/DV/MoR)、并发(都是 OCC,原子点来源不同)、引擎生态(中立性偏好不同)。脱离版本和口径的「谁更好」没有意义。
- 行级更新在收敛:三家都从「重写整文件」走向「标记/增量」;真正的分野是 upsert——Hudi 把主键索引做进了表格式,对高频主键更新结构性领先。
- 引擎中立性:Iceberg 设计最彻底(事实收敛点),Delta 用 kernel/UniForm 补,Hudi 强在自有入湖栈。
- 互通有两条路:UniForm(写 Delta、异步生成 Iceberg/Hudi 元数据、只读、不能与 DV 共存)、XTable(独立工具、任意互转、周期性、不绑写入栈)。互通解决「写一份多格式读」,不解决「多格式写」,且常退化到特性最大公约数。
- 选型按写入模式 → 引擎栈 → 更新频率展开:高频 upsert/CDC 倾向 Hudi,Spark 中心倾向 Delta,多引擎中立倾向 Iceberg,纯追加三家皆可;catalog 选择和 lock-in 退路也要一并考虑。不排名,按场景定,最终用 POC 验证。
返回 系列目录 | 上一篇:Apache Hudi | 下一篇:Catalog 之争
参考资料
- Delta Lake 文档,Universal Format (UniForm)(docs.delta.io,latest)— 启用方式、要求、异步生成、限制(含 DV 不兼容、只读)。
- Apache XTable(Incubating)文档,Creating your first interoperable table(xtable.apache.org)— sync 配置、源/目标格式、bundled jar 用法。
- Apache Hudi 文档,Table & Query Types / Indexes(hudi.apache.org,1.x)— CoW/MoR、索引体系(对照 2.2/2.4 口径)。
- Apache Iceberg 表规范 V2/V3 与
apache/iceberg(1.x);Delta 协议PROTOCOL.md(master)。 - 本系列 第 8–11 章 Iceberg 内核、第 12 章 Delta 事务日志、第 13 章 Apache Hudi、第 15 章 Catalog 之争。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【数据湖与开放表格式】表格式为什么存在
目录式分区表(Hive 表)在对象存储上有三处硬伤:并发写部分提交、list planning 太贵、缺快照隔离与原子提交。本文拆开放表格式补上的四件事——原子提交、快照隔离、文件级统计裁剪、schema 与分区演进,并抽象出三家共有的『元数据指针 + 不可变数据文件』骨架。
【数据湖与开放表格式】Parquet · Iceberg · Delta · Hudi 内核拆解
拆解 lakehouse 的两层基础:列式文件格式(Parquet/ORC/Arrow)与开放表格式(Iceberg/Delta/Hudi)。讲清没有数据库进程时,如何在对象存储上做 ACID、行级更新、快照与并发,以及 catalog、查询引擎、流式入湖如何拼成可运维的湖仓。面向数据平台工程师与从 OLAP/数仓转型的开发者。
【数据湖与开放表格式】Lakehouse 全景:从 Hive 表到开放表格式
Hive 目录式分区表把『表』等同于『一组目录加 metastore 里的分区行』,于是没有原子提交、planning 要 LIST 目录、schema 与分区演进常要重写。本文用这三个硬伤切入,讲清 lakehouse 把表拆成『不可变数据文件 + 可变元数据指针 + catalog』三层后各自解决了什么,并给出全系列的分层地图。
【数据湖与开放表格式】Iceberg 元数据树
拆解 Iceberg 的四层元数据:catalog 指针 → metadata.json → manifest list(snapshot)→ manifest file → data file。讲清 snapshot 与 manifest 里的分区数据和列级 stats(lower/upper bound、null/value count)如何让一次查询不 list 目录就收敛到文件集合,并给出表规范 V1/V2/V3 的版本边界。基于 pyiceberg 0.11.1 真实建表逐层 dump。