Apache Iceberg
Apache Iceberg 是一种为超大规模分析数据集设计的开放表格式(Open Table Format),最初由 Netflix 开发,用于解决其在 PB 级数据上遇到的诸多痛点,现已成为 Apache 顶级项目。

背景
数据存储架构的演进,本质上是在「成本与开放性」和「事务与管理能力」之间寻找平衡。
-
数据库(Database)
面向在线事务处理(OLTP),提供强一致性与完整事务语义,但受限于行式存储与单机/主从架构,难以支撑大规模分析负载。
-
数据仓库(Data Warehouse)
面向在线分析处理(OLAP),具备优异的查询性能与事务保证,但存储格式封闭、计算与存储耦合、成本高,且通常绑定单一引擎。
-
数据湖(Data Lake)
将原始文件(Parquet、ORC 等)直接存放于对象存储或 HDFS,具备低成本、格式开放、可容纳多模态数据的优势,但缺乏事务保证与统一的元数据管理。
-
湖仓一体(Lakehouse)
在数据湖的低成本与开放性之上,叠加数据仓库的事务、Schema 管理与查询优化能力。Iceberg 即是实现这一架构的核心组件之一。
Hive 表的局限
在 Iceberg 出现之前,业界普遍采用 Hive 表管理数据湖:以目录(directory)作为分区单位,元数据存放于 Hive Metastore。该方案在数据规模增长后暴露出一系列根本性缺陷:
| 缺陷 | 成因与影响 |
|---|---|
| 缺乏事务隔离 | 写入以目录为粒度、非原子提交;任务中途失败会残留不完整数据,并发读写可能读到中间状态。 |
| Schema 变更高风险 | 以列名/列序定位字段,新增、重命名或调整列序常需重写历史数据,否则导致数据错位。 |
| 分区方案固化 | 分区策略在建表时确定后无法变更,调整分区粒度只能重建整张表并迁移数据。 |
| 文件枚举开销大 | 查询前需对分区目录执行 LIST 操作,文件数量庞大时元数据访问成为性能瓶颈。 |
| 不支持快照与回滚 | 无版本管理机制,既无法查询历史状态,也无法在误操作后恢复。 |
Iceberg 通过重新设计表的元数据组织方式,系统性地解决了上述问题。
核心概念
Iceberg 既不是数据库,也不是存储引擎或查询引擎。
它是一套开放规范,定义了一张「表」由哪些文件构成、这些文件如何组织,以及计算引擎应如何对其进行读写。Iceberg 位于计算引擎与底层存储之间,作为逻辑表与物理文件之间的抽象层:
graph LR
A[查询引擎<br/>Spark / Flink / Trino] -->|读写| B[Iceberg 表格式<br/>元数据规范]
B -->|引用| C[数据文件<br/>Parquet / ORC / Avro]
C -->|存储于| D[对象存储 / HDFS<br/>S3 / OSS / GCS]
这一设计带来两个关键价值:
- 引擎中立:同一份数据可被 Spark、Flink、Trino 等多种引擎一致地读写,避免数据孤岛与重复拷贝。
- 存储计算解耦:数据持久化于廉价对象存储,计算资源按需弹性伸缩。
核心架构
Iceberg 的核心思想是将元数据(描述数据的数据)与实际数据彻底分离,并以分层结构组织元数据,使得引擎在访问数据前即可完成大量裁剪。
Catalog(入口:表名 → 当前版本指针)
│
└── Metadata File(.json) 表级元数据:Schema、分区规格、快照列表
│
└── Manifest List(.avro) 某一快照所引用的 Manifest 集合
│
└── Manifest File(.avro) 数据文件清单 + 列级统计信息
│
└── Data Files(.parquet / .orc / .avro) 实际数据

| 层级 | 文件类型 | 职责 |
|---|---|---|
| Catalog | 外部服务 | 维护 表名 → 当前 Metadata 文件 的映射,是定位表的入口 |
| Metadata File | JSON | 记录表的 Schema、分区规格(partition spec)、快照列表及当前快照指针 |
| Manifest List | Avro | 列出某一快照所引用的全部 Manifest 文件及其分区范围摘要 |
| Manifest File | Avro | 列出一批数据文件路径,并记录每列的 min/max、null_count 等统计信息 |
| Data File | Parquet/ORC/Avro | 存放实际数据 |
分层的意义:基于统计信息的查询裁剪
Manifest 中保存了每个数据文件的列级统计信息(最大值、最小值、空值数量等)。
执行 WHERE age > 30 时,引擎先读取元数据:若某数据文件 age 列的最大值为 25,即可在不读取该文件的前提下将其整体排除。
这种依据统计信息提前剔除无关文件的机制称为 Data Skipping(数据跳过),是 Iceberg 在大规模数据下保持高查询性能的核心来源——数据规模越大,裁剪收益越显著。
核心特性
Snapshot & Time Travel
Iceberg 采用多版本机制:每一次写操作(INSERT / UPDATE / DELETE)都会生成一个新的快照(Snapshot),历史快照默认予以保留。
snapshot-1 (2024-01-01 提交)
snapshot-2 (2024-01-02 提交)
snapshot-3 (2024-01-03 提交) ← current(当前快照)
其语义类似于版本控制系统的提交历史——表的每个状态都被完整记录。由此衍生出两项能力:
-
时间旅行(Time Travel)
查询表在任意历史时刻的状态,适用于报表复现、数据审计与对账等场景。
-- 按时间点查询 SELECT * FROM orders FOR SYSTEM_TIME AS OF '2024-01-01 00:00:00'; -- 按快照 ID 查询 SELECT * FROM orders FOR SYSTEM_VERSION AS OF 8765432198765; -
回滚(Rollback)
将表状态恢复至指定历史快照,用于误操作或异常写入后的快速恢复。
CALL catalog.system.rollback_to_snapshot( 'db.orders', 8765432198765 );
快照需定期清理
快照的持续累积会增加存储开销并拖慢元数据访问。生产环境需通过 expire snapshots(过期快照清理) 定期回收旧版本及其关联文件。
ACID 事务
Iceberg 基于乐观并发控制(Optimistic Concurrency Control, OCC)提供 ACID 保证,确保多任务并发读写同一张表的安全性。其提交流程如下:
- 写任务基于当前快照生成新的数据文件与元数据;
- 提交时,通过 Catalog 的原子操作(Compare-And-Swap, CAS)将当前快照指针由旧快照替换为新快照;
- 若期间已有其他提交导致指针变更,本次提交将失败并自动重试。
由此提供的保证
- 快照隔离(Snapshot Isolation):未提交的写入对读者不可见,读者始终看到一致的快照视图。
- 原子性:写入要么完整提交、要么完全不生效,失败任务不会污染表。
- 行级变更:支持
UPDATE/DELETE/MERGE INTO操作单行数据,无需重写整个分区。
Schema Evolution
Iceberg 以唯一的列标识符(field-id)而非列名或列序来追踪字段,因此 Schema 变更是安全、即时且无需重写历史数据的操作。
| 操作 | 是否安全 | 说明 |
|---|---|---|
| 新增列(Add) | ✅ | 历史数据中该列读取为 NULL |
| 删除列(Drop) | ✅ | 仅作元数据标记,不触及数据文件 |
| 重命名列(Rename) | ✅ | field-id 不变,数据正常读取 |
| 调整列序(Reorder) | ✅ | 基于 field-id 定位,与物理顺序无关 |
| 类型提升(如 int→long) | ✅ | 支持兼容方向的安全类型转换 |
ALTER TABLE orders ADD COLUMN discount double; -- 新增列
ALTER TABLE orders RENAME COLUMN amt TO amount; -- 重命名列
Partition Evolution
分区(Partition)指按特定规则(如按日期、地区)对数据进行物理分组,使查询得以仅扫描相关分组以提升性能。
传统 Hive 表的分区规则在建表后即固定,调整分区方案须重建整表。Iceberg 则允许直接变更分区规格,且无需重写任何历史数据——这是其相较于其他表格式的显著优势。
-- 初始:按天分区
ALTER TABLE logs ADD PARTITION FIELD days(event_time);
-- 数据规模增长后改为按小时分区(历史数据保持不变)
ALTER TABLE logs ADD PARTITION FIELD hours(event_time);
演化后,历史数据沿用旧规格、新数据采用新规格,查询引擎自动处理混合分区规格的扫描,对用户透明。
Hidden Partitioning
在 Hive 中,对按 date 分区的表,查询必须显式书写 WHERE date = '2024-01-01' 方可命中分区,否则触发全表扫描;同时需手动维护额外的分区列。
Iceberg 在内部维护分区逻辑与原始列之间的映射关系。用户仅需基于原始列进行查询,引擎即可自动完成分区裁剪:
-- 建表时声明分区规格:按 event_time 的"天"分区
CREATE TABLE logs (id bigint, event_time timestamp, msg string)
PARTITIONED BY (days(event_time));
-- 查询直接使用原始列,引擎自动完成分区裁剪
SELECT * FROM logs
WHERE event_time BETWEEN '2024-01-01' AND '2024-01-02';
常用的分区转换函数(partition transforms):
| 函数 | 含义 | 示例 |
|---|---|---|
identity |
按原值分区 | identity(country) |
year / month / day / hour |
按时间粒度分区 | day(event_time) |
bucket(N, col) |
哈希分桶为 N 份 | bucket(16, user_id) |
truncate(W, col) |
按宽度截断分区 | truncate(10, amount) |
读取流程
将前述概念串联,一次查询读取 Iceberg 表的完整流程如下:
graph TD
A[1. 查询 Catalog<br/>定位当前 Metadata 文件] --> B[2. 读取 Metadata<br/>获取 Schema 与当前 Snapshot]
B --> C[3. 读取 Manifest List<br/>枚举该快照的 Manifest]
C --> D[4. 读取 Manifest<br/>结合谓词与列级统计<br/>裁剪无关数据文件]
D --> E[5. 仅扫描剩余 Data Files<br/>返回结果]
整个流程的核心在于逐层裁剪:在实际读取数据之前,引擎已通过元数据排除了绝大部分无关文件。这正是 Iceberg 在海量数据下仍保持高性能的根本原因。
底层文件格式
Iceberg 不绑定特定数据文件格式,可根据负载特征选择:
| 格式 | 类型 | 适用场景 |
|---|---|---|
| Parquet | 列式 | 默认推荐,分析型查询性能最优 |
| ORC | 列式 | 与 Hive 生态深度兼容 |
| Avro | 行式 | 适合流式写入及整行读取为主的场景 |
Catalog:表的访问入口
Catalog 负责维护 表名 → 当前 Metadata 文件路径 的映射,是引擎定位表的第一站。不同实现适配不同的部署环境:
| Catalog | 说明 |
|---|---|
| Hive Metastore | 复用 Hive 元数据服务,传统大数据平台中应用最广 |
| AWS Glue | AWS 托管元数据服务,适配 S3 数据湖 |
| REST Catalog | 基于标准 HTTP 接口,与具体实现解耦,是社区主推方向 |
| JDBC Catalog | 以关系型数据库(如 PostgreSQL)存储元数据,轻量易部署 |
| Nessie | 支持类 Git 的分支与合并语义,适用于数据多版本协作 |
与其他湖仓表格式的对比
Iceberg 的主要竞品为 Delta Lake 与 Apache Hudi,三者各有侧重:
| 特性 | Apache Iceberg | Delta Lake | Apache Hudi |
|---|---|---|---|
| 发起方 | Netflix | Databricks | Uber |
| 时间旅行 | ✅ | ✅ | ✅ |
| Schema 演化 | ✅ 完善 | ✅ | ✅ |
| 分区演化 | ✅ 独有 | ❌ | ❌ |
| 隐式分区 | ✅ | ❌ | ❌ |
| 引擎生态 | 开放(Spark/Flink/Trino/Hive 等) | 偏向 Spark/Databricks | Spark/Flink |
| 流式写入 | ✅(Flink) | ✅ | ✅ 优势场景 |
综合而言,Iceberg 在开放性、引擎中立性与分区演化能力上具有明显优势,已成为当前湖仓领域事实上的主流标准之一。
表维护
随着读写持续进行,Iceberg 表会累积元数据与小文件,需定期执行维护操作(通常通过 Spark 存储过程调用)以保障性能与控制存储开销:
| 维护操作 | 作用 |
|---|---|
| Compaction(小文件合并) | 将大量小文件合并为大文件,降低文件数量、提升扫描效率 |
| Expire Snapshots(过期快照清理) | 删除超过保留期的旧快照,回收存储并抑制元数据膨胀 |
| Remove Orphan Files(孤立文件清理) | 清理不被任何快照引用的无效文件 |
| Rewrite Manifests(清单重写) | 优化 Manifest 组织结构,提升元数据读取效率 |
适用场景
- 湖仓一体的核心存储层:在低成本对象存储之上获得数据仓库级的管理与查询能力
- 需要 ACID 事务保证的大规模数据仓库与数据湖
- Schema 或分区频繁演化的业务表
- 需要数据版本管理、审计与回滚的合规场景
- 多引擎(Spark / Trino / Flink)对同一份数据的混合读写