Apache Iceberg:架构原理、读写机制、性能优化与生态

第一章 Apache Iceberg 简介

1.1 从数据仓库、数据湖到湖仓表格式

分析型数据平台通常需要同时解决六类问题:数据存在哪里、采用什么文件格式、哪些文件共同组成一张表、表的入口如何被发现、数据如何持续维护,以及由哪个计算引擎执行查询。传统数据仓库把其中大部分能力封装在同一套系统中,使用简单,但存储与计算往往紧密绑定。数据湖将数据放入 HDFS 或云对象存储,以开放文件格式降低成本,却把事务、一致性和表级管理问题留给了上层系统。

Iceberg 属于“表格式(Table Format)”。它位于 Parquet、ORC 等数据文件之上,通过一组标准化元数据描述一张表在某个时刻由哪些文件组成,并为多个计算引擎提供一致的表语义。它不是对象存储,也不是计算引擎;它解决的是开放数据湖缺失的表级事务、版本、演进和高效文件发现问题。

1.2 分析型数据平台的关键层次

层次

定义

典型实现或职责

底层存储

保存数据文件和元数据文件的物理介质。

HDFS、S3、GCS、OSS 等。

文件格式

定义单个文件内部的数据编码、压缩和列式组织方式。

Parquet、ORC、Avro;结构化、半结构化或二进制数据也可使用其他格式保存。

表格式

定义哪些文件属于一张表,以及 Schema、分区、统计信息、快照和提交规则。

Iceberg、Delta Lake、Hudi。

Catalog

维护表名到当前表元数据入口的映射,并参与提交协调。

REST Catalog、Hive Metastore、Glue、JDBC、Nessie 等。

维护与优化

按表格式规则写入、合并、重排文件,维护统计信息并清理过期数据。

Compaction、Rewrite Data Files、Expire Snapshots、Remove Orphan Files。

计算引擎

解析 SQL 或计算任务,读取表元数据并扫描数据文件。

Spark、Flink、Trino、Presto、Hive 等。

1.3 传统数据仓库的局限

数据类型与开放性受限。传统数仓主要围绕结构化表和 SQL 分析构建。现代数仓已经能够处理部分半结构化数据,但对图像、音频、视频、模型样本等非结构化数据的直接管理通常不如数据湖灵活;数据也常以厂商私有格式保存,跨引擎访问和迁移成本较高。

存储成本通常更高。传统数仓需要使用面向数据库工作负载设计的存储、索引、副本和高可用机制,并由数据库服务持续管理。相比本地 Hadoop 集群或云对象存储,单位容量价格通常更高;当原始数据、历史数据和冷数据长期增长时,成本差异会被进一步放大。

存储和计算难以独立扩展。在共享无架构或存算一体架构中,存储容量和计算能力由同一组节点提供。只需要增加存储时,往往也必须购买计算资源;只需要临时增加算力时,也可能受到本地数据分布和节点容量约束。现代云数仓已经在一定程度上实现存算分离,但数据和计算仍可能被绑定在同一产品体系中。

1.4 数据湖的优势与不足

数据湖把数据保存在低成本、可横向扩展的存储中,并使用开放文件格式,使多个计算框架能够访问同一份数据。它适合保存原始数据、结构化数据、半结构化数据和非结构化数据,也便于把存储生命周期与计算资源生命周期分开。

但“对象存储 + 一批文件”本身并不是一张可靠的数据库表。没有统一表格式时,系统需要自行组合 Catalog、并发控制、文件发现、统计信息和清理机制;多文件更新难以保证原子性,读者可能看到中间状态;大量目录和文件枚举会增加延迟;Schema、分区和历史版本也缺乏一致的演进规则。

1.5 Hive 表格式为什么不够

Hive 把表主要组织为目录和分区,并依赖文件系统目录结构表达分区关系。这种模型适合批量追加和分区覆盖,但随着并发写入、流批一体和行级更新需求增加,会出现以下瓶颈:

  • 操作粒度偏粗:更新通常围绕目录或分区展开,难以精确表达一组文件级变化。

  • 多分区提交缺少统一原子性:多个目录或分区的修改难以作为一次表级事务同时生效。

  • 并发写入协调困难:多个写入者可能覆盖彼此结果,或让读者观察到部分完成的状态。

  • 文件发现成本高:查询规划依赖递归列目录,在分区和文件数量很大时,List 操作会成为瓶颈。

  • 分区信息暴露给查询:用户和优化器需要理解物理分区列,分区策略变化还可能迫使查询改写。

  • 统计信息不完整或更新滞后:优化器难以在规划阶段准确裁剪无关文件。

现代数据湖表格式把表定义为一棵版本化的元数据树,而不是一个需要现场枚举的目录。一次提交生成新的元数据版本,并通过原子切换当前元数据指针使变更生效,从而为 ACID、一致性读取、并发控制和高效裁剪建立基础。

1.6 Iceberg 的定位

Apache Iceberg 是面向大规模分析数据集的开放表格式。它把“当前表由哪些数据文件和删除文件组成”记录在版本化元数据中,并使用 Snapshot 表示一次成功提交后的表状态。查询默认读取当前 Snapshot,也可以按 Snapshot ID、时间戳、Branch 或 Tag 读取指定版本。

Iceberg 只规定表的元数据结构、提交语义和读写规则。底层文件仍保存在 HDFS 或对象存储中,上层由 Spark、Flink、Trino 等引擎执行读写。因此,同一张表可以在不复制数据的前提下被多个兼容引擎共享。

1.7 Iceberg 的核心能力

能力

作用

ACID 与并发安全

通过 Snapshot、原子元数据切换和乐观并发控制,使多文件修改作为一个整体提交,并检测并发冲突。

隐藏分区

查询使用业务列,Iceberg 根据分区变换自动完成分区裁剪,减少用户对物理分区列的依赖。

Partition Evolution

允许新增或修改分区策略,新旧数据可以使用不同 Partition Spec,无需立即重写全部历史数据。

Schema Evolution

通过稳定字段 ID 支持增加、删除、重命名和安全调整字段,避免仅按列位置或名称匹配造成错误。

行级变更

支持 Copy-on-Write 和 Merge-on-Read 两类实现路径,用于 UPDATE、DELETE 和 MERGE。

Time Travel 与回滚

读取历史 Snapshot,或把当前表指针切回已有版本;长期固定版本可使用 Tag,临时版本由 Snapshot 保留策略管理。

元数据裁剪

利用 Manifest 中的分区信息、列统计和文件级指标,在打开数据文件之前排除无关文件。

1.8 本章小结

Iceberg 的价值不是提供一种新的列式文件,而是为开放存储补齐可靠的表语义。它在低成本对象存储与多种计算引擎之间增加一层标准化、可版本化的元数据,使数据湖具备事务、一致性读取、演进、行级变更和查询裁剪能力。下一章将进一步拆解 Iceberg 的 Catalog、Metadata 和 Data 三层架构。

第二章 Apache Iceberg 的架构

2.1 三层架构总览

一张 Iceberg 表可以从逻辑上划分为 Catalog、Metadata 和 Data 三层。Catalog 保存表名到当前元数据文件的入口;Metadata 层以 Snapshot 和 Manifest 组织、索引文件;Data 层保存实际数据文件与删除文件。一次提交通常先写数据和新元数据,最后原子更新 Catalog 中的当前元数据指针。

从查询入口向下看,核心寻址链路是:

Catalog → 当前 Table Metadata → 当前 Snapshot → Manifest List → Manifest File → Data File / Delete File

这个多层索引的目的,是让查询引擎不必递归扫描存储目录,而是逐层读取规模更小、信息更集中的元数据,并根据分区值和列统计尽早裁剪无关文件。

2.2 Catalog 层:定位当前表版本

Catalog 管理命名空间和表,并维护表名到当前 Table Metadata 文件位置的映射。常见实现包括 REST Catalog、Hive Metastore、Hadoop Catalog、JDBC Catalog、AWS Glue 和 Nessie。Catalog 不保存全部表数据,它保存的是进入 Iceberg 元数据树所需的权威入口。

写入提交的最后一步通常是把 Catalog 中的元数据指针从旧版本原子切换到新版本。Iceberg 使用乐观并发控制:写入者基于某个旧版本准备变更,提交时检查表是否仍处于预期状态;如果其他写入者已经先提交,则根据操作类型重试或报告冲突。只有指针切换成功,新 Snapshot 才对后续读者可见。

2.3 Metadata 层:版本与多层文件索引

Metadata 层描述表的逻辑状态,包括 Schema、Partition Spec、Sort Order、属性、Snapshot 历史、分支和标签等信息。它不是单个固定文件,而是一组由上到下逐层引用的元数据文件。

元数据对象

定义

主要功能

Table Metadata

某一版完整表定义的 JSON 文件。

记录当前 Snapshot、Snapshot 历史、Schema、分区规则、排序规则、属性、统计文件引用及分支和标签。

Snapshot

一次成功提交后形成的不可变表状态。

标识一个可读取版本,并引用该版本使用的 Manifest List;多个 Snapshot 可以共享未变化的下层文件。

Manifest List

某个 Snapshot 使用的 Manifest File 清单。

记录 Manifest 的路径、分区范围、文件数量以及新增、保留、删除文件计数,用于先裁剪 Manifest。

Manifest File

跟踪一组 Data File 或 Delete File 的 Avro 元数据文件。

保存文件路径、分区值、记录数、列级上下界、空值计数等指标,用于文件级裁剪。

Puffin Statistics

与表元数据关联的可扩展统计信息文件。

承载 NDV、Theta Sketch 等不适合直接放入 Manifest 的统计 blob,帮助支持它的查询引擎进行成本估算和优化。

多层元数据索引是指通过 Table Metadata、Snapshot、Manifest List 和 Manifest File 逐层缩小候选数据范围的索引体系。上层负责版本选择和粗粒度筛选,下层保存更细的文件级分区与列统计;最终只有无法被排除的 Data File 和 Delete File 会进入扫描计划。

这些对象大多是不可变文件。创建新 Snapshot 时,Iceberg 只新增或重写受影响的元数据,并复用没有变化的 Manifest 和数据文件。因此,Snapshot 并不等于复制整张表;额外成本主要来自新写入的数据、被重写的文件以及少量新增元数据。只有在旧 Snapshot 过期且不再被 Branch 或 Tag 引用后,相应的无引用文件才可以安全清理。

2.4 Data 层:数据文件与删除文件

Data File。保存表中的实际记录。Iceberg 常用 Parquet,也支持 ORC 和 Avro。文件格式负责单个文件内部的数据编码;Iceberg 负责记录这些文件是否属于某个 Snapshot,以及如何根据分区和统计信息找到它们。

Delete File。在 Merge-on-Read 模式下记录逻辑删除信息,读取时与 Data File 合并。Position Delete 通过数据文件路径和行位置标记被删除的记录;Equality Delete 根据一个或多个字段值匹配需要删除的记录。Iceberg V3 还引入了 Deletion Vector 等更紧凑的删除表示,实际可用性取决于引擎和版本支持。

Data File 和 Delete File 同样是不可变对象。UPDATE、DELETE、MERGE 或 Compaction 不会原地修改文件,而是生成新文件,并在新 Snapshot 的元数据中增加或移除文件引用。这是 Iceberg 实现快照隔离、并发安全和历史读取的基础。

2.5 查询如何穿过三层架构

  1. 查询引擎通过 Catalog 取得当前 Table Metadata;如果指定 Snapshot、时间戳、Branch 或 Tag,则先解析目标版本。

  2. 从目标 Snapshot 找到 Manifest List,并使用分区范围和计数信息排除无关 Manifest。

  3. 读取剩余 Manifest File,利用分区值、列上下界、空值计数等统计信息裁剪 Data File 和 Delete File。

  4. 生成扫描任务,读取候选数据文件;存在删除文件时,按 Copy-on-Write 或 Merge-on-Read 的表状态处理。

因此,Iceberg 的查询规划成本主要取决于需要读取的元数据规模和裁剪效果,而不是底层目录中总共有多少文件。第三章将进一步展开查询生命周期和各层的具体操作路径。

2.6 写入如何形成新版本

  1. 写入任务生成新的 Data File,或者生成用于行级变更的 Data File 与 Delete File。

  2. 写入者生成或复用 Manifest File,并创建属于新 Snapshot 的 Manifest List。

  3. 生成新的 Table Metadata,将新 Snapshot 设为当前版本。

  4. 通过 Catalog 原子提交新的元数据指针;成功后新版本可见,失败则按冲突规则重试或终止。

文件合并也遵循同一模型:Compaction 在新 Snapshot 中用较少的新文件替换一批旧文件。历史 Snapshot 仍可继续引用旧文件,所以合并不会破坏历史查询;旧文件要等所有受保护引用消失并完成过期与清理后,才会被物理删除。

2.7 各层职责与故障边界

层次

负责什么

不负责什么

常见风险

Catalog

表发现、当前元数据入口和提交协调。

不直接保存数据记录,也不执行查询。

指针更新冲突、权限或服务可用性问题。

Metadata

版本、Schema、分区、统计信息和文件索引。

不承担数据文件内部编码。

Snapshot、Manifest 过多导致规划开销增加。

Data

保存实际记录和逻辑删除信息。

自身不表达完整表状态。

小文件、删除文件堆积、数据分布不合理。

计算引擎

执行读写、过滤、连接、聚合和维护任务。

不应绕过 Iceberg 元数据直接修改表目录。

不同引擎或连接器版本的能力不一致。

2.8 本章小结

Iceberg 的架构核心可以概括为“Catalog 提供入口、Metadata 定义版本并索引文件、Data 保存实际记录”。不可变文件、分层索引和原子元数据切换共同构成 ACID、Time Travel、并发写入与高效查询裁剪的技术基础。理解这条引用链后,第三章中的 Append、Update、Delete、Compaction 和 Rollback 生命周期就可以统一解释为:生成一组新文件,并提交一个新的表状态。

第三章 写入和读取查询的生命周期

3.1 生命周期总览

Iceberg 的读取与写入沿着同一棵元数据树反向运行。查询从 Catalog 出发,自上而下定位需要扫描的数据文件;写入和更新先生成底层文件,再自下而上构造新的 Snapshot,最后通过 Catalog 原子切换当前 Table Metadata 指针。

读取:Catalog → Table Metadata → Snapshot → Manifest List → Manifest File → Data File / Delete File

写入:Data File / Delete File → Manifest File → Manifest List → Snapshot → Table Metadata → Catalog 原子提交

操作

主要方向

新 Snapshot

原地修改旧文件

Query / Time Travel

自上而下

Append

自下而上提交

Update / Delete

先定位,再提交

Compaction

读旧文件,写新文件后提交

Rollback

元数据提交

更新表元数据版本

3.2 读取查询的生命周期

普通查询通过 Catalog 将表名解析为当前 metadata.json。Table Metadata 保存 Schema、Partition Spec 和 current-snapshot-id;查询引擎据此找到当前 Snapshot 及其 Manifest List。

1. Catalog:表名 → 当前 metadata.json
2. Table Metadata:读取 Schema、Partition Spec、current-snapshot-id
3. Snapshot:找到当前版本的 Manifest List
4. Manifest List:根据分区范围裁剪无关 Manifest
5. Manifest File:根据分区值、min/max 等裁剪无关文件
6. Data File / Delete File:生成扫描任务并应用删除信息
7. Parquet / ORC:利用 Row Group / Stripe 统计继续裁剪
8. 返回查询结果

不指定版本时使用 current-snapshot-id;Time Travel 则选择指定 Snapshot ID,或选择目标时间点当时生效的 Snapshot。后续读取路径不变。Time Travel 只是读取历史版本,不会改变当前 Snapshot。

-- 当前版本
SELECT * FROM prod.db.orders;

-- 指定 Snapshot
SELECT * FROM prod.db.orders VERSION AS OF 638741234567890;

-- 指定时间点
SELECT * FROM prod.db.orders TIMESTAMP AS OF '2026-07-25 10:30:00';

如果表使用 Merge-on-Read,查询引擎还要读取 Delete Manifest,并将 Position Delete、Equality Delete 或 Deletion Vector 应用到对应 Data File。逻辑结果可以理解为 Data Files 减去 Deleted Rows。

统计类 Puffin 是可选旁路。优化器可以从 Table Metadata 的 statistics 字段读取 NDV 等扩展统计;不支持它的引擎可以忽略,不影响正确性。Deletion Vector 虽然也可存储在 Puffin 中,但属于正确性路径,不能忽略。

3.3 Append 写入的生命周期

Append 不修改旧 Data File。Writer 先加载当前元数据,确认 Schema、Partition Spec 和基准 Snapshot,然后写入新数据文件,再逐层生成 Manifest、Manifest List、Snapshot 和新的 metadata.json,最后原子更新 Catalog。

1. 加载当前 metadata.json
2. 写入新的 Data Files
3. 为新文件创建 Manifest File
4. 创建 Manifest List,并复用未变化的旧 Manifest
5. 创建新 Snapshot
6. 写入新的 metadata.json
7. 以比较并交换方式原子更新 Catalog 指针
Snapshot 100
└── Manifest A → file-1、file-2

Snapshot 101
├── Manifest A → file-1、file-2  (复用)
└── Manifest B → file-3          (新增)

提交的可见性边界是 Catalog 指针切换。若 Data File 已写出,但 Catalog 提交失败,读者仍看到旧 Snapshot;未被任何 Snapshot 引用的文件属于 orphan file,可由维护任务清理。

3.4 Update 和 Delete 的生命周期

Copy-on-Write

Copy-on-Write 先定位受影响的数据文件,读取并应用更新或删除,再写出替代文件。新 Snapshot 引用替代后的文件集合;旧 Snapshot 继续引用旧文件,因此仍可 Time Travel。

定位 file-A → 读取并修改 → 写出 file-C
→ 新 Manifest 记录 file-A 移除、file-C 加入
→ Manifest List → Snapshot → metadata.json → Catalog

Merge-on-Read

Merge-on-Read 不立即重写整个旧文件。DELETE 写入 Delete File;UPDATE 可以拆成“删除旧行+写入新行”。提交时 Data Manifest 和 Delete Manifest 共同进入新 Snapshot。

定位受影响的行
├── 写 Delete File
└── 写新 Data File
        ↓
Data Manifest + Delete Manifest
        ↓
Manifest List → Snapshot → metadata.json → Catalog

模式

写入行为

写入特点

读取特点

Copy-on-Write

重写受影响的 Data File

写放大较高

查询路径简单

Merge-on-Read

写 Delete File 和新行

更新提交轻量

查询需要合并删除信息

Iceberg 不会在旧 Parquet 或 ORC 文件中原地修改行。所有变更都通过新文件和新 Snapshot 表达,这是 Snapshot 隔离和 Time Travel 的基础。

3.5 Compaction 的生命周期

Compaction 在当前 Snapshot 上选择小文件,读取后合并为较大的新文件,再提交新 Snapshot。它与 Copy-on-Write 的元数据路径相似,但重写前后的逻辑数据必须等价。

 

Snapshot 100 → file-A、file-B、file-C ↓ Compaction Snapshot 101 → file-D

旧 Snapshot 仍引用旧文件,因此会有暂时的局部存储放大。只有旧 Snapshot 过期,并且没有 Branch 或 Tag 继续引用时,旧文件才可物理删除。rewrite_manifests 则只重新组织 Manifest,不重写业务数据。

3.6 Rollback 与版本保留

Rollback 不复制 Data File。系统找到目标 Snapshot,生成新的 Table Metadata,使 current-snapshot-id 指向目标版本,再原子切换 Catalog 指针。

Snapshot 100 → file-A、file-B、file-C
                      ↓ Compaction
Snapshot 101 → file-D

Snapshot 并非必须过期,但不应永久保留所有临时 Snapshot。正式数据版本应创建 Tag 并配置保留策略;日常写入产生的临时 Snapshot 则定期过期。被有效 Tag、Branch 或当前版本引用的文件不会被快照清理删除。

3.7 元数据层级操作汇总

层级

Query

Append

Update / Delete

Compaction

Catalog

读取当前元数据地址

原子切换

原子切换

原子切换

Table Metadata

读取结构和版本

生成新 Metadata

生成新 Metadata

生成新 Metadata

Snapshot

选择当前或历史版本

创建

创建

创建

Manifest List

读取并裁剪 Manifest

创建

创建

创建

Manifest File

定位 Data / Delete File

新建并复用

记录新增与移除

记录文件替换

Data File

扫描

新增

COW 重写或 MOR 新增

小文件重写为大文件

Delete File

应用删除

通常无

MOR 创建

可以合并或清理

Puffin

可选统计;DV 必须应用

可单独计算统计

V3 可写 DV

统计可能重算

总结来说,读取通过版本化元数据逐层缩小扫描范围;写入、更新和 Compaction 通过不可变文件构建新版本,并以 Catalog 的原子提交作为对外可见边界。

第四章 优化Iceberg表的性能

4.1 优化目标与优先级

Iceberg 性能优化的核心,是让查询在元数据阶段尽早排除无关文件,而不是扫描后再过滤。完整裁剪路径为:分区裁剪 → Manifest 裁剪 → Data File 裁剪 → Parquet Row Group 或 ORC Stripe 裁剪 → 扫描实际数据。

通常应按以下顺序处理:小文件治理 → 分区设计 → 数据排序与聚簇 → Delete File 治理 → Manifest 和 Snapshot 维护 → 读取参数微调。文件布局没有改善之前,单纯调大 Split 只能减少任务数量,不能消除对象存储请求和文件打开成本。

4.2 诊断表的当前状态

优化前先通过 Iceberg Metadata Table 判断瓶颈来自数据文件、删除文件还是元数据。

-- 数据文件数量与大小
SELECT
    COUNT(*) AS file_count,
    AVG(file_size_in_bytes) / 1024 / 1024 AS avg_file_mb,
    MIN(file_size_in_bytes) / 1024 / 1024 AS min_file_mb,
    MAX(file_size_in_bytes) / 1024 / 1024 AS max_file_mb
FROM prod.db.sample.files
WHERE content = 0;

-- Delete File 规模
SELECT COUNT(*) AS delete_file_count,
       SUM(record_count) AS deleted_rows
FROM prod.db.sample.delete_files;

-- Manifest 数量和平均大小
SELECT COUNT(*) AS manifest_count,
       AVG(length) AS avg_manifest_size
FROM prod.db.sample.manifests;

-- Snapshot 数量
SELECT COUNT(*) AS snapshot_count
FROM prod.db.sample.snapshots;

现象

常见原因

优先措施

扫描任务多、文件打开耗时高

大量小 Data File

调整写入分布并执行 Compaction

过滤条件仍扫描大量文件

分区或排序与查询模式不匹配

演进 Partition Spec 或 Sort Order

UPDATE 后查询明显变慢

Delete File 过多

合并 Delete File 或重写 Data File

查询启动和规划时间长

Manifest 或 Snapshot 过多

rewrite_manifests、expire_snapshots

Join 计划不合理

缺少 NDV 等扩展统计

对关键列计算 Puffin Statistics

4.3 分区设计

分区字段应来自高频过滤条件,并使单个分区维持合理的数据量。时间字段通常使用 years、months、days 或 hours;高基数字段优先使用 bucket;具有稳定前缀语义的字段可以使用 truncate。避免直接按 sample_id、episode_id 等高基数字段做 Identity Partition,否则会产生大量微小分区和小文件。

CREATE TABLE prod.robotics.samples (
    sample_id STRING,
    robot_id STRING,
    capture_time TIMESTAMP,
    scene_type STRING
)
USING iceberg
PARTITIONED BY (
    days(capture_time),
    bucket(32, robot_id)
);

Iceberg 支持隐藏分区和 Partition Evolution。修改 Partition Spec 只影响新写入的数据,旧文件保留原布局;查询引擎会分别为不同 Partition Spec 规划扫描。

4.4 文件大小与写入分布

Iceberg 默认目标 Data File 大小为 512 MB。通用分析可从 256~512 MB 起步,低延迟小范围查询可适当减小,大规模顺序扫描可适当增大,最终值应通过实际查询和对象存储指标验证。

ALTER TABLE prod.robotics.samples
SET TBLPROPERTIES (
    'write.target-file-size-bytes' = '536870912',
    'write.distribution-mode' = 'hash'
);

该属性只影响新文件,不会自动合并历史小文件。Spark 写出的文件不能大于单个 Task 处理的数据量,而且磁盘上的列式压缩文件通常远小于 Spark 内存中的行数据。因此还要结合 write.distribution-mode、Repartition 和 AQE 的 advisoryPartitionSizeInBytes 调整 Task 大小。

Distribution Mode

特点

适用场景

none

不主动 Shuffle,最依赖上游数据布局

上游已经按分区和排序组织好

hash

按分区值进行 Hash Shuffle

通用默认选择

range

按分区字段和 Sort Order 做范围分布

愿意增加写入成本以换取更好的读取聚簇

4.5 Data File Compaction

流式写入和高频小批次容易产生小文件。rewrite_data_files 可以通过 binpack 将小文件合并到目标大小,降低 Manifest 体积、文件打开次数和扫描任务数量。

定位受影响的行
├── 写 Delete File
└── 写新 Data File
        ↓
Data Manifest + Delete Manifest
        ↓
Manifest List → Snapshot → metadata.json → Catalog

生产环境优先对近期活跃分区做增量 Compaction,不要频繁全表重写。全表重写会带来大量 I/O、暂时的存储放大、新 Snapshot 堆积,以及与在线写入的提交冲突。

4.6 Sort Order 与数据聚簇

当查询经常过滤非分区字段时,可以设置 Sort Order,让同类值落在相邻文件和 Row Group 中,提高 Manifest 的 min/max 与 Parquet 统计裁剪效果。

ALTER TABLE prod.robotics.samples
WRITE LOCALLY ORDERED BY robot_id, capture_time;

CALL prod.system.rewrite_data_files(
    table => 'robotics.samples',
    strategy => 'sort',
    sort_order =>
      'robot_id ASC NULLS LAST,capture_time ASC NULLS LAST'
);

Z-Order 适合多个过滤字段组合变化较大的情况,但排序字段不宜过多,否则 Shuffle 成本升高且聚簇效果被稀释。Sort Order 只影响物理写入布局,不保证查询结果有序。

4.7 Delete File 治理

Merge-on-Read 将更新和删除记录为 Position Delete、Equality Delete 或 Deletion Vector,写入较轻,但读取需要把删除信息与 Data File 合并。Delete File 长期积累会增加规划、文件打开和运行时合并成本。

CALL prod.system.rewrite_position_delete_files(
    table => 'robotics.samples'
);

写入优先的表可以采用 Merge-on-Read 并定期维护;查询优先、更新不频繁的表更适合 Copy-on-Write。Compaction 后还应关注指向已重写 Data File 的 dangling delete。

4.8 Manifest 与 Snapshot 维护

高频提交会产生大量 Manifest 和 Snapshot。Manifest 过多主要增加查询规划时间;Snapshot 过多则增加 Table Metadata、历史文件和维护成本。

-- 重新组织 Manifest,不重写业务数据
CALL prod.system.rewrite_manifests(
    table => 'robotics.samples'
);

-- 清理不再需要的 Snapshot,同时至少保留最近 20 个
CALL prod.system.expire_snapshots(
    table => 'robotics.samples',
    older_than => TIMESTAMP '2026-07-01 00:00:00',
    retain_last => 20
);

被当前版本、有效 Branch 或 Tag 引用的 Snapshot 不会过期。正式数据集版本应先创建 Tag,再清理临时 Snapshot。remove_orphan_files 需要设置足够长的安全时间窗,并确认对象存储路径与 Iceberg Metadata Table 一致,避免误删在途写入文件。

4.9 Puffin Statistics 与读取参数

对 Join Key、过滤字段和 Group By 字段计算 NDV,可以帮助支持 Puffin Statistics 的优化器改善 Join 顺序和基数估算。不建议对所有字段无差别收集,尤其是大型字符串、JSON 和很少参与过滤或关联的字段。

CALL prod.system.compute_table_stats(
    table => 'robotics.samples',
    columns => array(
        'robot_id',
        'scene_type',
        'sensor_type',
        'episode_id'
    )
);

Iceberg 默认 read.split.target-size 为 128 MB,read.split.open-file-cost 为 4 MB,并默认启用 Parquet 向量化读取。只有在文件布局健康后,才考虑根据查询并发、对象存储延迟和执行器资源调整 Split 与 Batch Size。

4.10 推荐维护策略

操作

建议触发条件

rewrite_data_files

小文件数量或占比超过阈值;优先处理活跃分区

rewrite_position_delete_files

Delete File 数量、删除行数或查询合并成本明显增长

rewrite_manifests

Manifest 数量过多,查询规划耗时上升

compute_table_stats

正式数据版本发布或大规模数据变化后

expire_snapshots

每日或每周执行,并遵守 Tag、Branch 和长查询保留要求

remove_orphan_files

低频执行,设置足够安全时间窗

总结来说,Iceberg 的性能来自良好的数据布局与持续维护:写入时控制分区、分布、排序和文件大小,维护时控制 Data File、Delete File、Manifest 和 Snapshot 数量,让查询尽量在元数据阶段完成裁剪。

第五章 Iceberg生态

5.1 Iceberg 在湖仓体系中的位置

Iceberg 不是存储系统,也不是查询引擎,而是位于对象存储与计算引擎之间的开放表格式。它通过统一的表元数据、Snapshot、Schema、Partition Spec 和事务提交协议,让不同引擎可以安全地读写同一份数据。

应用层:BI、数据开发、机器学习、数据服务
                    ↓
计算与查询:Spark、Flink、Trino、Presto、Hive、Impala、Dremio 等
                    ↓
Catalog:REST、Hive Metastore、Hadoop、JDBC、Glue、Nessie 及厂商 Catalog
                    ↓
Iceberg 表格式:Table Metadata、Snapshot、Manifest List、Manifest、Puffin
                    ↓
数据文件:Parquet、ORC、Avro
                    ↓
底层存储:S3、GCS、ADLS、HDFS 及兼容对象存储

这种分层意味着计算与存储可以独立选择和扩展。数据不必绑定到某一个查询引擎,多个引擎可以围绕同一个 Catalog 和同一组 Iceberg 表协作。但“格式开放”不等于“所有引擎能力完全一致”,具体功能仍取决于各引擎连接器的实现。

5.2 社区活跃度

Apache Iceberg 是 Apache Software Foundation 下持续活跃开发的顶级开源项目,采用 Apache 2.0 许可证,设计规范公开,社区通过 GitHub Issue、Pull Request 和开发者邮件列表协作。Java 实现是参考实现,表格式规范与各语言实现可以独立演进。

截至 2026 年 7 月,官方最新版本为 1.11.0,于 2026 年 5 月发布。该版本由 200 多位贡献者完成超过 1,000 次提交,说明项目不仅有稳定的核心维护者,也有广泛的个人和厂商参与。与此同时,PyIceberg、iceberg-go、iceberg-rust 和 iceberg-cpp 等项目也在持续扩展非 JVM 生态。

观察维度

生态表现

实际意义

发布节奏

持续发布功能版本和修复版本

新引擎、新存储和新 Spec 能力可以较快进入生态

贡献者结构

社区与多家厂商共同参与

降低由单一厂商控制标准的风险

规范治理

表格式、REST Catalog、Puffin 等规范公开

第三方可以实现兼容引擎和 SDK

多语言实现

Java、Python、Go、Rust、C++

支持数据平台、服务端和嵌入式分析等不同场景

版本演进

Spec V1、V2、V3 持续增加能力

功能丰富,但升级前必须检查读写引擎兼容性

5.3 底层存储生态

Iceberg 通过 FileIO 抽象访问底层存储。表元数据记录数据文件的绝对路径,提交通过写入新元数据文件并原子更新表指针完成,不依赖目录重命名。这一设计天然适合对象存储,也可以运行在传统 Hadoop 文件系统上。

存储类型

典型实现

特点

公有云对象存储

Amazon S3、Google Cloud Storage、Azure ADLS

容量弹性大、存算分离,是湖仓的主要部署形态

Hadoop 文件系统

HDFS 及 Hadoop FileSystem 兼容实现

适合已有本地 Hadoop 基础设施

S3 兼容对象存储

私有云和本地对象存储

可以在本地或混合云部署开放湖仓

厂商托管存储

S3 Tables、OneLake 等

可将 Compaction、Snapshot 清理等维护工作托管化

存储层只负责保存对象,Iceberg 负责定义哪些文件属于当前表版本。实际部署还需关注对象存储请求成本、前缀热点、Range Read、批量删除、加密、权限和跨区域访问延迟。

5.4 数据文件与扩展元数据

文件类型

用途

生态位置

Parquet

列式数据文件

最常用,适合分析查询和机器学习特征数据

ORC

列式数据文件

在 Hive 等生态中较常见

Avro

行式数据或 Iceberg 元数据

Iceberg Manifest 使用 Avro;也可作为业务数据格式

Puffin

统计、索引和 Deletion Vector Blob

扩展 Manifest 不适合承载的大型元数据

开放表格式与开放文件格式相互独立。Iceberg 管理表级事务和版本,Parquet、ORC、Avro 管理文件内部编码。引擎是否支持某种文件格式、向量化读取、Puffin Statistics 或 Deletion Vector,需要分别确认。

5.5 Catalog 生态

Catalog 负责将表名解析到当前 Table Metadata,并提供原子提交和并发控制。它是多引擎协作的控制面,也是 Iceberg 架构中最需要统一选型的部分。

Catalog 类型

典型场景

主要特点

REST Catalog

跨引擎、跨语言、平台化湖仓

通过标准协议隔离客户端与后端实现,便于统一认证和治理

Hive Catalog

已有 Hive Metastore 环境

迁移成本低,Spark、Flink、Hive 等生态成熟

Hadoop Catalog

简单环境、测试或单平台部署

以文件系统路径组织表,组件较少

JDBC Catalog

希望使用关系数据库保存 Catalog 元数据

部署直接,但要自行保证高可用和并发能力

AWS Glue Catalog

AWS 数据湖

与 S3、Athena、EMR 和 Glue 服务集成

Nessie

需要 Catalog 级 Branch、Tag 和多表版本控制

提供类似 Git 的 Catalog 版本语义

厂商 Catalog

托管湖仓和企业治理

常集成权限、审计、优化、数据发现和跨引擎访问

Iceberg Snapshot 是单表级版本;如果业务需要多张表一致发布,仅靠表内 Snapshot 不够。可以在应用层记录多表 Snapshot 集合,或选择具备 Catalog 级事务和版本语义的实现。

5.6 上层计算与查询生态

Iceberg 的价值很大程度来自多引擎共享。Spark、Flink 和 Hive 的连接器由 Iceberg 主仓库维护,并针对不同引擎版本发布独立 Runtime JAR;Trino、Presto 等引擎通常在各自项目中维护 Iceberg 连接器。

引擎类型

代表组件

典型用途

批处理与数据工程

Apache Spark

建表、批量 ETL、MERGE、Compaction、表维护和机器学习数据准备

流处理

Apache Flink

流式写入、增量读取、CDC 入湖和流批一体

交互式 SQL

Trino、Presto、Dremio

低延迟分析、联邦查询和 BI

传统 Hadoop SQL

Hive、Impala

复用既有数仓任务和查询体系

嵌入式与单机分析

DuckDB 等

本地分析、数据科学和轻量服务

流数据接入

Kafka Connect、Flink CDC、厂商流平台

将 Kafka、数据库变更和事件流持续写入 Iceberg

Spark 通常是 Iceberg 功能最完整的引擎,适合执行表管理和维护;Flink 在流式写入与增量处理方面更有优势;Trino 等 MPP 引擎适合交互式查询。生产架构可以由不同引擎分工,但必须统一 Catalog、表格式版本和并发写入约束。

5.7 云厂商与商业产品生态

Iceberg 已经从开源表格式发展为主流云数据平台共同支持的开放接口。AWS、Google Cloud、Microsoft、Snowflake、Databricks、Dremio、Starburst、Cloudera 等均提供不同程度的 Iceberg 读写、Catalog、治理或自动维护能力。

厂商支持扩大了 Iceberg 的可用范围,也带来了两种部署路径:一种是自建对象存储、Catalog 和计算引擎,获得最大控制权;另一种是使用托管 Iceberg 表或托管 Catalog,把 Compaction、Snapshot 清理、权限和监控交给云平台。选型时应区分“能够读取 Iceberg”与“完整支持 DDL、DML、Branch、Tag、Puffin、Deletion Vector 和维护操作”。

5.8 语言 SDK 与应用集成

实现

定位

适用场景

Java Iceberg

参考实现,能力最完整

计算引擎、Catalog 服务、表维护和 JVM 数据平台

PyIceberg

Python 原生实现

数据科学、Python 服务、轻量表管理与 Arrow 集成

iceberg-go

Go 原生实现

高并发服务、平台控制面和云原生基础设施

iceberg-rust

Rust 原生实现

高性能查询引擎和嵌入式数据系统

iceberg-cpp

C++ 原生实现

原生计算引擎与跨语言系统集成

多语言实现提升了 Iceberg 作为开放标准的可移植性,但各实现对 Spec V1、V2、V3、Puffin、行级删除和维护操作的覆盖并不完全一致。应用接入前应检查官方 Implementation Status,而不能只看是否能够打开表。

5.9 生态丰富度带来的优势

优势

说明

降低计算引擎锁定

同一份数据可以被批处理、流处理和交互式查询引擎共享

降低存储平台锁定

可以部署在公有云、私有云、HDFS 和多种兼容对象存储上

统一批流数据

离线回填、实时写入和增量消费可以围绕同一张表协作

开放治理接口

Catalog、元数据表和 REST 协议便于接入权限、血缘、审计和质量平台

厂商共同支持

主流云平台和数据产品都能围绕 Iceberg 提供服务

5.10 生态选型中的现实风险

功能矩阵不一致。引擎支持 Iceberg 并不代表支持全部能力。基础 SELECT 可能成熟,但 UPDATE、MERGE、Branch、Tag、Puffin、Deletion Vector、Spec V3 和维护命令的支持程度可能不同。

版本与依赖冲突。Spark、Flink、Hive 需要匹配具体版本的 Runtime JAR。将 iceberg-core、iceberg-parquet 等额外依赖错误打入引擎 classpath,可能与引擎内置依赖冲突。

Catalog 是关键依赖。多个引擎必须对 Catalog、Warehouse 路径、FileIO、认证和表属性形成一致理解,否则可能出现提交冲突、权限差异或表不可见。

开放格式不等于免运维。小文件合并、Delete File 治理、Manifest 重写、Snapshot 过期、孤儿文件清理和统计更新仍需持续运行,除非使用托管服务。

厂商扩展可能影响可移植性。采用某个平台特有的 Catalog、优化器或扩展能力前,应确认其他目标引擎是否可以读写,以及迁移时是否需要回退到标准能力。

5.11 生态选型建议

需求

建议组合

批处理湖仓

对象存储 + REST/Hive Catalog + Spark + Trino

实时湖仓

Kafka/CDC + Flink + Iceberg + Trino/Spark

云上托管

云对象存储 + 托管 Catalog + 托管计算与自动维护

本地 Hadoop 演进

HDFS + Hive Metastore + Spark/Hive/Trino

机器学习数据平台

对象存储 + Iceberg Manifest 表 + Spark/Flink 数据准备 + 独立 Dataset Release

整体来看,Iceberg 的最大竞争力并不是某个单点功能,而是开放规范、活跃社区、多引擎共享、多云存储和丰富厂商支持共同形成的生态网络。它适合作为湖仓数据的公共表层,但生产落地仍应围绕 Catalog 统一、功能兼容矩阵和持续维护能力进行设计。

5.12 参考资料

Apache Iceberg Releases

Apache Iceberg Multi-Engine Support

Apache Iceberg FileIO

Vendors Supporting Iceberg Tables

Apache Iceberg GitHub Repository

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值