Iceberg 表结构:Catalog、Namespace、Table 三层架构与元数据文件详解
好,咱们今天来聊聊 Iceberg 的表结构。说实话,我第一次接触 Iceberg 时,最直观的感受就是——它跟 Hive 表长得太不一样了。Hive 表说白了就是个目录,里面堆着一堆文件。但 Iceberg 不一样,它把表拆成了三层:Catalog、Namespace、Table。每一层都有自己的职责。
我习惯把这套结构比作「图书馆管理系统」。Catalog 就是图书馆本身,Namespace 是书架分类,Table 是具体的书。这么一想,是不是好理解多了?
Catalog:表的注册中心
Catalog 是 Iceberg 的最顶层。它负责管理所有 Namespace 和 Table 的元数据。说白了,你要找一张表,得先告诉系统去哪个 Catalog 里找。
Iceberg 支持多种 Catalog 实现:
| Catalog 类型 | 适用场景 | 我个人的偏好 |
|---|---|---|
| HiveCatalog | 与 Hive Metastore 集成 | 适合已有 Hive 环境的团队 |
| HadoopCatalog | 直接基于文件系统 | 轻量级,适合测试环境 |
| RESTCatalog | 跨集群共享元数据 | 我最近项目里用的就是这个 |
| JDBCCatalog | 基于关系型数据库 | 适合需要事务保障的场景 |
Namespace:逻辑上的命名空间
Namespace 就是传统意义上的「数据库」或「Schema」。它把表组织成逻辑分组。比如你有个电商平台,可以建 ods、dwd、dws 三个 Namespace,分别放原始数据、明细数据、汇总数据。
创建 Namespace 很简单:
-- 在 Flink SQL 中创建 Namespace
CREATE DATABASE IF NOT EXISTS my_catalog.ods;
-- 或者用 Spark
spark.catalog.createNamespace("ods");
嗯,这里要注意:Namespace 本身不存数据,它就是个容器。但 Iceberg 会在元数据里记录 Namespace 的属性,比如创建时间、描述信息等。
Table:真正的数据载体
Table 是 Iceberg 的核心。它不再像 Hive 表那样直接指向 HDFS 目录,而是通过元数据文件来管理数据。一张 Iceberg 表由三部分组成:
- 元数据层:记录表的 schema、分区信息、快照列表
- 清单层:记录数据文件的位置和统计信息
- 数据层:实际存储的 Parquet/ORC/Avro 文件
你想想看,这种分层设计带来了什么好处?
最直接的好处是——快照隔离。每次写入都会生成一个新的元数据版本,读操作可以基于任意历史版本进行。我在做实时数仓时,经常利用这个特性做「时间旅行查询」,查某个时间点的数据状态,不用停写。
元数据文件详解
好,接下来咱们深入看看元数据文件。这部分我当初啃源码时花了不少时间,但搞懂后,很多问题就迎刃而解了。
一张 Iceberg 表的元数据文件结构是这样的:
my_table/
├── metadata/
│ ├── 00001-xxx.metadata.json # 当前元数据
│ ├── 00002-yyy.metadata.json # 历史元数据
│ ├── snap-123-xxx.avro # 快照清单
│ └── snap-456-yyy.avro
└── data/
└── partition_key=value/
└── 00000-xxx.parquet
每个 metadata.json 文件包含以下关键信息:
| 字段 | 说明 | 我在项目中踩过的坑 |
|---|---|---|
format-version |
元数据格式版本(v1/v2) | v2 支持行级删除,但兼容性要注意 |
table-uuid |
表的全局唯一标识 | 重命名表时这个不会变 |
location |
表数据的基础路径 | 千万别手动改,否则表就废了 |
last-sequence-number |
最后一次操作的序列号 | 用于冲突检测,v2 新增 |
current-snapshot-id |
当前快照 ID | 时间旅行就是改这个值 |
snapshots |
所有快照的列表 | 包含每个快照的 manifest 列表 |
write.metadata.delete-after-commit.enabled=true,保留最近 10 个版本就够了。别让元数据文件无限制增长,否则 NameNode 会报警的。
快照与清单文件的关系
每个快照(Snapshot)对应一个清单列表(Manifest List)。清单列表里记录了一组清单文件(Manifest File),每个清单文件又记录了数据文件的统计信息。
我画了一张图,帮你理清这个关系:
从这张图你能看到,Iceberg 的元数据是层层递进的。每次查询时,引擎只需要读取 metadata.json,找到当前快照,再顺着清单列表找到需要的数据文件。这个过程非常高效,因为清单文件里已经记录了每个数据文件的列统计信息(min/max、null count 等),可以提前过滤掉不需要的文件。
Flink 集成中的三层架构使用
在 Flink SQL 中,这三层架构的使用非常直观:
-- 1. 注册 Catalog
CREATE CATALOG my_iceberg WITH (
'type' = 'iceberg',
'catalog-type' = 'hadoop',
'warehouse' = 'hdfs://namenode:8020/warehouse'
);
-- 2. 使用 Namespace
USE CATALOG my_iceberg;
CREATE DATABASE IF NOT EXISTS ods;
-- 3. 创建表
CREATE TABLE ods.order_log (
order_id BIGINT,
user_id BIGINT,
amount DOUBLE,
order_ts TIMESTAMP(3)
) WITH (
'format' = 'parquet',
'write.metadata.delete-after-commit.enabled' = 'true',
'write.metadata.previous-versions-max' = '10'
);
这段代码背后发生了什么?
- Flink 会先连接 HadoopCatalog,在
/warehouse下创建ods.db目录 - 然后在
ods.db下创建order_log目录 - 首次写入时,生成
metadata/00001-xxx.metadata.json - 每次 checkpoint 后,生成新的快照和对应的清单文件
write.metadata.delete-after-commit.enabled=true。为什么?因为 Flink 的 checkpoint 频率通常很高(几秒一次),如果不清理历史元数据,一天就能生成上万个文件。我曾经在一个 24/7 运行的流作业中遇到过这个问题,元数据目录膨胀到 50 万文件,NameNode 直接 OOM 了。
总结一下
Iceberg 的三层架构——Catalog、Namespace、Table——不是凭空设计的。它解决了传统 Hive 表在 ACID、快照隔离、文件管理上的痛点。元数据文件的分层设计,让 Iceberg 既能支持高并发写入,又能提供毫秒级的快照切换。
我个人觉得,理解这套架构是掌握 Iceberg 的第一步。后面咱们讲 Flink 写入、读取、时间旅行时,都会反复用到这些概念。先把基础打牢,后面就顺了。