Iceberg 元数据治理怎么做对:四类膨胀源、维护三板斧与自动化六原则

0 阅读6分钟

一个真实生产案例:一张 Flink 实时写入的 Iceberg 表,数据文件总共 46MB,元数据目录却有 15GB——是数据的 300 多倍。提交历史 5713 次:每次 checkpoint 提交一次、写一份新 metadata.json,旧的一份都没删。表现出来的症状很典型:对象存储账单翻倍而业务数据没涨,查询规划(planning)越来越慢,count(*) 出来的量和存储桶实际体积对不上。

这不是 bug,是 Iceberg 的默认行为:它把清理责任完全留给了使用者。这篇讲清楚元数据治理怎么做对:四类膨胀源各用什么动作治、一轮维护按什么顺序跑、以及怎么把这些事自动化。文中方案已在数据平台「我的数据空间」(datastudiohappy.cn)里产品化为周期维护作业。

一、机制速览:为什么放着不管一定膨胀

Iceberg 元数据四层结构与维护三板斧

记住三个性质就够:

  • 一切皆追加。每次写入(insert/overwrite/delete/compaction)都不修改旧文件,而是生成新数据文件 + 新 manifest + 新快照 + 新 metadata.json,再原子切换指针。旧的全留着——这是 time travel 和事务隔离的基础。
  • 文件被快照引用才「存在」。不在任何引用链里的文件对查询不可见,但照占存储、照常计费。
  • 默认没有任何自动清理。快照不过期、旧 metadata.json 不删、失败写入残留的文件没人管。

于是膨胀有四个来源,各有各的治法。

二、四类膨胀源,四个治理动作

1. metadata.json 堆积 → 两个表属性

流式写入提交频率极高:checkpoint 间隔 1 分钟,一天就是 1440 次提交;metadata.json 含全部快照列表,表越老单个文件越大,再乘以提交次数,体积平方级增长。开头那张 15GB 的表就是这么来的。

治理动作是给表加两个属性:write.metadata.delete-after-commit.enabled=true + write.metadata.previous-versions-max=20。一个好用的细节:这条 ALTER 本身就是一次提交,提交当刻就把元数据日志裁剪到 20 份并物理删除多余的旧文件——已膨胀的存量表打上属性即刻瘦身,不用另跑清理。更进一步,把这两个属性放进建表默认值:根因修复优于事后治理。

2. 快照堆积 → expire_snapshots

快照默认永不过期;不过期,它引用的数据文件就不能物理删除——哪怕数据早被 DELETE/OVERWRITE「逻辑删除」了。快照多还意味着 manifest 多,查询规划要读的元数据随之变多,planning 从毫秒级涨到秒级。

CALL catalog.system.expire_snapshots(table => 'db.tbl', older_than => TIMESTAMP '...', retain_last => 10);

这是唯一能把「逻辑删除」变成「物理释放」的常规途径。参数本质是 time travel 能力与存储成本的交易,常用基线:保留最近 7 天、且至少 10 个快照——窗口内可回看回滚,retain_last 兜底低频写入的表。快照收敛后若 manifest 仍碎(高频小提交的后遗症),再按阈值(如超过 50 个)跑 rewrite_manifests 合并清单。

3. 孤儿文件 → remove_orphan_files(正确姿势最重要)

孤儿文件是物理存在于表目录、却不被任何快照引用的文件,来源很多:作业在提交快照前失败(文件已写出、快照没提交)、流式作业未从 checkpoint 恢复的中断、并发提交冲突后的重试、DROP TABLE 不带 PURGE 只摘登记不删文件。expire_snapshots 永远清不掉它们——孤儿从没被快照引用过,两个动作治的是两种病,一个都不能少。

清理动作 remove_orphan_files 会列出表目录全部文件、与元数据引用做反连接,删掉早于 older_than 且无人引用的文件。它是三板斧里唯一可能造成数据丢失的动作,正确姿势三条:

  • older_than 宽限至少 3 天,须覆盖最长写入事务再加富余——正在写入、尚未提交的文件恰恰处于「存在但不被引用」状态,窗口给短了就会误删:轻则丢数据,重则表元数据指向已删除的文件,查询直接报错;
  • 避开写入高峰跑;
  • 确认表 location 无误,配错目录会把别的表的文件全当孤儿。

原则是 fail-closed:孤儿多占几天存储是钱的问题,误删是数据丢失的问题,不在一个量级。

4. 小文件碎片 → rewrite_data_files

高频小提交攒出大量小文件,拖慢扫描。合并动作:

CALL catalog.system.rewrite_data_files(table => 'db.tbl');

它是真正读写全量数据的重活:给独立资源队列,别与业务查询抢资源;按文件统计(平均大小、小文件占比)挑表跑,不无脑全量;高频写入的表开 partial-progress 分批提交,降低与并发写入撞提交冲突的代价。

三、一轮维护的正确顺序

rewrite_data_files → expire_snapshots → remove_orphan_files:先合并(旧小文件变成「仅被旧快照引用」),再过期快照(物理释放那批旧文件),最后清孤儿收尾。顺序反了不会出错,但旧文件要多占一轮存储、等下轮才释放。

四、平台化:自动化六原则

500 张表、30 个团队,靠「工程师记得维护自己的表」的结局一定是:核心表有人管、长尾表没人管,而出事的永远是长尾表——开头那张 15GB 的表,就是一张没人看的 CDC 落地表。这些动作必须做成自动周期执行的维护作业,六条设计原则:

  • 表清单驱动,不靠人报名。维护系统自动同步全量表清单,新表自动纳管;「注册制」必然漏表,漏的就是雷。
  • 建表默认值 + 存量巡检补齐。元数据保留属性建表即带上;周期巡检为存量表幂等补齐,绕过平台直连引擎建的表也会被捞回来。
  • 幂等对账,不记流水账。每轮从「期望态(配置)+ 实际态(表当前属性/快照/文件统计)」重新推导该做什么,重复执行无害——调度重试、错过补跑、崩溃重跑都安全。
  • 逐表隔离 + 审计留痕。一张表失败不阻断其余;每轮每表的动作与结果落审计表,「上周谁过期了这批快照」查得到。
  • 危险操作保守化。孤儿清理不进默认自动化、时间窗口设硬下限;快照保留天数只许调大。自动化放大效率,也放大误操作。
  • 监控前置。快照数、元数据文件数、小文件占比、逻辑/物理存储比做成指标定期采集,等账单异常才发现,已经晚了几个月。

这套原则在「我的数据空间」(产品介绍)里的形态:全部表统一纳管、自动进入维护清单,治理作业按 cron 周期执行:

表管理:全部表统一纳管,新表自动进入维护清单

每张表的当前快照、TTL 保留策略在表详情直接可见、可配置,配置写穿到表属性,由维护作业按幂等对账执行:

表详情:当前快照与 TTL 保留策略可见可配

Iceberg 给了一台性能优秀的发动机,但没给保养手册。表格式选型只是开始,元数据治理的自动化才是长期成本和稳定性的分水岭


这些维护作业在「我的数据空间」里已产品化(自动周期执行)——一套可私有化部署的数据平台,支持 OEM 合作。产品介绍:datastudiohappy.cn/,交流合作 QQ:1559851993。