Delta Lake 是什么?为什么我们需要它?
聊 Lakehouse,绕不开 Delta Lake。我刚开始接触数据湖时,最头疼的问题就是:数据写到一半任务挂了,留下一堆脏数据;或者今天跑的报告和昨天对不上,查了半天发现是有人改了历史文件。说白了,传统数据湖缺少数据库那样的事务保障。
Delta Lake 就是来解决这个问题的。它是一个开源存储层,跑在 Parquet 之上,给数据湖加上了 ACID 事务、版本控制、Schema 校验这些能力。你可以把它理解成「带数据库特性的文件系统」。
一句话总结:Delta Lake = Parquet 文件 + 事务日志(Transaction Log)+ 版本快照。
核心特性一:ACID 事务
ACID 是数据库的四个基本特性:原子性、一致性、隔离性、持久性。Delta Lake 把这些带到了数据湖里。
举个例子。你正在往一张表里写入 100 个文件,写到第 50 个时集群挂了。没有 Delta Lake 的话,这 50 个文件就留在那了,下次读数据的人会看到半成品。有了 Delta Lake,这次写入要么全部成功,要么全部回滚——不会留下中间状态。
我遇到过最典型的场景是:多个任务同时往同一张表写数据,互相覆盖。用了 Delta Lake 之后,每个写入都是一个独立事务,冲突时会自动重试或报错,不会静默覆盖。
我的习惯:生产环境里,我建议所有写入操作都走 Delta Lake 的 merge 或 upsert,而不是直接覆盖分区。这样能避免并发写入时的数据丢失。
核心特性二:时间旅行(Time Travel)
时间旅行是 Delta Lake 最酷的特性之一。它允许你查询某个时间点的数据快照,或者回滚到之前的版本。
为什么会需要这个?
- 数据回滚:有人误删了数据,你可以直接回滚到一小时前的版本。
- 审计追溯:监管要求提供上个月某天的数据状态,直接查那个时间点就行。
- 实验对比:跑新模型时,可以对比新旧两版数据对结果的影响。
实现原理其实不复杂。Delta Lake 的每个操作都会在事务日志里记录一条「提交」。每次查询时,引擎会读取日志,重建出那个版本的文件列表。旧文件不会被立即删除,而是保留一段时间(默认 7 天或 168 小时)。
-- 查询某个时间点的数据
SELECT * FROM my_table TIMESTAMP AS OF '2024-01-15 10:00:00'
-- 查询某个版本的数据
SELECT * FROM my_table VERSION AS OF 42
-- 查看历史版本
DESCRIBE HISTORY my_table
避坑指南:我曾经因为没配清理策略,导致事务日志膨胀到几百 GB。记得定期执行 VACUUM 清理旧文件,但别把保留期设得太短——至少留够 7 天,方便回滚。
核心特性三:Schema 演进(Schema Evolution)
数据湖里,Schema 变化是家常便饭。业务加字段、改类型、删列……传统 Parquet 表遇到 Schema 不匹配就直接报错。Delta Lake 允许你优雅地处理这些变化。
Delta Lake 支持以下几种 Schema 演进操作:
- 新增列:默认允许,新列会填充 null。
- 列类型变更:需要显式设置
mergeSchema为 true。 - 列重命名:通过
ALTER TABLE RENAME COLUMN实现。 - 列删除:通过
ALTER TABLE DROP COLUMN实现。
-- 写入时自动合并 Schema
df.write \
.mode("append") \
.option("mergeSchema", "true") \
.format("delta") \
.save("/path/to/table")
-- 手动修改 Schema
ALTER TABLE my_table ADD COLUMNS (new_col STRING)
ALTER TABLE my_table ALTER COLUMN old_col TYPE BIGINT
我个人习惯是:在开发阶段用自动合并 Schema,生产环境里手动管理 Schema 变更,避免意外引入不兼容的改动。
Delta Lake vs Parquet:一张表说清楚
很多人问:Delta Lake 不就是 Parquet 吗?其实不是。Delta Lake 底层确实用 Parquet 存数据,但多了很多关键能力。我整理了一张对比表:
| 特性 | Parquet | Delta Lake |
|---|---|---|
| 存储格式 | 列式存储 | Parquet + 事务日志 |
| ACID 事务 | 不支持 | 支持(可序列化隔离级别) |
| 时间旅行 | 不支持 | 支持(按版本或时间戳查询) |
| Schema 演进 | 手动处理 | 自动或手动演进 |
| 并发控制 | 无(文件级覆盖) | 乐观并发控制 |
| 数据版本管理 | 无 | 事务日志记录每次变更 |
| 性能优化 | 列裁剪、谓词下推 | 以上 + Z-Order、数据跳过、文件压缩 |
| 适用场景 | 静态数据、只读分析 | 实时写入、增量处理、数据治理 |
说白了,如果你只是存一批历史数据,跑一次分析就完事,Parquet 够用了。但如果你需要多人协作、增量写入、数据回滚、Schema 灵活变化——Delta Lake 是更好的选择。
Delta Lake 的核心架构
下面这张图展示了 Delta Lake 的核心组件和读写流程:
这张图展示了 Delta Lake 的读写流程。用户通过 Spark、Flink 或 Presto 发起请求,Delta Lake 的 API 层会做两件事:一是更新事务日志(记录这次操作),二是读写实际的 Parquet 文件。每次查询时,引擎先读事务日志,重建出当前版本的文件列表,再读取对应的数据文件。
嗯,这里要注意:事务日志是 Delta Lake 的「大脑」。它记录了每一次提交的元数据,包括新增了哪些文件、删除了哪些文件、Schema 是什么版本。没有事务日志,Delta Lake 就只是一个普通的 Parquet 目录。
什么时候用 Delta Lake?
根据我的经验,以下场景特别适合引入 Delta Lake:
- 实时数据管道:需要频繁写入和读取,且要求数据一致性。
- 多团队协作:多个任务同时操作同一张表,需要并发控制。
- 数据回滚需求:业务上需要「后悔药」,能回到某个时间点。
- 增量 ETL:每天只处理新增或变更的数据,而不是全量覆盖。
- Schema 频繁变化:业务字段经常增减,不想每次手动改表结构。
反过来说,如果你的场景是「一次性导入数据,之后只读不写」,或者数据量极小(几百 MB),那直接用 Parquet 就够了,没必要上 Delta Lake 的复杂度。
我的建议:刚开始接触 Lakehouse 时,可以先从一张 Delta 表开始试水。把最核心的、写入最频繁的那张表迁移过来,感受一下 ACID 事务和版本管理带来的变化。等熟悉了再逐步推广。