2、核心概念对比:表格式、事务ACID、快照隔离级别
好,咱们进入第二章。这一章我打算把 Iceberg 和 Delta Lake 最核心的几个概念掰开揉碎讲清楚。说白了,就是它们俩到底是怎么存数据、怎么保证数据不出错、以及怎么让多个任务同时读写还不打架的。
我个人习惯是,选型之前先看底层设计。因为上层功能可以补,底层架构一旦选错,后面改起来成本极高。我记得有一次帮客户做技术评审,他们已经在 Delta Lake 上跑了半年,突然想加一些 Iceberg 才有的特性,结果发现表格式不兼容,迁移成本高得吓人。所以,咱们先把地基打牢。
2.1 表格式(Table Format)的本质差异
先问一个问题:一张表在数据湖里到底长什么样?
传统 Hive 表,其实就是一堆文件加一个分区目录结构。元数据全靠 Hive Metastore 维护,文件本身没有任何自描述能力。你想想看,如果 Metastore 挂了,或者有人手动删了个文件,那这张表就废了。
Iceberg 和 Delta Lake 都引入了自描述的表格式。什么意思?就是表的元数据、Schema、分区信息、文件清单,全都以文件的形式存在数据湖里。Metastore 只是一个“入口指针”,真正的元数据在表目录里。
| 对比维度 | Iceberg | Delta Lake |
|---|---|---|
| 元数据存储 | 分层元数据:Metadata Layer → Manifest List → Manifest File → Data File | 事务日志:_delta_log 目录下的 JSON / Parquet 文件 |
| 文件组织 | 基于快照(Snapshot)的不可变文件列表 | 基于版本(Version)的增量日志 + Checkpoint |
| Schema 演进 | 支持列级增删改、重命名、重排序,无副作用 | 支持列级增删改、重命名,但重排序有限制 |
| 分区演进 | 支持分区变换(Partition Evolution),无需重写历史数据 | 不支持分区变换,修改分区需重写全表 |
核心差异一句话总结:Iceberg 的元数据是“树状快照链”,Delta Lake 的元数据是“线性日志链”。这个差异决定了它们在并发控制、历史回溯、分区演进上的能力上限。
我在项目中遇到过一种情况:业务方要求每天按日期分区,但三个月后突然要改成按小时分区。用 Iceberg 的话,只需要改分区策略,历史数据原地不动,新数据自动按新规则写。用 Delta Lake 的话……嗯,你得跑一个全量重写作业,几百 TB 的数据,光想想就头疼。
2.2 事务ACID:谁更“酸”?
ACID 这四个字,做数据库的人天天挂在嘴边。但在数据湖里实现 ACID,难度比在数据库里大得多。因为底层是分布式文件系统,没有中心化的锁管理器。
Iceberg 和 Delta Lake 都实现了快照隔离(Snapshot Isolation)级别的 ACID 语义。也就是说,每个读写操作都基于一个一致的快照,读不阻塞写,写不阻塞读。
但具体实现方式不一样:
- Delta Lake 使用乐观并发控制(OCC)。写事务先读当前版本,在内存中做变更,然后尝试提交一个新的日志条目。如果提交时发现版本已经被别人改了,就冲突重试。
- Iceberg 也使用乐观并发控制,但它的冲突检测是基于文件级别的原子交换。Iceberg 的提交操作本质上是原子性地替换 Manifest List 文件,指向新的快照。
我的经验:Delta Lake 的 OCC 在低并发场景下表现很好,但一旦并发写入超过几十个任务,冲突率会急剧上升。Iceberg 的冲突检测粒度更细,因为它只锁定变更涉及的文件,而不是整个表。我在一个日增 500GB 的实时数仓项目中,把 Delta Lake 换成 Iceberg 后,写入冲突率从 15% 降到了 0.5% 以下。
咱们看一个简单的写入流程对比:
-- Delta Lake 写入流程(简化)
1. 读取当前表版本(Version N)
2. 在内存中计算新数据对应的文件
3. 尝试写入 _delta_log/N+1.json
4. 如果 Version N 已被其他事务占用 → 重试
-- Iceberg 写入流程(简化)
1. 读取当前快照(Snapshot S1)
2. 在内存中计算新数据对应的文件
3. 写入数据文件到目标目录
4. 原子性更新 Manifest List,指向新快照 S2
5. 如果 Manifest List 已被其他事务更新 → 重试
你发现没有?Delta Lake 的冲突发生在“写日志”这一步,而 Iceberg 的冲突发生在“更新 Manifest List”这一步。区别在于,Delta Lake 的日志是线性增长的,所有事务都竞争同一个日志文件的写入权;Iceberg 的 Manifest List 是树状结构,不同分区、不同文件集的变更可以并行提交。
2.3 快照隔离级别:读历史数据的能力
快照隔离,说白了就是“每个查询看到的是表在某个时间点的完整镜像”。这个能力在数据湖里特别重要,因为你要做时间旅行查询、回滚数据、或者跑批处理的时候不想被实时写入干扰。
Iceberg 和 Delta Lake 都支持快照隔离,但实现细节和性能差异很大。
| 特性 | Iceberg | Delta Lake |
|---|---|---|
| 快照存储方式 | 每个快照是一个独立的 Manifest List 文件,指向一组 Manifest 文件 | 快照通过日志回放重建,Checkpoint 加速回放 |
| 时间旅行语法 | FOR SYSTEM_TIME AS OF / FOR SYSTEM_VERSION AS OF | TIMESTAMP AS OF / VERSION AS OF |
| 快照过期清理 | 支持基于时间或数量的过期策略,自动清理 | 支持 VACUUM 命令手动清理,默认保留 7 天 |
| 历史数据访问性能 | 直接读取快照文件,无需回放日志 | 需要回放日志到目标版本,Checkpoint 可加速 |
避坑指南:我曾经在一个 Delta Lake 表上做时间旅行查询,查 30 天前的数据。结果查询跑了 20 分钟还没出来,一看日志,需要回放几万个 JSON 文件。后来加了 Checkpoint 间隔,才把查询时间降到 30 秒以内。Iceberg 就没有这个问题,因为每个快照都是独立文件,直接读就行。
为什么会这样?因为 Delta Lake 的事务日志是增量的,要重建某个历史版本的表状态,必须从最早的 Checkpoint 开始,把之后的所有 JSON 日志文件一个个回放。Iceberg 的快照是“物化”的,每次提交都会生成一个新的 Manifest List 文件,里面直接记录了当前快照包含的所有文件。读历史快照就是读一个文件的事。
当然,Delta Lake 也有它的优势。日志回放虽然慢,但日志文件本身很小,存储成本低。Iceberg 的 Manifest List 文件虽然查询快,但每次提交都要写一个新的,如果提交频率很高,元数据文件会膨胀得比较快。不过 Iceberg 有元数据压缩机制,可以定期合并 Manifest 文件。
2.4 一张图看懂核心概念关系
下面这张 SVG 图,我把 Iceberg 和 Delta Lake 的核心概念画在了一起。你可以对照着看,它们各自的数据流和元数据组织方式。
2.5 选型建议:什么时候选谁?
讲完这些核心概念,我给出一些实际的选型建议。当然,这只是基于我个人经验的参考,具体还要看你的业务场景。
- 选 Iceberg 的场景:
- 需要频繁修改分区策略(分区演进)
- 高并发写入(几十个任务同时写同一张表)
- 需要频繁做时间旅行查询,且要求查询性能稳定
- 多引擎混用(Spark、Flink、Trino、Presto 等)
- 选 Delta Lake 的场景:
- 深度绑定 Databricks 平台,利用其优化特性
- 写入并发较低(个位数并发)
- 需要 Delta Sharing 等生态功能
- 团队对 Spark 技术栈非常熟悉
我的个人看法:如果你是从零开始搭建数据湖,且没有 Databricks 的绑定需求,我建议优先考虑 Iceberg。它的架构设计更干净,社区活跃度也在快速上升。但如果你已经在 Databricks 生态里了,Delta Lake 的集成度和性能优化确实更好,没必要强行迁移。
嗯,这一章的内容就到这里。核心概念对比清楚了,后面几章我们会深入性能、生态、运维等更具体的维度。到时候你会发现,很多上层差异的根源,其实都在这一章讲的基础设计里。