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

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

恢复误删的地区,第二天的数据先在分支上检查再发布

在 TT Lab 中继续学习

目标

在提交了三天的数据之后,制造一起整个删除某个地区的事故,用时间旅行读取事故之前的状态,用 tag 给那个时间点起名,再用回滚恢复过来。接着把第二天的数据先写入分支而不是 main,审核之后再快进 main 来发布。

为什么重要

Iceberg 的每一次提交,都不会删除旧快照,而是添加新快照。所以“昨天的状态”不是从备份里取出来,而是读取那个快照的文件列表;回滚也不是复制文件,而是把 main 这个指针挪到旧快照上。几秒钟就能完成。 不过,旧快照并不是永久的。快照过期作业一运行,比标准更旧的快照就会被删除,只记下了 ID 的时间点也会消失。tag 给快照起一个名字,并另外设置保留期限,使它免受过期的影响。 分支则是反方向的工具。先把新数据提交到 main 之外的地方,检查通过后,再把 main 快进到那个快照。读取的人一次都看不到检查之前的数据(write-audit-publish)。

步骤

  1. 用 /root/ice/tt/days.py(应用 ice-tt-days)创建 lake.tt.orders(format-version 2),并把 2026-03-01、02、03 每天一次提交地写入。
  2. 用 /root/ice/tt/accident.py(应用 ice-tt-accident)运行 DELETE FROM lake.tt.orders WHERE region = 'busan'(这是故意制造的事故)。
  3. 用 /root/ice/tt/travel.py(应用 ice-tt-travel)找到第三次提交(事故之前)的快照,把那时的行数、busan 的行数以及现在 busan 的行数写入 /root/ice/tt/out/travel.json。
  4. 用 /root/ice/tt/tag.py(应用 ice-tt-tag)给那个快照打上 tag before_accident,保留期限为 RETAIN 7 DAYS。
  5. 用 /root/ice/tt/rollback.py(应用 ice-tt-rollback)调用 rollback_to_snapshot,把 main 回滚到那个快照。
  6. 用 /root/ice/tt/branch.py(应用 ice-tt-branch)创建分支 fix,并把 2026-03-04 只写入这个分支。
  7. 用 /root/ice/tt/publish.py(应用 ice-tt-publish)调用 fast_forward,把 main 快进到 fix。
  8. 在 /root/ice/tt/report.md 中写出 ## 타임트래블、## 롤백과 태그、## 브랜치 三个小节。

参考

三天,三次提交

创建 /root/ice/tt/days.py,应用名称为 ice-tt-days,创建 lake.tt.orders(六个列,'format-version' = '2'),并把 2026-03-01、03-02、03-03 每天用一次 append() 写入。

三个快照通过父快照相连。评分器会检查前三个快照是否依次为加上当天行数的 append。如果运行了多次导致快照增多,请在执行 DROP TABLE lake.tt.orders PURGE 之后重新开始。

事故——整个地区消失了

创建 /root/ice/tt/accident.py,应用名称为 ice-tt-accident,并运行 DELETE FROM lake.tt.orders WHERE region = 'busan'。

DELETE 也是一次提交,所以会产生第四个快照。含有 busan 行的文件,会换成去掉 busan 的新文件并加入列表,旧文件只是从列表中去掉,在磁盘上依然存在。评分器会检查第四个快照是否以第三个为父快照,并且没有 busan 的行。

时间旅行——读取事故之前

创建 /root/ice/tt/travel.py,应用名称为 ice-tt-travel,从 lake.tt.orders.snapshots 中找到第三次提交的快照 ID,用 VERSION AS OF 统计那时的总行数和 busan 的行数,再统计现在的表中 busan 的行数,按 {"snapshot_id", "rows_before", "busan_before", "busan_after"} 的格式写入 /root/ice/tt/out/travel.json。

VERSION AS OF 只是读取那个快照的清单列表。事故之前的文件还在磁盘上,所以能原样读取。快照 ID 是 19 位整数,不要手抄,请在脚本中读取后使用。

tag——给时间点起名并设置保留期限

创建 /root/ice/tt/tag.py,应用名称为 ice-tt-tag,用 ALTER TABLE lake.tt.orders CREATE TAG before_accident AS OF VERSION <ID> RETAIN 7 DAYS 给第 3 步的快照打上 tag。

tag 会以名称、快照 ID 和 max-ref-age-ms 的形式写进 metadata 的 refs。RETAIN 7 DAYS 就是 604,800,000ms。tag 所指向的快照不会被列为过期对象。评分器会检查 refs 中的这三个值。

回滚——只移动指针

创建 /root/ice/tt/rollback.py,应用名称为 ice-tt-rollback,对 tag before_accident 所指向的快照调用 CALL lake.system.rollback_to_snapshot('lake.tt.orders', <ID>)。

回滚不会创建新快照,只是改变 main 所指向的快照。事故快照依然留在 snapshots 列表中,“事故 → 事故之前”会被记录在 main 走过的历史(snapshot-log)里。评分器会检查这条记录,以及事故快照是否还在。

先写入分支

创建 /root/ice/tt/branch.py,应用名称为 ice-tt-branch,在执行 ALTER TABLE lake.tt.orders CREATE BRANCH fix 之后,用 writeTo("lake.tt.orders.branch_fix").append() 把 2026-03-04 的文件只写入 fix。

分支会从创建时的 main(回滚后的快照)分叉出去。向 fix 提交后,只有 fix 向前推进,main 保持不变,所以读取的人还看不到 3 月 4 日的数据。评分器会检查 fix 的头部是否以回滚后的快照为父快照,并加上了 3 月 4 日的行数。

发布——快进 main

创建 /root/ice/tt/publish.py,应用名称为 ice-tt-publish,调用 CALL lake.system.fast_forward('lake.tt.orders', 'main', 'fix')。

fast_forward 只有在 main 是 fix 的祖先时才能成功(如果这期间 main 上插进了别的提交,就会被拒绝)。它不产生新快照,只把 main 指针挪到 fix 的头部。评分器会检查 main 和 fix 是否指向同一个快照,以及 main 中是否同时有 busan 和 3 月 4 日的数据。

把回滚步骤总结在一页纸上

在 /root/ice/tt/report.md 中写出 ## 타임트래블、## 롤백과 태그、## 브랜치 三个小节。第一节以数字写入第 3 步的 busan_before。

请按凌晨出了同样的事故时,值班工程师应当遵循的顺序来写。如果是有过期作业在运行的表,还要写出应该先做什么。