土法炼钢兴趣小组的算法知识备份

【数据湖与开放表格式】Catalog 之争

文章导航

分类入口
databasestorage
标签入口
#catalog#rest-catalog#polaris#unity-catalog#nessie

目录

第 11 章 讲清了 Iceberg 的提交协议:一次提交本质上是把表的「当前元数据指针」从 vN.metadata.json 原子地换成 vN+1.metadata.json。但那一章刻意回避了一个问题——这个原子 swap 到底由谁来做、靠什么保证原子。答案就是 catalog。

catalog 不是可有可无的目录服务。在没有数据库进程的湖仓里,它是唯一的串行化点:所有写入者通过它竞争同一个指针,所有读取者通过它找到「现在应该读哪个 metadata」。catalog 选错,轻则多引擎读不到对方写的数据,重则两个并发提交互相覆盖、丢数据而不报错。

本章先把 catalog 的两件职责拆到最小,再逐个对比主流形态的锁与原子性语义,最后单独讲 2024 年开源的 Apache Polaris 与 Unity Catalog——它们把「catalog」从一个元数据存储,扩展成了带权限治理、凭证派发的服务层。

版本锚定:Iceberg 表规范 V2、Iceberg REST Catalog OpenAPI(apache/icebergopen-api/rest-catalog-open-api.yaml。Polaris 引用其 Apache 孵化期(2024-08 进入孵化)与 GitHub 主线;Unity Catalog 引用其 OSS 0.x(2024-06 开源,Apache 2.0)。涉及具体默认值与端点以对应版本规范为准。


一、catalog 到底负责什么

把一切营销词去掉,开放表格式语境下的 catalog 只有两件不可让渡的职责。

1.1 职责一:表标识符 → 当前元数据指针

读一张 Iceberg 表的第一步,不是去 list 数据目录,而是问 catalog:「db.events 这张表,现在的 metadata 文件在哪?」catalog 返回一个指针,通常是对象存储上某个 …/metadata/00042-<uuid>.metadata.json 的路径。

这一步是整个 lakehouse「不 list 目录」承诺的起点。第 8 章 描述的四层元数据树——metadata.json → manifest list → manifest → data files——全都挂在这个指针下面。指针一旦拿到,后续 planning 全靠读元数据文件,与目录里到底躺了多少文件无关。

flowchart LR
  R[读/写客户端] -->|1 loadTable db.events| C[catalog]
  C -->|2 返回 metadata-location| R
  R -->|3 读 metadata.json| M[vN.metadata.json]
  M --> ML[manifest list]
  ML --> MF[manifest files]
  MF --> D[data files on S3]

1.2 职责二:原子提交点

写入更难。提交一个新 snapshot,等价于让 catalog 把指针从 metadata-location = vN 改成 vN+1,且必须满足:

这就是乐观并发控制(optimistic concurrency control)的 compare-and-swap:

\[ \text{CAS}(\text{pointer},\; \text{expected}=v_N,\; \text{new}=v_{N+1}) \]

成功的前提是 \(\text{pointer} = v_N\) 仍然成立。catalog 形态之间的根本差异,就在于这个 CAS 用什么底层机制实现、以及它在并发下到底安不安全。 后面所有对比都围绕这一点。

1.3 为什么不能让客户端直接改对象存储

一个自然的疑问:既然 metadata 都在 S3 上,写入者直接把指针写进某个约定文件不就行了?问题在于 S3 这类对象存储的语义(见 第 6 章):

于是「谁来仲裁并发」这件事必须外包给一个能提供线性化语义的组件——可能是关系数据库的行锁、可能是一个 HTTP 服务背后的事务,也可能就是对象存储自己的条件写。catalog 就是这个仲裁者的抽象。


二、Iceberg 的 catalog 抽象

Iceberg 把 catalog 定义成一个接口(org.apache.iceberg.catalog.CatalogTableOperations),所有实现都要回答上面两个问题。关键方法语义:

操作 语义
loadTable(id) 返回当前 metadata-location 指向的表对象
createTable(id, schema, …) 注册新表标识符 → 初始 metadata 文件
commit(base, metadata) 原子地把指针从 base(旧 metadata)换到新 metadata,失败抛 CommitFailedException
renameTable / dropTable 改 / 删指针,数据文件视 purge 决定是否删

commit 的实现是分水岭。TableOperations.commit(base, metadata) 拿到「我看到的旧 metadata」base 和「我想提交的新 metadata」,由具体 catalog 决定如何原子地完成替换。下面逐个看主流实现怎么做这件事。


三、形态对比:锁与原子性语义

3.1 Hive Metastore(HMS)

Hive Metastore 是最老的玩家:一个 Thrift 服务,背后是关系数据库(Derby/MySQL/PostgreSQL),最初为 Hive 目录式分区表设计。

Iceberg 通过 HiveCatalog 复用 HMS:表在 HMS 里登记为一个 entry,真正的指针存在表属性 metadata_location 里。提交时:

  1. 读出 HMS 中当前的 metadata_location,确认等于 base
  2. 写出新的 metadata.json 到对象存储;
  3. 通过 HMS 的 alter_table,把 metadata_location 改成新值,同时把旧值写入 previous_metadata_location 做校验。

原子性来自 HMS 对该表加的(早期实现用 HMS 的 lock 接口;不同版本用 alter_table 配合后端数据库事务做条件更新)。要点:

HMS 的价值是生态惯性:Hive、Spark、Trino、Flink 都原生认 HMS,遗留数仓迁移时它是默认落点。但它把「Hive 表模型」和「Iceberg 表模型」混在一个服务里,治理与演进都受 Hive 历史包袱拖累。

3.2 JDBC Catalog

JdbcCatalog 直接把指针存进一张关系表,省掉 HMS 这层 Thrift 服务。Iceberg 在配置的数据库里建一张表(默认 iceberg_tables,列含 catalog 名、namespace、表名、metadata_locationprevious_metadata_location)。

提交的原子性来自一条带 WHERE 条件的 UPDATE

UPDATE iceberg_tables
SET metadata_location = :new,
    previous_metadata_location = :base
WHERE catalog_name = :cat
  AND table_namespace = :ns
  AND table_name = :tbl
  AND metadata_location = :base;   -- CAS 前提条件

WHERE … metadata_location = :base 就是 CAS:如果别人抢先改过指针,base 不再匹配,UPDATE 影响行数为 0,提交失败、客户端重试。原子性由数据库单语句的事务隔离保证,干净直接。

适用场景:不想跑 HMS、又已有一套可靠关系数据库(RDS/PG)时,JDBC catalog 是最轻的「正确」实现。代价是每个客户端都要能直连数据库并持有凭证——这恰恰是 REST catalog 想消灭的耦合。

3.3 Hadoop Catalog(file-based,并发风险)

HadoopCatalog 不依赖任何外部服务,指针完全靠文件系统约定:metadata 文件按 v1.metadata.jsonv2.metadata.json 递增命名,外加一个 version-hint.text 记录当前版本号。提交就是写出 vN+1.metadata.json 然后更新 hint。

在 HDFS 上,它依赖文件系统的原子 rename:提交者把新版本 rename 到目标名,rename 失败说明被人抢先,于是重试。HDFS 的 rename 是原子的,这条路勉强成立。

在 S3 这类对象存储上,它不安全。 原因正是 第 6 章 讲的:S3 没有原子 rename(rename = copy + delete,非原子),传统上也不保证「create-if-absent」。两个写入者可能同时认为自己拿到了 vN+1,先后覆盖,造成丢提交。Iceberg 官方文档明确警告:HadoopCatalog 不应在对象存储上用于并发写入的生产表

所以 Hadoop catalog 的定位是:本地文件系统 / HDFS 上的单写入者场景、教学与测试。一旦上 S3 且有并发,就该换 REST/JDBC/Glue。

3.4 AWS Glue Catalog

GlueCatalog 用 AWS Glue Data Catalog 当元数据后端,指针同样存在表属性里。原子性依赖 Glue 的乐观锁:Glue 的表对象带一个 version 标识,UpdateTable 时带上期望版本,版本不符则失败。

注意历史细节:早期为了在 Glue 上获得更强的并发保证,Iceberg 文档曾建议配合 DynamoDB 锁表LockManager)做额外串行化;随着 Glue 自身乐观锁完善,新版本可不依赖外部锁。具体以所用 Iceberg 版本的 AWS 模块文档为准——这是个典型的「版本边界」结论,不能照抄老博客。

Glue 的吸引力在于和 AWS 生态(Athena、EMR、Lake Formation 权限)打通;代价是绑定 AWS,且跨云互通要另想办法。

3.5 Nessie:类 git 的分支与标签

Project Nessie 把 catalog 做成了版本控制系统。核心抽象不是「表的当前指针」,而是「一棵提交树」:

原子性由 Nessie 服务端保证(后端可用 RocksDB、关系库、Mongo 等),提交是基于「期望 branch HEAD」的 CAS:HEAD 变了就冲突重试,语义和 git 的 non-fast-forward 拒绝一致。

flowchart TD
  M0["main@c0"] --> M1["main@c1"]
  M1 --> M2["main@c2"]
  M1 -->|branch| D1["etl_dev@c1"]
  D1 --> D2["etl_dev@d2 改 t1,t2"]
  D2 -.->|验证后 merge| M2

合并(merge)时,Nessie 会检测冲突:如果两条分支都改了同一张表,且基点不同,merge 会像 git 一样报冲突,需要决策(要哪一方、或重新基于最新 main 做)。冲突粒度通常在「表内容(content)」级别——两个分支各自把某张表推到了不同 snapshot,merge 不能简单二选一时即冲突。

垃圾回收(GC)在 Nessie 里也变成版本控制问题:一个 data file 只要还被任意一条 branch/tag 可达的 commit 引用,就不能删。Nessie 的 GC 要遍历所有 ref 的可达集合再做差集——这比单表 expire 复杂,但语义清晰:可达即存活。

Nessie 适合需要「数据版本化、跨表原子、分支隔离做 ETL/回归」的团队,代价是引入一个新的有状态服务与心智模型。它也可以作为 Iceberg REST catalog 的后端实现之一对外暴露——此时引擎看到的是标准 REST 端点,Nessie 在背后提供分支与多表事务能力。

3.6 Apache Gravitino

Gravitino(Apache 孵化中)定位是统一元数据湖(metadata lake):不只是 Iceberg 一种表,而是把关系库、消息、文件集、不同表格式的 catalog 聚合到一个「catalog 的 catalog」之下,并对外提供包括 Iceberg REST 在内的接口。它解决的是「一个组织里有 N 套 catalog,如何统一发现与治理」的上层问题,而不是替换某个具体 catalog 的 CAS 实现。在本系列语境里,把它理解为「多 catalog 之上的联邦层」即可。

3.7 小结对比

Catalog 指针存储 原子性来源 对象存储并发安全 多表事务 主要绑定
Hive Metastore HMS + 后端 RDB 表锁 + DB 事务 安全(依赖 HMS) Hadoop 生态
JDBC 关系表一行 单语句条件 UPDATE 安全(依赖 DB) 一套 RDB
Hadoop(file) vN.metadata.json + hint 文件系统原子 rename 不安全(S3) HDFS / 本地
AWS Glue Glue 表属性 Glue 乐观锁(+可选 DDB 锁) 安全 AWS
Nessie 提交 DAG 服务端 CAS(类 git) 安全 Nessie 服务
REST Catalog 由后端决定 服务端事务 安全 取决于实现 HTTP 协议
Polaris / Unity 见第五、六节 服务端 安全 取决于实现 各自服务

「多引擎共享兼容性」的判据,不在于 catalog 能不能存指针,而在于:所有引擎是否都把同一处当作唯一权威指针,且对 CAS 语义理解一致。HMS 时代多引擎要各自实现 HMS 客户端、各自处理锁,互通靠运气;REST catalog 把这件事变成一份 HTTP 契约,下一节展开。


四、Iceberg REST Catalog 规范

REST Catalog 是 Iceberg 社区给「catalog 互通」开的标准答案:定义一份 OpenAPI 规范(apache/iceberg 仓库的 open-api/rest-catalog-open-api.yaml),客户端只认 HTTP 端点,完全不关心服务端背后是 HMS、JDBC、Nessie 还是某云厂商实现

4.1 解耦:客户端只说 HTTP

传统 catalog 的耦合在于:客户端要打包 HMS Thrift 客户端、或持有数据库凭证、或带 AWS SDK。引擎升级、catalog 换后端,客户端都得跟着改。REST catalog 把这层全部收到服务端:

flowchart LR
  subgraph clients[多引擎客户端]
    SP[Spark]
    TR[Trino]
    FL[Flink]
    PY[PyIceberg]
  end
  clients -->|HTTP / OpenAPI| RS[REST Catalog 服务]
  RS --> BK[(后端: HMS/JDBC/Nessie/自研)]
  RS --> OS[(对象存储)]

客户端只需要一个 URI、一个 token、可能一个 warehouse 名。引擎侧的 catalog 实现统一成一个薄 HTTP 客户端。

4.2 关键端点

规范的核心端点(路径中 {prefix} 用于多租户/多 warehouse 路由):

端点 作用
GET /v1/config 客户端启动握手,服务端返回默认与覆盖配置
GET /v1/{prefix}/namespaces 列 namespace
POST /v1/{prefix}/namespaces 建 namespace
GET /v1/{prefix}/namespaces/{ns}/tables 列表
POST /v1/{prefix}/namespaces/{ns}/tables 建表
GET …/tables/{table} loadTable,返回 metadata 与(可选)凭证
POST …/tables/{table} commit:提交 updates,带 requirements

4.3 提交语义:requirements + updates

REST catalog 的提交不是「把新指针 PUT 上去」,而是发一个 UpdateTableRequest,里面分两部分:

这正是 CAS 的 HTTP 表达。requirements 里典型的断言类型:

requirement 含义
assert-create 断言这是新建(表此前不存在)
assert-table-uuid 表 UUID 必须等于期望值(防张冠李戴)
assert-ref-snapshot-id 某个 ref(如 main)当前指向的 snapshot-id 必须等于期望值
assert-last-assigned-field-id 上次分配的 field-id 不变(防 schema 并发演进冲突)
assert-current-schema-id 当前 schema-id 等于期望
assert-default-spec-id 默认分区 spec-id 等于期望
assert-last-assigned-partition-id 上次分配的 partition field-id 不变

服务端在一个事务里校验全部 requirements 再应用 updatesassert-ref-snapshot-id 就是「我基于 snapshot X 算的,提交时 main 还得是 X」——和 第 11 章 的乐观并发完全对应,只是从客户端本地逻辑挪到了服务端契约里。请求体骨架:

{
  "requirements": [
    { "type": "assert-ref-snapshot-id", "ref": "main", "snapshot-id": 4387548087484650653 }
  ],
  "updates": [
    { "action": "add-snapshot", "snapshot": { "snapshot-id": 4044267194436670622, "...": "..." } },
    { "action": "set-snapshot-ref", "ref-name": "main", "type": "branch", "snapshot-id": 4044267194436670622 }
  ]
}

main 已经被别人推到了别的 snapshot,assert-ref-snapshot-id 失败,服务端返回 409,客户端重新基于最新 snapshot 计算再试。

4.4 凭证派发(credential vending)

REST catalog 还能解决一个治理痛点:数据文件在对象存储上,谁有权读? 传统做法是每个引擎客户端各自配 S3 凭证,权限散落各处。REST 规范允许 loadTable 响应里携带作用域受限的临时凭证(vended credentials)——服务端按调用者权限,只发放「能读这张表对应前缀」的短期 token。权限判断集中在 catalog 服务,客户端不再长期持有宽泛的存储密钥。Polaris 和 Unity Catalog 都把凭证派发当作核心卖点。

4.5 启动握手:GET /v1/config

客户端连上 REST catalog 第一件事是握手。GET /v1/config 返回两组配置:

合并规则是:overrides > 客户端显式配置 > defaults。这让服务端能集中下发「这个 warehouse 的数据根路径」「该用哪种 FileIO」「是否启用凭证派发」等,客户端无需硬编码。典型响应骨架:

{
  "defaults":  { "write.parquet.compression-codec": "zstd" },
  "overrides": { "warehouse": "s3://lake/prod", "io-impl": "org.apache.iceberg.aws.s3.S3FileIO" }
}

握手是 REST catalog「服务端集中治理、客户端薄」的起点:连配置都不让客户端自己拍板。

4.6 认证

REST 规范本身不绑死某一种认证,但约定了 OAuth2 风格的 token 流:客户端用 token 或走 OAuth2 客户端凭证拿到 Bearer token,之后每个请求带 Authorization: Bearer …。服务端据此识别调用者身份,再决定:能不能访问这个 namespace/表、loadTable 该派发什么作用域的存储凭证。这把「身份认证」与「存储授权」都收到了 catalog 服务端,是与 HMS/JDBC 时代「客户端各自持密钥」最大的治理差异。

4.7 多表事务

单表提交是 POST …/tables/{table}。规范还定义了事务端点POST /v1/{prefix}/transactions/commit),允许在一个请求里携带多张表的 requirements + updates,由服务端原子地一起提交——要么全部成功,要么全部失败。这把 Nessie 的「多表原子」能力纳入了标准接口(具体是否支持取决于服务端实现)。对「一批维表必须和事实表同时切版本」的场景,这是单表 CAS 给不了的。

4.8 一次提交冲突的完整往返

把前面拼起来,看一次「我和别人同时提交」的真实过程。两个 writer A、B 都基于 main = snapshot 100 计算各自的新 snapshot。A 先提交:

POST /v1/prod/namespaces/db/tables/events
Authorization: Bearer <A 的 token>

{ "requirements": [{"type":"assert-ref-snapshot-id","ref":"main","snapshot-id":100}],
  "updates":      [{"action":"add-snapshot", "snapshot":{"snapshot-id":101, ...}},
                   {"action":"set-snapshot-ref","ref-name":"main","type":"branch","snapshot-id":101}] }

服务端校验 main 确实是 100,接受,main 推进到 101,A 拿到 200。随后 B 提交同样的前提(它也以为 main 是 100):

POST /v1/prod/namespaces/db/tables/events
{ "requirements": [{"type":"assert-ref-snapshot-id","ref":"main","snapshot-id":100}],
  "updates":      [ ... 把 main 推到 B 的 102 ... ] }

此刻 main 已经是 101,assert-ref-snapshot-id 失败,服务端返回冲突:

HTTP/1.1 409 Conflict
{ "error": { "message": "Requirement failed: branch main has changed", "type": "CommitFailedException", "code": 409 } }

B 必须重新基于 101 算出新 snapshot 再试。这就是乐观并发的全貌:不加悲观锁,谁先到谁赢,输的一方重试。冲突能否自动重试取决于变更是否仍可在新基线上成立(纯 append 几乎总能重试成功;改了同一批行的操作可能需要重新计算)。这套语义和 第 11 章 的提交协议是同一件事,只是搬到了 HTTP 契约上。


五、Apache Polaris

5.1 来历与定位

Polaris 由 Snowflake 发起并开源(GitHub 仓库 2024 年 5 月建立),2024 年 8 月 9 日进入 Apache 孵化器,后于 2026 年初毕业为 Apache 顶级项目,Apache 2.0 许可。它的一句话定位:一个厂商中立、实现 Iceberg REST Catalog API 的开源 catalog,目标是让 Spark、Flink、Trino、Doris、StarRocks、Dremio 等引擎通过同一份 REST 契约共享 Iceberg 表。

换句话说,Polaris 不发明新的 catalog 协议,它就是 Iceberg REST Catalog 规范的一个完整服务端实现,外加一套管理与权限 API。

5.2 两套 API

Polaris 对外暴露两类 API(均有 OpenAPI 规范):

API 作用
Iceberg REST Catalog API 引擎读写表走这套,即第四节的标准端点
Polaris Management API 管理 catalog、principal、role、grant 等治理对象

引擎只认前者,所以任何支持 Iceberg REST catalog 的引擎开箱即用;治理操作走后者,由平台管理员使用。

5.3 内部 catalog 与外部 catalog

Polaris 区分两种 catalog:

这让 Polaris 既能做新表的权威 owner,也能纳管存量。

5.4 权限模型(RBAC)

Polaris 的治理是基于角色的访问控制(RBAC),层级大致是:

flowchart TD
  P[principal 主体] -->|被授予| PR[principal role]
  PR -->|被授予| CR[catalog role]
  CR -->|持有| GR[privileges 授权]
  GR --> OBJ[作用对象: catalog / namespace / table]

授权链是:principal → principal role → catalog role → privilege on object。配合凭证派发,Polaris 可以做到「principal X 只能读 db.events,且只拿到读该表数据前缀的临时存储凭证」。

5.5 与 REST 规范的关系

一句话:Polaris = Iceberg REST Catalog 规范的实现 + 治理层。它对 Iceberg 生态的兼容性来自「严格实现规范端点与提交语义」,互通性是规范给的,不是 Polaris 私有的。这也是它能被 ASF 接纳、被多引擎直接对接的根本原因。


六、Unity Catalog(OSS)

6.1 来历与定位

Unity Catalog 由 Databricks 在 2024 年 6 月(Data + AI Summit)开源,Apache 2.0 许可,托管在 Linux Foundation 旗下的 LF AI & Data。与 Polaris「专注 Iceberg catalog」不同,Unity Catalog 的定位更宽:面向数据与 AI 的统一治理目录,治理对象不止表,还包括卷(volumes,非结构化文件)、函数、AI 模型。

6.2 多协议兼容

Unity Catalog OSS 的关键设计是同时兼容多套接口标准

凭证派发同样是核心能力:客户端访问底层云存储要先经 Unity Catalog 鉴权,由它发放受限凭证。

6.3 三层命名空间与治理对象

Unity Catalog 采用 catalog.schema.table 三层命名空间,治理对象比纯表 catalog 丰富:

对象 说明
catalog 顶层命名空间
schema 中层(即 namespace)
table 表(Delta / Iceberg via UniForm / Parquet / CSV / JSON …)
volume 非结构化数据(文件目录)
function UDF
model AI / ML 模型

这反映了 Databricks 的视角:治理边界不止「表」,而是「数据 + AI 资产」。

6.4 与 REST 规范的关系

Unity Catalog 实现了 Iceberg REST Catalog API 作为其多协议的一面,但它本身有更大的 API surface(卷、函数、模型、Delta 专属能力)。所以:

需要强调版本边界:Unity Catalog OSS 的 API 在 0.x 阶段明确标注「仍在演进、不应假设稳定」。本节描述的是其设计意图与公开能力,具体端点行为以所用 OSS 版本为准。

6.5 Polaris vs Unity Catalog

维度 Apache Polaris Unity Catalog (OSS)
发起方 Snowflake Databricks
治理归属 Apache 软件基金会 LF AI & Data(Linux Foundation)
开源时间 2024(08 入 ASF 孵化) 2024-06
许可 Apache 2.0 Apache 2.0
核心定位 Iceberg REST catalog + 治理 数据 + AI 统一治理目录
治理对象 catalog/namespace/table + volume/function/model
Iceberg REST 原生实现 实现之一(多协议)
HMS 兼容 否(聚焦 REST)
多格式 Iceberg Delta / Iceberg(UniForm) / Hudi / Parquet…
权限模型 principal/role/privilege RBAC 三层命名空间 + 权限

二者都实现 Iceberg REST Catalog API,所以从 Iceberg 引擎看,它们是可替换的 catalog 后端。差异在治理广度(Unity 更宽)与生态中立性叙事(Polaris 进 ASF)。选型时真正要问的是:你的资产是否只有 Iceberg 表(偏 Polaris),还是要统一管 Delta + 文件 + 模型(偏 Unity)。


七、本章实验用到的 catalog

为了让 第 16 章第 17 章 的实验可复现,本系列实验统一用 PyIceberg 的 SQL catalog(SQLite 后端)——它正是 3.2 节 JDBC catalog 思路的轻量版:指针存在 SQLite 一张表里,提交靠单语句条件更新。环境与版本:

OS:       Arch Linux on WSL2, kernel 6.6.87.2-microsoft-standard-WSL2
CPU:      12th Gen Intel Core i9-12900K (24 logical)
Python:   3.14.5
PyIceberg: 0.11.1
PyArrow:  24.0.0
对象存储:  本地文件系统(file://)替代 S3,便于离线复现

最小可运行配置(后续两章复用):

from pyiceberg.catalog.sql import SqlCatalog

catalog = SqlCatalog(
    "demo",
    uri="sqlite:////tmp/ice_wh/catalog.db",   # JDBC 思路:指针存这里
    warehouse="file:///tmp/ice_wh",            # 数据与 metadata 落这里
)
catalog.create_namespace("db")

换成生产里的 REST catalog(如 Polaris / Unity / 任意合规实现),客户端只需改 catalog 配置,表读写代码不变——这正是 REST 规范「解耦」承诺的直接体现:

from pyiceberg.catalog.rest import RestCatalog

catalog = RestCatalog(
    "prod",
    uri="https://polaris.example.com/api/catalog",
    token="…",
    warehouse="my_catalog",
)

说明:本章是 catalog 的形态与语义对比,结论锚定各 catalog 的接口定义与 Iceberg REST 规范;不构造性能数字。涉及实测的提交/读写在第 16、17 章用上面的 SQL catalog 真跑。


八、把表注册进 catalog

选定 catalog 后,存量表与跨 catalog 迁移是绕不开的实操。Iceberg 提供几条路径,差别在「动不动数据文件」「指针归谁」:

操作 做什么 是否重写数据 典型场景
register_table 把一个已有 Iceberg 表的 metadata.json 登记到目标 catalog 跨 catalog 搬迁同一张 Iceberg 表
add_files 把已存在的 Parquet/ORC 文件「收编」进 Iceberg 表的 manifest 否(只建元数据) 把目录式数据快速纳管
snapshot 为 Hive 表创建一个 Iceberg 的「影子」表做验证 否(共享数据文件) 迁移前试跑、不影响原表
migrate 原地把 Hive 表转成 Iceberg 表 否(in-place,复用文件) 正式迁移 Hive 表
重写导入 读源表,按新表写入 需要顺带改文件布局/分区

要点与风险:

flowchart TD
  H[已有 Hive 表] -->|snapshot 影子表| V[验证查询正确]
  V -->|通过| MIG[migrate 原地转 Iceberg]
  P[已有 Parquet 目录] -->|add_files| ICE[纳管为 Iceberg 表]
  E[别处的 Iceberg 表] -->|register_table| TGT[目标 catalog]

九、选型与陷阱

把前面的语义落到决策上:

场景 倾向 理由
遗留 Hadoop/Hive 数仓迁移 HMS(过渡)→ REST 生态惯性,但应规划迁出
已有可靠 RDB、单团队 JDBC 最轻的正确实现
本地/HDFS 单写入者、测试 Hadoop(file) 简单,勿上 S3 并发
深度 AWS Glue 与 Athena/EMR/Lake Formation 打通
需数据版本化/分支/跨表原子 Nessie 类 git 能力独一份
多引擎互通、要标准契约 REST(Polaris/Unity/其他) 客户端解耦、凭证派发
只有 Iceberg、要中立 Polaris 专注 + ASF
统一管 Delta+文件+模型 Unity Catalog 治理面最宽

常见陷阱:


十、小结

下一章把视角转向时间维度:有了 catalog 提供的快照指针,如何做时间旅行、回滚、以及不靠位置而靠 field ID 的 schema 演进。


返回 系列目录 | 上一篇:三者对照与互通 | 下一篇:时间旅行、Schema 与分区演进

参考资料

  1. Apache Iceberg, REST Catalog OpenAPI Specapache/icebergopen-api/rest-catalog-open-api.yaml)— A 级。
  2. Apache Iceberg, Spec(table spec V2)与 Catalog / TableOperations 接口定义 — A 级。
  3. Apache Iceberg 文档, Hive, JDBC, Hadoop, AWS(Glue) catalog 章节 — A 级。
  4. Apache Polaris 官网与 apache/polaris 仓库、Apache 孵化器状态页(2024-08-09 进入孵化)— A 级。
  5. Unity Catalog OSS, unitycatalog/unitycatalog 仓库与 Databricks 开源公告(2024-06,Apache 2.0,LF AI & Data)— A/B 级(公告为 B 级,仓库/规范为 A 级)。
  6. Project Nessie 官方文档(branch/tag、多表提交语义)— A 级。
  7. Apache Gravitino(孵化中)官方文档 — A 级。
  8. 本机实验:PyIceberg 0.11.1 + SQLite SQL catalog(环境见第七节)— A 级(实测)。

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-30 · database / storage

【数据湖与开放表格式】提交协议与并发控制

没有数据库进程,Iceberg 怎么在对象存储上做原子提交与并发控制?拆解提交=catalog 对元数据指针做 compare-and-swap,乐观并发如何基于当前 snapshot 生成新 snapshot、冲突按操作类型与隔离级别重试,不同 catalog 的原子性来源(DB 行锁/CAS、REST 后端、对象存储条件写、文件系统 rename),以及 REST Catalog 的 requirements+updates 提交语义。基于 pyiceberg 0.11.1 实测并发冲突与重试。

2026-06-30 · database / storage

【数据湖与开放表格式】Lakehouse 全景:从 Hive 表到开放表格式

Hive 目录式分区表把『表』等同于『一组目录加 metastore 里的分区行』,于是没有原子提交、planning 要 LIST 目录、schema 与分区演进常要重写。本文用这三个硬伤切入,讲清 lakehouse 把表拆成『不可变数据文件 + 可变元数据指针 + catalog』三层后各自解决了什么,并给出全系列的分层地图。

2026-06-30 · database / storage

【数据湖与开放表格式】Parquet 文件格式深拆

拆 Parquet 的物理结构:file → row group → column chunk → page,footer 里的 FileMetaData(Thrift)与 PAR1 magic。讲清 PLAIN/RLE-bitpacking/字典/DELTA_BINARY_PACKED/BYTE_STREAM_SPLIT 各自压谁,Dremel 的 repetition/definition level 如何表达嵌套,column index/offset index 与 split-block bloom filter 怎样让谓词在读盘前裁掉 page。基于本机 pyarrow 24.0.0 真实 dump footer 与编码。

2026-06-30 · database / storage

【数据湖与开放表格式】ORC 文件格式与 Parquet 对照

ORC 用 stripe 而非 row group、用三级统计(file/stripe/row-group index)而非独立 page index、用 PRESENT/DATA 等 stream 而非 page 组织一列。本文按 ORC 规范拆其文件尾(postscript + footer)、stripe 内部结构与 RLEv2 整数编码,并用本机 pyarrow 24.0.0 把同一份 30 万行数据写成 ORC 与 Parquet,对比真实体积与物理布局,最后给出什么场景仍用 ORC。


By .