3、Iceberg核心概念:Table Format、Snapshot、Manifest、Metadata层
好,咱们进入正题。这一章要聊的,是Iceberg最核心的四个概念:Table Format、Snapshot、Manifest和Metadata层。
说实话,我刚接触Iceberg的时候,也被这些名词绕得有点晕。但后来我发现,只要理解了它们之间的关系,整个Iceberg的设计思路就豁然开朗了。你想想看,一个表的数据文件散落在文件系统里,怎么管理?怎么知道哪些文件是当前有效的?怎么做到快照隔离?——答案就在这四个概念里。
3.1 Table Format:不只是文件格式
很多人一听到"Table Format",第一反应是Parquet、ORC、Avro这些。嗯,这里要注意,Table Format和文件格式是两个完全不同的东西。
文件格式(比如Parquet)定义的是单行数据怎么存、怎么压缩、怎么编码。而Table Format定义的是:一个表的所有文件怎么组织、怎么发现、怎么演化。
我个人习惯把Table Format理解为"表的元数据管理系统"。它不关心你每行数据具体怎么存,它关心的是:
- 这个表有哪些数据文件?
- 这些文件在哪个分区?
- 哪些文件是新增的?哪些是删除的?
- 表的Schema是什么?分区策略是什么?
说白了,Table Format就是一张"地图",告诉你数据在哪儿、长什么样。
核心要点:Iceberg的Table Format是一种开放、跨引擎的表格式规范。它不绑定任何计算引擎,Spark、Flink、Trino、Hive都能读。这一点和Hive的ACID表、Delta Lake都不一样。
3.2 Snapshot:时间旅行的基础
Snapshot是Iceberg里我最喜欢的概念之一。它本质上就是表在某个时间点的完整快照。
每次对表进行写入、删除、更新操作,Iceberg都会生成一个新的Snapshot。旧的Snapshot不会被删除,而是保留下来。这就实现了两个非常重要的能力:
- 时间旅行(Time Travel):你可以查询任意历史时刻的表状态
- 读-写隔离:读操作读的是某个Snapshot,写操作生成新的Snapshot,互不干扰
我记得有一次线上排查问题,业务方说某条数据"莫名其妙消失了"。我直接查了前一天晚上的Snapshot,发现那条数据其实还在——是当天凌晨的某个ETL任务把它删了。如果没有Snapshot,这种问题排查起来简直是大海捞针。
小技巧:Iceberg默认会保留最近10个Snapshot。你可以通过write.snapshot.num-retained和write.snapshot.min-age来控制保留策略。生产环境我一般建议保留最近7天的Snapshot,既保证时间旅行能力,又不会让元数据膨胀得太厉害。
3.3 Manifest:数据文件的索引
Manifest文件,说白了就是数据文件的清单。每个Manifest文件里记录了一组数据文件的元信息,包括:
- 文件路径
- 文件格式
- 分区信息
- 列统计信息(最小值、最大值、空值数量等)
- 文件大小、行数
你可能会问:为什么不直接把所有文件信息放在一个文件里?
原因很简单——可扩展性。一个表可能有几万甚至几十万个数据文件,把所有信息塞到一个文件里,更新一次就要重写整个文件,代价太大了。
Iceberg的做法是:把Manifest文件组织成树形结构。根节点是Manifest List,它指向一组Manifest文件,每个Manifest文件再指向具体的数据文件。这样,每次更新只需要修改受影响的Manifest文件,而不是全量重写。
避坑指南:我曾经遇到过一个场景,某个表每天有上千个分区,每个分区几十个文件。默认的Manifest合并策略导致Manifest文件数量爆炸,查询性能急剧下降。后来我调整了write.manifest.target-size-bytes和write.manifest.merge-enabled参数,才把问题解决。如果你的表分区特别多,一定要关注Manifest的合并策略。
3.4 Metadata层:一切的中心
Metadata层是Iceberg的"大脑"。它由三层文件组成:
| 层级 | 文件 | 作用 |
|---|---|---|
| 第0层 | version-hint.text |
指向当前最新的Metadata文件版本号 |
| 第1层 | v[n].metadata.json |
表的全局元数据:Schema、分区策略、Snapshot列表、Manifest List位置 |
| 第2层 | [uuid]-m[n].avro |
Manifest List:指向一组Manifest文件 |
| 第3层 | [uuid]-[uuid].avro |
Manifest文件:指向具体的数据文件 |
每次对表进行写操作,Iceberg都会:
- 生成新的数据文件
- 生成新的Manifest文件(指向新数据文件)
- 生成新的Manifest List(指向新的Manifest文件)
- 生成新的Metadata文件(包含新的Snapshot和Manifest List位置)
- 原子性地更新
version-hint.text,指向新的Metadata文件
整个过程是原子性的。如果第5步失败,整个写操作就像没发生过一样——因为version-hint.text还指向旧的Metadata文件。这就是Iceberg实现ACID事务的底层原理。
3.5 整体架构图
下面这张图展示了这四个概念之间的关系:
3.6 总结一下
这四个概念的关系,我可以用一句话概括:
Table Format定义了"游戏规则",Metadata层是"裁判",Snapshot是"比赛录像",Manifest是"球员名单"。
每次数据写入,就像一场新的比赛——生成新的录像(Snapshot)、更新球员名单(Manifest)、裁判记录在案(Metadata)。而Table Format确保所有球队(Spark、Flink、Trino)都按同一套规则打球。
理解了这些,你就掌握了Iceberg的"内功心法"。后面讲Kafka集成的时候,你会发现这些概念会反复出现——尤其是Snapshot和Manifest,它们直接决定了数据入湖的一致性和性能。