1. Lakehouse 概述:从数据仓库到 Lakehouse 的演进
大家好,我是这次课程的主讲。今天咱们聊聊 Lakehouse 这个架构。
说实话,我刚入行那会儿,数据仓库和数据湖还是两个泾渭分明的概念。做数仓的看不起做数据湖的,觉得湖里全是“脏数据”;做数据湖的觉得数仓太死板,业务一变化就得改模型。嗯,这种“鄙视链”持续了好几年。
直到后来,大家发现——其实两边都有道理,也都有痛点。于是 Lakehouse 应运而生。
1.1 数据仓库的演进:从 OLTP 到 OLAP
数据仓库这个概念,最早可以追溯到 80 年代。那时候数据库主要是做交易处理的,也就是 OLTP。后来大家发现,跑报表和做分析的查询,跟交易查询完全是两码事。
我记得在 2015 年,我参与过一个零售项目的数仓建设。业务方要求 T+1 出报表,但底层还是用 MySQL 直接查。结果一到凌晨跑批,整个系统就卡死。后来我们上了专门的数仓方案,才把问题解决。
数据仓库的核心特点,我总结为三点:
- Schema-on-Write:写入时就要定义好结构,数据必须“规规矩矩”进来
- 强 ACID 事务:保证数据的一致性和可靠性
- 面向分析优化:列式存储、预聚合、物化视图等
但数仓也有硬伤。最明显的就是——贵。存储贵、计算贵、扩展也贵。而且数据模型一旦定下来,改起来就像动手术。
1.2 数据湖的兴起:存储廉价,但管理混乱
后来 Hadoop 生态起来了,大家发现可以用廉价硬件存海量数据。数据湖的概念就这么火了。
数据湖的核心思想是:先把数据存下来,格式不限,结构不限。等要用的时候再处理。也就是 Schema-on-Read。
听起来很美好对吧?但我在项目中踩过不少坑。
有一次,业务方说“湖里有一张用户行为表,你直接查就行”。结果我跑了一个简单的 count,等了半小时没出结果。后来发现,那张表是几千个小文件拼起来的,而且数据质量极差——时间戳字段里混着中文、空值和负数。
这就是数据湖的典型问题:缺乏治理,数据沼泽。
数据湖的主要问题包括:
- 不支持事务:并发写入容易出问题
- 数据质量难保证:没有 Schema 约束,脏数据随便进
- 性能不稳定:小文件问题、元数据膨胀
- 缺乏统一的权限管理:谁都能读,谁都能写
1.3 Lakehouse 架构定义:鱼和熊掌我都要
Lakehouse 的出现,说白了就是想把数仓的“规整”和数据湖的“灵活”结合起来。
2017 年,Databricks 提出了 Lakehouse 的概念。核心思路是:在数据湖的廉价存储之上,增加一层“元数据层”和“事务层”,让湖里的数据也能像数仓一样被管理。
我个人理解,Lakehouse 有四个核心特征:
- 事务支持:ACID 特性,保证并发读写的一致性
- Schema 管理:支持 Schema 演进,但不允许脏数据
- 开放存储:数据以 Parquet/ORC 等开放格式存储,不被厂商锁定
- 统一批流:同一份数据,既能做批处理,也能做流处理
💡 一句话总结:Lakehouse = 数据湖的存储底座 + 数据仓库的管理能力 + 流批一体的计算引擎。
下面这张图,是我自己画的 Lakehouse 核心架构。你可以看到,底层是对象存储(比如 S3 或 HDFS),中间是表格式层(Delta Lake / Iceberg / Hudi),上层是计算引擎(Spark / Flink / Trino)。
1.4 Delta Lake 与 Apache Iceberg 对比
说到表格式层,目前最主流的就是 Delta Lake 和 Apache Iceberg。这两个我都用过,各有千秋。
先说说 Delta Lake。它是 Databricks 在 2019 年开源的,后来捐给了 Linux 基金会。Delta Lake 的核心是一个叫 Transaction Log 的东西,所有对表的操作都会记录在这个日志里。
我最早接触 Delta Lake 是在 2020 年,当时做一个实时数仓项目。业务要求数据写入后 5 分钟内就能查到,而且不能有重复。Delta Lake 的 Merge 操作 帮了大忙——upsert 语义天然支持,不用自己写去重逻辑。
再说 Apache Iceberg。它最早是 Netflix 开发的,2018 年开源,后来也捐给了 Apache 基金会。Iceberg 的设计哲学是“不依赖任何计算引擎”,所以它跟 Spark、Flink、Trino 都能很好地集成。
我个人觉得,Iceberg 最大的亮点是 分区演进。什么意思呢?就是你可以随时改分区策略,不用重写历史数据。这在 Delta Lake 里实现起来就比较麻烦。
下面这张表,是我根据实际使用经验整理的对比:
| 对比维度 | Delta Lake | Apache Iceberg |
|---|---|---|
| 开源时间 | 2019 年(Databricks) | 2018 年(Netflix) |
| 事务实现 | Transaction Log(JSON 格式) | Manifest 文件 + Snapshot |
| Schema 演进 | 支持(列添加/删除/重命名) | 支持(更灵活,支持嵌套结构) |
| 分区演进 | 不支持(需重写数据) | 支持(隐式分区,无需重写) |
| 时间旅行 | ✅ 支持(基于版本号) | ✅ 支持(基于 Snapshot ID) |
| 增量查询 | ✅ 支持(CDC Feed) | ✅ 支持(Incremental Read) |
| 引擎兼容性 | Spark 为主,Flink/Trino 有限 | Spark / Flink / Trino / Hive 全支持 |
| 小文件合并 | OPTIMIZE 命令(自动/手动) | Rewrite Data Files(手动触发) |
| 社区活跃度 | 高(Databricks 主导) | 高(Apache 基金会 + 多家厂商) |
💡 我的选型建议:
- 如果你的技术栈以 Spark 为主,且团队对 Databricks 生态熟悉,选 Delta Lake 会更顺手。
- 如果你的场景需要 多引擎混用(比如 Spark 做 ETL,Flink 做实时,Trino 做 Ad-hoc 查询),Iceberg 的兼容性更好。
- 如果你对 分区演进 有强需求(比如业务分区策略经常变),Iceberg 是唯一选择。
1.5 一个简单的代码示例:用 Delta Lake 创建表
光说不练假把式。咱们看一段实际的代码。下面是用 Spark + Delta Lake 创建一张用户行为表的例子:
-- 创建 Delta 表
CREATE TABLE user_events (
user_id STRING,
event_type STRING,
event_time TIMESTAMP,
page_url STRING
)
USING DELTA
PARTITIONED BY (event_date STRING)
LOCATION 's3://my-lakehouse/user_events/';
-- 写入数据
INSERT INTO user_events VALUES
('u001', 'click', '2024-01-15 10:30:00', '/home'),
('u002', 'view', '2024-01-15 10:31:00', '/product/123');
-- 查询历史版本(时间旅行)
SELECT * FROM user_events VERSION AS OF 1;
-- 合并数据(upsert)
MERGE INTO user_events AS target
USING updates AS source
ON target.user_id = source.user_id AND target.event_time = source.event_time
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;
这段代码看起来简单,但背后涉及的东西不少。比如 MERGE 操作,Delta Lake 内部会先做一次读取,判断哪些数据需要更新,哪些需要插入,然后生成新的数据文件,最后更新 Transaction Log。
嗯,这里要注意:如果表很大,MERGE 的性能可能会成为瓶颈。我建议在写入前先做一次数据去重,减少 Merge 的负担。
1.6 本章小结
咱们这一章聊了三个核心问题:
- 数据仓库 规整但昂贵,适合结构化报表场景
- 数据湖 灵活但混乱,适合原始数据存储
- Lakehouse 取两者之长,用表格式层解决数据湖的治理问题
Delta Lake 和 Iceberg 是目前最主流的两个表格式方案。Delta Lake 强在 Spark 生态和易用性,Iceberg 强在引擎兼容性和架构灵活性。选哪个,取决于你的实际场景。