☰
Apache Iceberg 元数据术语详解:Snapshot、Manifest List、Manifest File 与 Snapshot Log
2026/10/14 21:58:03 网站建设 项目流程
  • 数据湖
  • 湖仓一体
  • 大数据

【免费下载链接】iceberg

Apache Iceberg

项目地址:https://gitcode.com/gh_mirrors/iceberg5/iceberg
点击查看免费下载

本文基于 Apache Iceberg 官方术语表(site/docs/terms.md),逐一解释 Iceberg 元数据体系中的六个核心概念:Snapshot(快照)、Manifest list(清单列表)、Manifest file(清单文件)、Partition spec(分区规范)、Partition tuple(分区元组)与Snapshot log(快照日志),并对照格式规范 format/spec.md 与 Java 参考实现源码,说明这些概念在表元数据 JSON 和 manifest 文件中的具体存储位置与作用机制。读完本文后,你将能够看懂 Iceberg 元数据层级的完整结构,理解 scan planning(扫描规划)如何利用这些元数据做文件裁剪,并能在表元数据 JSON 中定位每个术语对应的字段。

上图为 Iceberg 的元数据层级结构(源自 site/docs/assets/images/iceberg-metadata.png):Catalog 中的表条目持有指向当前 metadata file 的指针;metadata file 中内嵌所有有效快照(s0、s1);每个快照指向一个 manifest list;manifest list 再指向若干 manifest file;manifest file 最终列出快照中包含的全部数据文件。本文的六个术语恰好覆盖了这条链路上每一层的关键角色。

Snapshot:某一时刻的表状态

官方定义:一个 snapshot(快照)是表在某个时刻的状态。每个快照列出构成该时刻表内容的全部数据文件;这些数据文件分布在多个 manifest file 中,而某个快照的全部 manifest 则被记录在一个唯一的 manifest list 文件中。

规范对快照字段的完整定义见 format/spec.md 的### Snapshots章节:

版本字段说明
v1/v2 必填snapshot-id唯一 long ID
可选parent-snapshot-id父快照 ID,无父快照时省略
v2 必填sequence-number单调递增的 long,标记表变更顺序
v1/v2 必填timestamp-ms快照创建时间戳,用于垃圾回收与表检查
v2 必填manifest-list该快照的 manifest list 位置(v1 中可选,存在时manifests必须省略)
v1 可选manifestsmanifest 文件位置列表(v2 已由 manifest-list 取代)
v2 必填summary字符串 map,总结快照变更,必含operation字段
可选schema-id快照创建时表的当前 schema ID

summary中的operation字段取值为append(仅新增数据文件)、replace(增删文件但表数据逻辑不变,如 compaction、格式转换)、overwrite(逻辑覆盖)、delete(删除数据),快照过期等操作会依据该字段跳过特定快照。

在 Java 参考实现中,快照由接口 api/src/main/java/org/apache/iceberg/Snapshot.java 描述,关键访问器与上表一一对应:sequenceNumber()、snapshotId()、parentId()、timestampMillis()、summary()、manifestListLocation()。可以确认manifestListLocation()正是术语表中"快照的 manifests 列在单一 manifest list 文件中"这一说法在代码层面的落点——v2 表读取快照时即从此位置出发读取 manifest 清单。

规范还解释了为什么一个快照的数据文件可以存放在多个 manifest 中:

  • 快速追加(fast append):追加数据时只需写入一个全新 manifest,而不是改写已有 manifest,最小化写入量;
  • 多分区规范共存:一个 manifest 只对应单一 partition spec,表的分区配置演化后,旧文件留在旧 manifest、新文件写入新 manifest,而查询无需变化,因为分区过滤器始终由数据列谓词推导;
  • 并行规划:大表拆分到多个 manifest,实现可以并行规划作业、降低单个 manifest 重写成本。

Manifest list:快照的 manifest 索引

官方定义:manifest list 是列出构成某个表快照的所有 manifest 的元数据文件。清单列表中的每个 manifest 都附带描述其内容的信息(如分区值范围),用于加速元数据操作。

规范#### Manifest Lists章节补充了关键机制:每次提交快照的尝试都会写一份新的 manifest list(因为产生新快照时 manifest 列表必然变化);写入时,清单中所有新 manifest 会记录该快照的(乐观)序号。此外,manifest list 携带概要元数据,可在规划表扫描时避免读取快照内的全部 manifest——包括新增/保留/删除的文件数,以及写入该 manifest 所用的每个分区字段的取值概要(field_summary:contains_null、contains_nan、lower_bound、upper_bound)。

docs/docs/performance.md 的 "Metadata filtering" 一节从使用角度印证了这一点:manifest list 充当 manifest 文件的索引,Iceberg 先用清单中记录的分区值范围过滤 manifest,再读取被选中的 manifest 获取数据文件,这使得单节点规划多 PB 级大表成为可能。

Manifest file:快照数据文件的清单

官方定义:manifest file 是列出构成快照的一子集数据文件的元数据文件。清单中的每个数据文件都随附一个 partition tuple(分区元组)、列级统计信息,以及用于在 scan planning 中裁剪 split 的概要信息。

规范### Manifests章节给出了完整细节,值得完整了解:

  • manifest 是不可变的 Avro 文件,本身是合法的 Iceberg 数据文件(使用合法的 Iceberg 格式、schema 和列投影);
  • 一个 manifest 只存放数据文件或只存放删除文件(二者不能混放,因为含删除文件的 manifest 在作业规划时会被优先扫描),content属性记录"data"或"deletes";
  • 一个 manifest 只对应单一 partition spec,spec 变更时旧文件保留在旧 manifest,新文件写入新 manifest——因为 manifest 文件的 schema 由分区 spec 决定;
  • manifest 的 Avro key-value metadata 中必须写入schema/schema-id、partition-spec/partition-spec-id、format-version,v2 还必填content。

manifest_entry与data_file的核心字段(节选自规范字段表):

字段类型说明
0 statusint0: EXISTING、1: ADDED、2: DELETED,跟踪文件增删
1 snapshot_idlong文件被加入(或删除)的快照 ID,null 时从 manifest list 继承
3 sequence_number/4 file_sequence_number(v2)long数据序号 / 文件序号,用于规划删除文件适用于哪些数据文件
2 data_filestruct文件路径、partition tuple、指标(metrics)等

data_file中的列级统计(column_sizes、value_counts、null_value_counts、nan_value_counts、distinct_counts、lower_bounds、upper_bounds)正是术语表中"列级统计"的具体所指,它们支持在规划阶段按列过滤文件;split_offsets(如 Parquet 的所有 row group 偏移)则直接服务于 split 裁剪。规范#### Scan Planning描述了规划流程:先按 manifest 中的分区值范围与文件计数跳过无匹配文件的 manifest,再把扫描谓词通过**包含式投影(inclusive projection)**转换为分区谓词,结合列级上下界选出实际要读的文件——docs/docs/performance.md 的 "Data filtering" 一节以同样的口径描述了这条链路。

Partition spec:如何对数据分区

官方定义:partition spec 描述表中的数据如何分区。一个 spec 由"源列 + 转换(transform)"的列表组成,transform 从源值生成分区值——例如date(ts)从名为ts的时间戳列生成日期分区值。

规范### Partitioning章节说明,每个分区字段由四部分组成:源列 ID、分区字段 ID(在 spec 内唯一,v2 表元数据中全局唯一)、transform、分区名。transform 支持identity、bucket[N](Murmur3 x86 32 位哈希取模)、truncate[W]、year/month/day/hour、void,且所有 transform 对 null 输入必须返回 null。

规范中一段关键表述解释了 spec 的设计意图:分区 spec 捕获"从表数据到分区值的转换",这既用于转换数据值,也用于把列谓词转换为分区谓词,从而将逻辑查询与物理存储解耦——分区布局可以随数据量变化而演化,正确的分区过滤器始终从列谓词推导得出,用户无需同时提供逻辑谓词和分区谓词。docs/docs/partitioning.md 的 "Iceberg's hidden partitioning" 一节正是这一思想的展开:Iceberg 负责把event_time转成分区值(如date(event_time)),生产者与消费者都不需要看见显式的分区列,工作查询不再绑定表的物理布局。

在源码中,分区规范由 api/src/main/java/org/apache/iceberg/PartitionSpec.java 建模,specId()返回规范 ID,与 manifest list 中partition_spec_id字段(规范要求该 ID 必须列于表元数据的partition-specs中)对应。

Partition tuple:随每个数据文件存储的分区值

官方定义:partition tuple(分区元组)是随每个数据文件存储的分区数据元组(tuple/struct)。数据文件中所有行的分区元组取值相同;元组由分区 spec 对行数据转换而来。术语表还特别对比:Iceberg 原样(unmodified)存储分区值,不像 Hive 表那样在文件系统路径与键之间来回做字符串转换。

规范### Partitioning开头一段明确了其用途:数据文件在 manifest 中随一个分区值 tuple 存储,扫描时用它过滤不可能包含匹配记录的文件;同一 manifest 中数据文件可来自任意分区,只要分区 spec 相同。同时规范指出partitionstruct 的 schema 由写入该 manifest 的分区 spec 推导,v2 中 partition struct 的字段 ID 必须与分区 spec 的分区字段 ID 一致——这解释了为什么分区演化时必须复用既有字段 ID:分区字段 ID 会作为 manifest 文件中 partition tuple 的字段 ID 被持久化。

docs/docs/partitioning.md 的 "Problems with Hive partitioning" 一节列出了 Hive 式显式分区的三类问题(写入方需手工给出分区值且无法校验、查询必须手写额外过滤、查询与物理布局耦合),而术语表中"Iceberg 原样存储分区值"这一句正是对上述问题的回应:值不做字符串化转换,也就不存在2018-12-01与20181201这类静默错误的格式陷阱。在 Java 实现中,分区元组由 core/src/main/java/org/apache/iceberg/PartitionData.java 承载,即 manifest 条目中102 partition字段的运行时表示。

Snapshot log:当前快照的历史日志

官方定义:snapshot log(history table)是记录表当前快照如何随时间变化的元数据日志,内容是一组"时间戳 + 快照 ID"对:当前快照在何时变更、变更成了哪个快照 ID。它作为snapshot-log字段存储在表元数据中(规范位置见 format/spec.md 的### Table Metadata and Snapshots章节)。

表元数据 JSON 中相关字段长这样(节选自规范附录 C):

"current-snapshot-id": 3051729675574597004, "snapshots": [ { "snapshot-id": 3051729675574597004, "timestamp-ms": 1515100955770, "summary": { "operation": "append" }, "manifest-list": "s3://b/wh/.../s1.avro", "schema-id": 0 } ], "snapshot-log": [ { "snapshot-id": 3051729675574597004, "timestamp-ms": 1515100955770 } ]

注意规范中一句关键说明:"Snapshots are not serialized separately. Instead, they are stored in the table metadata JSON."——快照不是独立文件,而是直接内嵌在表元数据 JSON 中;snapshot-log只是当前快照(main 分支)变更历史的精简日志,与完整快照列表snapshots并存。

参考实现中这组概念的落点非常集中:

  • core/src/main/java/org/apache/iceberg/TableMetadata.java 持有private final List<HistoryEntry> snapshotLog字段,snapshotLog()方法对外暴露;
  • 日志条目类型是 api/src/main/java/org/apache/iceberg/HistoryEntry.java 接口,仅两个方法:timestampMillis()(变更时间戳)与snapshotId()(变更后的当前快照 ID),与术语表"时间戳和 ID 的对"定义完全一致;
  • TableMetadata内部 Builder 中,setRef针对main分支时会执行currentSnapshotId = ref.snapshotId()并向snapshotLog追加一条SnapshotLogEntry;反之removeRef("main")时snapshotLog.clear()——即日志只随 main 分支(当前快照)推进,这与"当前快照如何随时间变化"的语义吻合;
  • JSON 序列化侧,core/src/main/java/org/apache/iceberg/TableMetadataParser.java 定义了常量SNAPSHOT_LOG = "snapshot-log",读写表元数据 JSON 时遍历metadata.snapshotLog()序列化为上述条目列表。

小结:六个术语在元数据链路中的位置

术语存储位置核心作用
Snapshot表元数据 JSON 的snapshots列表表在某一时刻的完整状态,指向一个 manifest list
Manifest list快照manifest-list指向的 Avro 文件manifest 的索引,携带分区值范围与文件计数,加速规划
Manifest filemanifest list 列出的 Avro 文件列出快照的一部分数据/删除文件及其分区元组、列级统计
Partition spec表元数据 JSON 的partition-specs定义"源列 + transform",驱动分区谓词推导,支持演化
Partition tuplemanifest 中每个 data file 的partition字段文件内所有行共享的分区值,原样存储,用于扫描过滤
Snapshot log表元数据 JSON 的snapshot-log当前快照的 (timestamp-ms, snapshot-id) 变更历史

这些概念共同构成了 Iceberg 单节点快速规划的基础:从元数据指针出发,经 manifest list 的范围概要跳过无用的 manifest,再经 manifest 中的分区元组与列级统计裁剪数据文件。进一步的字段级细节(含 v1/v2 差异、序列号继承规则)可继续查阅 format/spec.md 的### Manifests、### Snapshots、### Table Metadata and Snapshots章节,以及 docs/docs/performance.md 对 scan planning 过程的完整描述。

  • 数据湖
  • 湖仓一体
  • 大数据

【免费下载链接】iceberg

Apache Iceberg

项目地址:https://gitcode.com/gh_mirrors/iceberg5/iceberg
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询