TT Lab
开始
学习 学习路径 课程

湖仓表格式 — 从元数据理解 Apache Iceberg

撤销是移动指针而不是复制文件 — 时间旅行、回滚、标签与分支

在 TT Lab 中继续学习

一句话总结

Iceberg 的提交不会删除旧快照,而是添加新快照,所以读取过去(时间旅行)就是读取旧快照的列表,回滚则是把 main 指针挪到旧快照上。tag 给某个时间点起名字并设置保留期限,分支则形成与 main 分别生长的谱系。

为什么需要——凌晨三点的 DELETE

一条漏了条件的 DELETE 把某个地区的订单整个删掉了。如果是传统的数据湖,就得从备份里找出文件复制回来,还要手动调整,避免与这段时间里进来的数据混在一起。这要花好几个小时,而在这期间,仪表板显示的都是错误的数字。

在 Iceberg 中,造成事故的 DELETE 也是一次提交,只是创建了一个新快照,事故之前的快照和它的文件原样都在。回滚所需要的,只是改变“main 指向哪个快照”的一次 metadata 提交。

工作原理——读取过去与回滚

Spark 的时间旅行有两种。

SELECT count(*) FROM lake.demo.events VERSION AS OF 1234567890123456789;  -- 스냅샷 ID·브랜치·태그 이름
SELECT count(*) FROM lake.demo.events TIMESTAMP AS OF '2026-04-01 09:00:00';

它只是读取那个快照的清单列表,所以成本和读取当前的表是一样的。条件只有一个——那个快照和它的文件必须还在。

回滚有好几个过程(procedure)。

过程 作用
rollback_to_snapshot 把 main 回滚到当前谱系中的一个旧快照
rollback_to_timestamp 回滚到那个时刻为 current 的快照
set_current_snapshot 把一个不必是祖先的快照(或 ref)设为 current
cherrypick_snapshot 把某个快照的变更,在当前状态之上以新快照的形式搬过来(仅限 append 和动态覆盖)
fast_forward 把一个分支快进到另一个分支的最新快照

回滚既不创建新快照,也不删除事故快照。只是在 main 走过的历史(snapshot-log)中多了一行“事故 → 事故之前”。真正删除事故快照的,是之后的过期作业。

tag 与分支——有名字的引用

规范中的 snapshot references把 refs 放在 metadata 中。每个 ref 都有快照 ID 和类型(tag、branch),并带有保留策略:max-ref-age-ms(ref 本身的寿命),如果是分支,还有 min-snapshots-to-keep、max-snapshot-age-ms。main 是一个不会过期的分支。

tag 是贴在某个快照上的名字标签。这样就不必去记 19 位的 ID,更重要的是,过期不会删除 tag 所指向的快照。Branching 文档举了这样的例子:为审计而给每周、每月的快照打上 tag,并像 RETAIN 7 DAYS 这样指定保留期限。按照保留策略,过期作业会先摘掉已超过 max-ref-age-ms 的 ref,而对其余 ref 所指向的快照则予以保留。

分支是与 main 分别生长的谱系。先把新数据提交到分支而不是 main,质量检查通过后,再把 main 快进到这个分支的头部(fast_forward)。读取的人只看 main,所以一次都看不到检查之前的数据。文档把这称为审计分支(write-audit-publish,WAP),并展示了如何用 write.wap.enabled 和 spark.wap.branch,在不修改现有作业的情况下让它写入分支。fast_forward 只有在 main 是分支的祖先时才能成功,所以如果这期间 main 上插进了别的提交,就会被拒绝。

在现场相遇的样子

事故之后的回滚。值班工程师用 SELECT * FROM 표.history(占位符为表名)查看 main 走过的快照,用 VERSION AS OF 读取事故之前的快照并核对数字,然后调用 rollback_to_snapshot。几秒钟就能完成。为了调查事故原因,事故快照要保留下来。

过期作业先运行了。如果每天运行的清理作业会删除超过 5 天的快照,那么等到发现需要回到一周前的状态时,就已经晚了。对可能需要回去的时间点(月末结账、发布之前),要提前打上 tag。

未经验证的数据上了仪表板。如果加载作业直接写入 main,即使质量检查失败,也已经晚了。写入分支、检查之后再发布,失败的数据就只会留在分支上。

实际工作中真正重要的事

下一项实验要做什么

每天提交一次,放入三天的数据,然后故意制造一起删除某个地区的事故。用 VERSION AS OF 读取事故之前的行数,给那个快照打上保留 7 天的 tag,再用 rollback_to_snapshot 把 main 回滚。接着创建分支 fix,把第二天的数据只写入这个分支,再用 fast_forward 快进 main 来发布。