2. Lakehouse 架构基础:数据湖与数据仓库的演进、核心特征与开放格式对比
好,咱们直接进入正题。这一章我打算把 Lakehouse 的底牌翻出来给你看。说白了,就是回答三个问题:这东西从哪来?凭什么能打?具体怎么选?
我最早接触数据架构那会儿,还在跟 Hadoop 集群死磕。那时候最头疼的就是——数据写进去了,读出来对不对全靠运气。嗯,后来 Lakehouse 的概念一出来,我第一反应是:这不就是我一直想要的东西吗?
2.1 数据湖与数据仓库的演进:从两条路到一条路
先聊聊历史。你想想看,传统数据仓库(Data Warehouse)是什么?
- 强 Schema 约束:数据进来之前,表结构必须定死。ETL 清洗完才能入库。
- ACID 事务:支持事务,读写一致性有保障。
- 高性能查询:针对结构化数据做了大量优化。
但它的毛病也很明显——贵、扩展难、非结构化数据基本没法处理。
后来数据湖(Data Lake)火了。Hadoop 生态圈那阵子,大家都在喊「存原始数据,想怎么用怎么用」。好处是便宜、能存任意格式、支持机器学习。但问题也来了:
所以你看,数据仓库太死板,数据湖太随意。Lakehouse 的思路很简单——把数据仓库的 ACID 和 Schema 管理能力,搬到数据湖的廉价存储上。两条路并一条,这就是 Lakehouse 的底层逻辑。
核心演进路径:
- 数据仓库 → 强管控、高成本、结构化
- 数据湖 → 低成本、弱管控、任意格式
- Lakehouse → 低成本 + 强管控 + 任意格式 + 机器学习
2.2 Lakehouse 的核心特征
聊完演进,咱们看看 Lakehouse 到底凭什么能打。我个人习惯把它的核心特征归纳为三个关键词:ACID 事务、Schema-on-Read/Write、开放格式。
2.2.1 ACID 事务:数据一致性的最后一道防线
ACID 这四个字母,做数据库的人都不陌生。但在数据湖场景下,实现起来可没那么简单。
- 原子性(Atomicity):写入要么全成功,要么全失败。不会出现半拉子数据。
- 一致性(Consistency):事务前后,数据约束必须满足。
- 隔离性(Isolation):并发读写互不干扰。
- 持久性(Durability):提交后数据不会丢。
我建议你记住这个场景:假设你同时在跑两个 Spark 任务,一个在写分区数据,一个在读报表。没有 ACID,读任务可能读到一半写了一半的数据,报表直接崩掉。有了 ACID,读任务要么看到完整的新数据,要么看到完整的老数据,不会有中间态。
2.2.2 Schema-on-Read vs Schema-on-Write
这个点很有意思。传统数据仓库是 Schema-on-Write——数据进来之前就得定好结构。数据湖早期是 Schema-on-Read——数据先存着,读的时候再解析。
Lakehouse 的做法是:两者都要。
- 写入时,可以带 Schema 约束(比如 Delta Lake 的 schema enforcement)。
- 读取时,也支持灵活解析(比如读 JSON 嵌套字段)。
- Schema 还能演化(增加列、修改类型),不用重写整个表。
我记得有个项目,业务方三天两头加字段。传统数仓得改 ETL、重建表,折腾死人。换成 Lakehouse 后,直接 ALTER TABLE ADD COLUMN,几秒钟搞定。这就是 Schema 演化的威力。
2.2.3 开放格式:不被厂商绑架
Lakehouse 的存储层用的是 Parquet、ORC、Avro 这些开放格式。不是某个厂商的私有格式。这意味着:
- Spark、Flink、Presto、Trino 都能读。
- 数据迁移成本低,不会被锁定。
- 压缩率高,存储成本可控。
开放格式 + 开放元数据 = 真正的数据自由
这也是为什么 Delta Lake、Iceberg、Hudi 都选择基于 Parquet 构建。你想想看,如果底层格式是闭源的,谁敢把核心业务数据放上去?
2.3 Delta Lake / Iceberg / Hudi 对比
好,到了大家最关心的环节。这三个项目,到底怎么选?
我先说结论:没有银弹。每个项目都有自己的设计哲学和适用场景。我三个都用过,下面这张表是我个人的使用感受。
| 特性 | Delta Lake | Apache Iceberg | Apache Hudi |
|---|---|---|---|
| 创始团队 | Databricks | Netflix / Airbnb | Uber |
| 事务实现 | 事务日志(JSON) | Manifest 清单文件 | Timeline 时间线 |
| Schema 演化 | 支持(列增删改) | 支持(列增删改、重命名) | 支持(列增删改) |
| 时间旅行 | ✅ 原生支持 | ✅ 原生支持 | ✅ 原生支持 |
| UPSERT 性能 | 中等(Merge 语法) | 中等(Merge 语法) | 优秀(专为增量设计) |
| 查询引擎兼容 | Spark、Presto、Trino | Spark、Flink、Trino、Hive | Spark、Flink、Presto、Hive |
| 增量查询 | 需 CDC 方案 | 需 CDC 方案 | 原生增量查询 |
| 社区活跃度 | 高(Databricks 主导) | 高(Apache 基金会) | 中(Apache 基金会) |
2.3.1 Delta Lake:Databricks 的亲儿子
Delta Lake 是我最早接触的 Lakehouse 方案。它的设计哲学很明确——跟 Spark 深度绑定。
- 事务日志用 JSON 文件存储,简单直观。
- 支持
MERGE、UPDATE、DELETE标准 SQL。 - 时间旅行(Time Travel)用版本号或时间戳查询历史快照。
-- Delta Lake 时间旅行示例
SELECT * FROM my_table VERSION AS OF 123;
SELECT * FROM my_table TIMESTAMP AS OF '2024-01-01 10:00:00';
我曾经踩过的坑:Delta Lake 的事务日志如果文件太多,Checkpoint 不及时,查询性能会急剧下降。建议定期执行 OPTIMIZE 和 VACUUM,保持事务日志的整洁。
2.3.2 Apache Iceberg:开放生态的标杆
Iceberg 的设计更「学术派」。它的 Manifest 清单文件结构非常清晰,查询引擎解耦做得最好。
- 支持 Spark、Flink、Trino、Hive 等多种引擎。
- 分区裁剪(Partition Pruning)效率极高。
- Schema 演化支持列重命名,Delta Lake 早期版本不支持。
-- Iceberg 创建表示例
CREATE TABLE iceberg_catalog.my_db.my_table (
id BIGINT,
name STRING,
ts TIMESTAMP
) USING iceberg
PARTITIONED BY (days(ts));
我建议你关注这个点:如果你的团队同时用 Spark 和 Flink,Iceberg 的引擎兼容性是最好的。不需要为不同引擎维护两套表。
2.3.3 Apache Hudi:增量处理的王者
Hudi 出身 Uber,天生就是为增量数据设计的。它的 Copy-on-Write 和 Merge-on-Read 两种存储类型,给了用户很大的灵活性。
- Copy-on-Write(COW):写入时直接重写文件,读性能好。
- Merge-on-Read(MOR):写入时只写增量日志,写性能好,读时合并。
- 原生支持增量查询(Incremental Query),适合 CDC 场景。
-- Hudi 增量查询示例
-- 读取从某个 commit 之后的变化数据
SELECT * FROM hudi_table
WHERE _hoodie_commit_time > '20240101000000';
选型建议:
- 如果你深度绑定 Databricks/Spark 生态 → Delta Lake
- 如果你需要多引擎兼容、开放标准 → Apache Iceberg
- 如果你的核心场景是增量写入、CDC、实时入湖 → Apache Hudi