Skip to content

Apache Iceberg

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

Apache Iceberg


背景

数据存储架构的演进,本质上是在「成本与开放性」和「事务与管理能力」之间寻找平衡。

  • 数据库(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)  实际数据

Iceberg 表规范结构

层级 文件类型 职责
Catalog 外部服务 维护 表名 → 当前 Metadata 文件 的映射,是定位表的入口
Metadata File JSON 记录表的 Schema、分区规格(partition spec)、快照列表及当前快照指针
Manifest List Avro 列出某一快照所引用的全部 Manifest 文件及其分区范围摘要
Manifest File Avro 列出一批数据文件路径,并记录每列的 min/maxnull_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 保证,确保多任务并发读写同一张表的安全性。其提交流程如下:

  1. 写任务基于当前快照生成新的数据文件与元数据;
  2. 提交时,通过 Catalog 的原子操作(Compare-And-Swap, CAS)将当前快照指针由旧快照替换为新快照;
  3. 若期间已有其他提交导致指针变更,本次提交将失败并自动重试。

由此提供的保证

  • 快照隔离(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 LakeApache 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)对同一份数据的混合读写