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都会:

  1. 生成新的数据文件
  2. 生成新的Manifest文件(指向新数据文件)
  3. 生成新的Manifest List(指向新的Manifest文件)
  4. 生成新的Metadata文件(包含新的Snapshot和Manifest List位置)
  5. 原子性地更新version-hint.text,指向新的Metadata文件

整个过程是原子性的。如果第5步失败,整个写操作就像没发生过一样——因为version-hint.text还指向旧的Metadata文件。这就是Iceberg实现ACID事务的底层原理。

3.5 整体架构图

下面这张图展示了这四个概念之间的关系:

Iceberg 核心概念架构图 version-hint.text v[n].metadata.json Schema / 分区策略 / Snapshot列表 Manifest List 指向一组 Manifest 文件 Manifest 文件 1 列统计 / 分区信息 Manifest 文件 2 列统计 / 分区信息 Manifest 文件 N 列统计 / 分区信息 数据文件 1.parquet 数据文件 2.parquet 数据文件 3.parquet 数据文件 N.parquet Snapshot = 元数据文件 + Manifest List + Manifest 文件 + 数据文件 每次写操作生成一个新 Snapshot,旧 Snapshot 保留用于时间旅行

3.6 总结一下

这四个概念的关系,我可以用一句话概括:

Table Format定义了"游戏规则",Metadata层是"裁判",Snapshot是"比赛录像",Manifest是"球员名单"。

每次数据写入,就像一场新的比赛——生成新的录像(Snapshot)、更新球员名单(Manifest)、裁判记录在案(Metadata)。而Table Format确保所有球队(Spark、Flink、Trino)都按同一套规则打球。

理解了这些,你就掌握了Iceberg的"内功心法"。后面讲Kafka集成的时候,你会发现这些概念会反复出现——尤其是Snapshot和Manifest,它们直接决定了数据入湖的一致性和性能。