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

聊 Lakehouse,绕不开的就是这三大件:Delta Lake、Iceberg、Hudi。很多人刚接触时容易懵——它们到底解决了什么问题?说白了,传统 Hive 表对 ACID 的支持几乎为零,你跑个批处理写到一半,别人读到的可能是脏数据。我最早在 2018 年做实时数仓时就踩过这个坑,那时候还没这些格式,只能靠分区和重跑补救,累得够呛。

这三兄弟的核心目标其实一致:让数据湖上的表具备数据库一样的事务能力。但实现思路各有侧重。下面我带你逐个拆解。

2.1 它们解决了什么共同问题?

先看一张整体对比图,帮你建立全局认知:

三大开放表格式核心能力对比 开放表格式 Delta Lake Apache Iceberg Apache Hudi Delta Lake 特色 • 事务日志 (Transaction Log) • 时间旅行 (Time Travel) • Schema 演进 • 与 Spark 深度集成 Iceberg 特色 • 快照隔离 (Snapshot Isolation) • 隐藏分区 (Hidden Partition) • 开放接口 (REST Catalog) • 多引擎原生支持 Hudi 特色 • 增量查询 (Incremental Query) • 写时复制/读时合并 • 索引机制 (Bloom/Global) • 流式摄取优化

嗯,这张图基本把三者的定位说清楚了。它们都支持 ACID、时间旅行、Schema 演进,但侧重点完全不同。

2.2 Delta Lake:Spark 生态的“亲儿子”

Delta Lake 是 Databricks 开源的,后来捐给了 Linux 基金会。我个人的第一套实时链路就是基于 Delta Lake 搭的,当时看中的就是它跟 Spark 的无缝衔接。

核心机制:事务日志(Transaction Log)

Delta Lake 用 _delta_log 目录下的 JSON 文件记录每次操作。读数据时,引擎会先读日志,再定位到最新版本的文件。这有点像数据库的 WAL(Write-Ahead Log)。

-- 创建 Delta 表
CREATE TABLE events (
  event_id BIGINT,
  event_time TIMESTAMP,
  event_type STRING
) USING DELTA
LOCATION '/data/events';

-- 时间旅行:查询历史版本
SELECT * FROM events VERSION AS OF 5;
-- 或者按时间戳
SELECT * FROM events TIMESTAMP AS OF '2024-01-15 10:00:00';
我的经验:Delta Lake 的 OPTIMIZE 命令一定要定期跑。我在一个日增 500 万条的项目里,没做 compaction 导致小文件超过 10 万个,查询慢到怀疑人生。跑完 OPTIMIZE ZORDER BY (event_time) 后,性能提升了 3 倍。

适用场景:

  • Spark 技术栈为主的公司
  • 需要简单易用的 ACID 能力
  • 批处理 + 流处理混合场景(Structured Streaming 原生支持)

2.3 Apache Iceberg:开放与多引擎的“瑞士军刀”

Iceberg 由 Netflix 和 Apple 贡献,它的设计哲学是“引擎无关”。我后来在需要同时对接 Flink、Trino、Spark 的项目里,果断换成了 Iceberg——因为它对多引擎的支持最成熟。

核心机制:快照隔离(Snapshot Isolation)

Iceberg 的元数据分三层:Catalog → Metadata File → Manifest List → Manifest File → Data File。每次写操作生成一个新快照,读操作只认快照 ID,读写互不阻塞。

-- 创建 Iceberg 表
CREATE TABLE orders (
  order_id BIGINT,
  customer_id INT,
  amount DECIMAL(10,2),
  order_date DATE
) USING ICEBERG
PARTITIONED BY (days(order_date))
LOCATION '/data/orders';

-- 增量读取:只读新增数据
SELECT * FROM orders
  FOR SYSTEM_TIME AS OF '2024-02-01 00:00:00'
  WHERE order_date >= '2024-02-01';
注意:Iceberg 的隐藏分区(Hidden Partition)是个好东西,但别滥用。我曾经在一个表上设了 5 层分区,结果元数据膨胀到几百 MB,查询计划生成都变慢了。建议分区数控制在 3 层以内。

适用场景:

  • 多引擎并存(Spark + Flink + Trino + Presto)
  • 需要严格快照隔离的金融、电商场景
  • 大规模数据湖(PB 级以上)

2.4 Apache Hudi:增量处理与流式摄取的“急先锋”

Hudi 是 Uber 开源的,最初就是为了解决“如何高效地更新数据湖里的记录”这个问题。如果你要做 CDC(变更数据捕获)入湖,Hudi 几乎是首选。

核心机制:写时复制(COW) vs 读时合并(MOR)

Hudi 提供了两种存储类型:

  • Copy on Write (COW):写数据时直接重写整个文件,读性能好,写放大严重。
  • Merge on Read (MOR):写数据时只写增量日志(log file),读时合并 base 文件和 log 文件。写性能好,读性能稍差。
-- 创建 Hudi 表(MOR 模式)
CREATE TABLE user_profile (
  user_id BIGINT,
  name STRING,
  email STRING,
  last_updated TIMESTAMP
) USING HUDI
TBLPROPERTIES (
  'primaryKey' = 'user_id',
  'type' = 'mor',
  'hoodie.table.name' = 'user_profile'
)
LOCATION '/data/user_profile';

-- 增量查询:只读最近 1 小时的变更
SELECT * FROM user_profile
  WHERE `_hoodie_commit_time` > '20240201120000';
避坑指南:我曾经在生产环境把 MOR 表的 hoodie.compact.inline 设成了 false,结果 log 文件越积越多,读查询越来越慢。后来改成每 5 个 commit 自动 compaction 一次,才恢复正常。MOR 模式一定要配好 compaction 策略。

适用场景:

  • CDC 入湖(Kafka → Hudi)
  • 需要行级更新的场景(如用户画像、订单状态变更)
  • 写多读少的实时摄取链路

2.5 三大格式横向对比

下面这张表是我在实际选型时常用的对比维度,你直接拿去用:

对比维度 Delta Lake Apache Iceberg Apache Hudi
创始团队 Databricks Netflix / Apple Uber
事务模型 乐观并发控制(OCC) 快照隔离(Serializable) 基于时间线的 MVCC
更新方式 重写文件(Merge on Read 有限支持) 重写文件(Copy on Write) COW + MOR 双模式
增量查询 通过 Change Data Feed 通过 Incremental Read 原生增量查询(最强)
引擎支持 Spark 为主,Flink 有限 Spark / Flink / Trino / Presto / Hive Spark / Flink / Presto / Hive
Catalog 接口 Hive Metastore / Unity Catalog REST / JDBC / Hive / Nessie Hive Metastore / AWS Glue
小文件合并 OPTIMIZE 命令 Rewrite Data Files 动作 Compaction 机制(自动/手动)
社区活跃度 高(Databricks 主导) 高(中立,厂商参与多) 中高(Onehouse 等厂商推动)

2.6 我如何选型?

说实话,没有“最好”的格式,只有“最合适”的。我一般按这个思路判断:

  1. 如果团队全是 Spark 技术栈,且不想折腾多引擎——选 Delta Lake。开箱即用,文档最全。
  2. 如果公司有 Flink + Trino + Spark 混用,或者未来可能换引擎——选 Iceberg。它的 REST Catalog 和快照隔离是长期维护的保障。
  3. 如果核心场景是 CDC 入湖、行级更新、增量 ETL,——选 Hudi。它的 MOR 模式和增量查询能力,其他两家目前还追不上。

我的建议:别在项目初期就纠结“选哪个最好”。先选一个你最熟悉的,跑通一条完整链路。我见过太多团队花两个月对比选型,结果连一条实时流都没跑通。先动起来,再优化。