2、Lakehouse核心概念解析:Delta Lake、Apache Iceberg、Apache Hudi三大开放表格式对比

聊到Lakehouse,绕不开的就是那三个“表格式”——Delta Lake、Apache Iceberg、Apache Hudi。很多刚接触的朋友会问:“这不就是个存数据的格式吗?有什么好比的?”

嗯,我刚开始也这么想。直到有一次在项目中,数据写到一半任务挂了,结果表直接锁死,整个管道停了两个小时。那时候我才意识到——表格式选错了,后面全是坑。

说白了,这三兄弟解决的是同一个问题:让数据湖里的文件,拥有数据库表一样的事务、更新、删除能力。但它们的实现思路、生态成熟度、适用场景,差别还挺大。

核心观点:表格式是Lakehouse的“操作系统内核”。选型不对,后面所有迁移、治理、查询优化都会受限。我个人习惯在项目启动前,先花一周做表格式的POC验证。

2.1 为什么需要开放表格式?

传统数据湖(比如Hive风格的表)有个硬伤:文件是文件,元数据是元数据,两者之间没有事务保障。你写一半任务挂了,留下的“脏数据”没人管。想更新一行?对不起,你得重写整个分区。

开放表格式就是在文件之上加了一层“事务日志”和“元数据管理”。每次写入、删除、更新,都会先写日志,再改文件。读的时候,引擎通过日志知道“哪些文件是当前有效的”。

这样,数据湖就拥有了ACID事务、时间旅行、Schema演化这些“数据库级”的能力。你想想看,没有这些,谁敢把数据湖当生产环境用?

  • ACID事务:并发写入不冲突,失败自动回滚
  • 时间旅行:可以查任意历史版本的数据
  • Schema演化:加列、删列、改类型,不用重写数据
  • 高效更新/删除:支持Merge、Update、Delete操作

我的经验:有一次客户说“我们不需要时间旅行,太复杂了”。结果上线两周,业务方就问“昨天的数据长什么样?能回滚吗?”——嗯,时间旅行这东西,用一次就回不去了。

2.2 三大表格式的“出身”与定位

这三兄弟的“爹”不一样,设计哲学自然也不同。我列个表,大家一目了然。

特性 Delta Lake Apache Iceberg Apache Hudi
出身 Databricks(2019年开源) Netflix(2018年开源) Uber(2019年开源)
核心定位 Spark生态的“增强版Parquet” “通用表格式”,引擎无关 “近实时写入”,面向流批一体
事务模型 乐观并发控制(OCC) 乐观并发控制(OCC) 基于时间线的MVCC
元数据存储 事务日志(JSON文件) 元数据文件(Manifest列表) 时间线(Timeline)+ 元数据表
主要查询引擎 Spark、Presto(有限) Spark、Flink、Trino、Hive Spark、Flink、Presto

你看,Delta Lake一开始就是为Spark量身定做的。Iceberg则更“博爱”,从设计上就想着“不管什么引擎都能读”。Hudi呢?它最擅长的是“快速写入”,尤其是近实时的流式数据。

2.3 核心能力对比:事务、更新、Schema演化

光看出身不够,咱们得掰开揉碎,看看它们在关键能力上的差异。

2.3.1 ACID事务与并发控制

三者都支持ACID,但实现方式有区别。

  • Delta Lake:用事务日志(_delta_log目录下的JSON文件)记录每次操作。写之前先检查日志,看有没有冲突。有冲突?谁先提交谁赢,后面的重试。我在项目中遇到过并发写入导致“死锁”的情况,后来调整了写入批次大小才解决。
  • Iceberg:通过Manifest文件列表来管理快照。每次提交,生成一个新的Manifest列表,指向当前有效的数据文件。读操作只读快照,写操作互不影响。Iceberg的“快照隔离”做得最干净。
  • Hudi:基于时间线(Timeline)的MVCC。每个操作都有一个时间戳,读的时候只读“已提交”的版本。Hudi的写入模式分“写时复制”(COW)和“读时合并”(MOR),前者写入慢但读得快,后者反过来。

避坑指南:我曾经在Hudi的MOR表上做大批量更新,结果读性能直接崩了。后来才发现,MOR表的“小文件合并”没配好,读的时候要合并太多增量日志。记住:MOR表一定要定期做Compaction。

2.3.2 更新与删除(Merge/Upsert)

这是表格式的“杀手锏”。没有这个,数据湖就只能“只写不删”。

-- Delta Lake的Merge语法(Spark SQL)
MERGE INTO target_table AS t
USING source_table AS s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.value = s.value
WHEN NOT MATCHED THEN INSERT *;

-- Iceberg的Merge语法(Spark SQL)
MERGE INTO target_table AS t
USING source_table AS s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.value = s.value
WHEN NOT MATCHED THEN INSERT *;

-- Hudi的Upsert(通过DataFrame API)
df.write
  .format("hudi")
  .option("hoodie.table.name", "target_table")
  .option("hoodie.datasource.write.operation", "upsert")
  .mode(Append)
  .save("/path/to/table");

语法上,Delta Lake和Iceberg几乎一样(都支持SQL标准的MERGE)。Hudi则更依赖DataFrame API或Flink SQL。从性能上看,Hudi的Upsert在“小批量高频写入”场景下表现最好,Iceberg在“大批量批量写入”时更稳定。

2.3.3 Schema演化

业务变了,表结构也得变。加个列、改个类型,能不能不重写数据?

  • Delta Lake:支持加列、改列名、改类型(向上兼容)。但注意,Delta Lake的Schema演化默认是“严格模式”,加列需要显式声明。
  • Iceberg:支持加列、删列、改列名、改类型(向上兼容)。Iceberg的Schema演化最灵活,甚至支持“列重排序”。
  • Hudi:支持加列、改类型。但删列和改列名支持有限,需要谨慎操作。

我的建议:如果业务Schema变化频繁(比如每天都有新字段),优先考虑Iceberg。我在一个广告业务项目中,Iceberg的Schema演化帮了大忙——业务方每周加3-5个新指标,不用停服务,不用重跑历史数据。

2.4 生态兼容性与查询引擎支持

表格式再好,也得有人用。查询引擎的支持广度,直接决定了你的技术选型。

查询引擎 Delta Lake Apache Iceberg Apache Hudi
Apache Spark 原生支持(最佳) 原生支持 原生支持
Apache Flink 通过Delta Connector 原生支持 原生支持(最佳)
Trino / Presto 通过Delta Connector(有限) 原生支持 通过Hudi Connector
Hive 不支持 支持(通过HiveCatalog) 支持(通过HiveSync)
Snowflake / Redshift 通过Parquet读取 通过Iceberg Catalog 通过Parquet读取

你看,Iceberg在“引擎兼容性”上是最广的。Delta Lake在Spark之外的支持相对弱一些。Hudi在Flink生态里表现最好,毕竟Uber内部就是Flink的重度用户。

2.5 选型建议:什么时候选哪个?

说了这么多,到底怎么选?我根据自己的项目经验,给几个“场景化”的建议。

  • 场景一:你的技术栈以Spark为主,且团队对Databricks平台有依赖。选Delta Lake。它和Spark的集成度最高,性能调优最方便。但要注意,如果未来想引入Trino或Flink,Delta Lake可能会成为瓶颈。
  • 场景二:你的查询引擎多样化,有Spark、Trino、Flink、Hive混用。选Iceberg。它的“引擎无关”设计,让你不用为每个引擎单独适配。我在一个金融客户那里,用Iceberg统一了Spark批处理、Trino即席查询、Flink实时写入,效果很好。
  • 场景三:你的核心场景是“近实时写入”,比如IoT数据、日志流、CDC数据。选Hudi。它的MOR模式和Flink集成,能实现秒级延迟。但要做好Compaction策略,否则读性能会下降。
  • 场景四:你正在做“数据湖迁移”,从Hive/Parquet迁移到Lakehouse。我个人建议优先考虑Iceberg。它的“无锁迁移”能力最强,可以直接把Hive表“挂载”成Iceberg表,不用重写数据。

一句话总结:Delta Lake适合“Spark重度用户”,Iceberg适合“多引擎混合架构”,Hudi适合“近实时写入场景”。没有银弹,只有最适合你业务场景的选择。

2.6 知识体系总览:三大表格式核心逻辑

下面这张图,我把三大表格式的核心逻辑画出来了。你可以看到,它们都围绕“事务日志/时间线/元数据文件”来管理数据文件,但具体实现路径不同。

三大开放表格式核心逻辑对比 Delta Lake 事务日志 (_delta_log) JSON文件记录每次操作 数据文件 (Parquet) 通过日志确定有效版本 乐观并发控制 (OCC) 先检查日志,再提交 最佳引擎:Spark 生态:Databricks Apache Iceberg 元数据层 (Manifest) Manifest列表 + Manifest文件 快照隔离 (Snapshot) 每次提交生成新快照 引擎无关设计 Spark / Flink / Trino / Hive 最佳引擎:多引擎通用 生态:Netflix / 社区 Apache Hudi 时间线 (Timeline) 按时间戳记录所有操作 COW / MOR 模式 写时复制 / 读时合并 近实时写入 秒级延迟,流批一体 最佳引擎:Flink / Spark 生态:Uber / 社区 核心逻辑:事务日志/时间线/元数据 → 管理数据文件 → 提供ACID、时间旅行、Schema演化

从图上你能看到,三者的“骨架”其实很像:都是通过一层元数据来管理底层的Parquet文件。区别在于元数据的组织方式、并发控制策略、以及生态集成的深度。

2.7 小结:选型不是终点,是起点

表格式选型,只是Lakehouse迁移的第一步。选完之后,还有Catalog配置、表优化策略、Compaction调优、查询引擎适配……一堆事情等着你。

但这一步走对了,后面就顺了。我见过太多项目,因为表格式选错,导致迁移到一半又回退,白白浪费几个月时间。

所以,我的建议是:别急着拍板。先拿真实数据、真实查询、真实写入场景,做一轮POC。看看哪个表格式在你的环境里跑得最稳、最快、最省心。

最后说一句:技术选型没有“最好”,只有“最合适”。Delta Lake、Iceberg、Hudi,都是优秀的开源项目。选哪个,取决于你的团队、你的业务、你的未来规划。别被“流行”绑架,也别被“情怀”误导。