目录是把一个名字连到一个元数据文件的原子指针
一句话总结
Catalog 里保存着“表名 → 当前 metadata.json 路径”这一栏,它通过只有读到的值没变才修改的条件式替换,来负责提交的原子性和并发写入的裁决。名称、位置、文件是彼此独立的不同层。
为什么需要 Catalog
正如上一个模块所见,Iceberg 表的状态由一个 metadata.json 文件决定。但是 metadata 文件每次提交都会新生成一个。这样就得有人来告知“现在的是哪个文件”,而且当两个写入方同时提交时,只能接受其中一方。
最初,这件事由文件系统来做。规范中的文件系统表方式,是用名为 v<V>.metadata.json 的固定名称尝试重命名(rename),先成功的一方获胜。在 HDFS 这种 rename 是原子的地方,这行得通。但规范现在把这种方式标为即将弃用,并警告它在对象存储和本地文件系统上并不安全。因为 S3 没有原子的 rename。
工作原理——条件式替换
于是标准变成了元存储表方式:把指针放在数据库或服务里,用 check-and-put 来修改。
- 读取当前的 metadata(版本 V)。
- 以它为基础,写出新的 metadata
<V+1>-<uuid>.metadata.json。 - 向 Catalog 请求“如果指针仍然是 V,就改成 V+1”。
- 成功则提交完成。失败说明有人先创建了 V+1,就从第 1 步重新开始。
遵守这份契约的实现有好几种。
| Catalog | 指针所在的位置 | 条件式替换 |
|---|---|---|
| JDBC | 数据库表 iceberg_tables 的一行 |
只有 metadata_location 与读到的值相同时才 UPDATE |
| REST | Catalog 服务器 | 由服务器检查要求条件后提交 |
| Hive 元存储 | HMS 表属性 metadata_location |
连同元存储锁一起替换 |
JDBC Catalog 文档指出,所连接的数据库必须支持原子事务,才能保证原子提交和可串行化读取。本课程的实验环境把这张表放在一个 SQLite 文件里,Spark(Java 的 JdbcCatalog)和 pyiceberg(SqlCatalog)打开的是同一个文件。不用启动服务器,两个引擎也能共用一个 Catalog。
REST Catalog 规范把提交分成要求条件(requirements)与变更(updates)两部分。服务器先检查 assert-ref-snapshot-id 这类要求条件,不符合就返回 409 CommitFailedException,客户端可以重新尝试。由服务器来接收提交,客户端就不必直接持有存储凭据,而且也有一次性原子地提交多张表的途径。
Hive 元存储便于与现有的 Hive 生态对接。如 Hive 文档中的示例,指针放在 HMS 的表属性 metadata_location 里。不过,HMS 是为 Hive 而不是为 Iceberg 打造的服务,要运维的部件会增多,同一份文档还指出,跨多张表的 INSERT 不是原子的。
名称、位置、文件是不同的层
Catalog 的一行只把名称和 metadata 路径连在一起。所以下面这些成立。
- 重命名只修改那一行中的名称。表的位置和文件保持不变。重命名后的表继续使用旧名称的目录,是正常的。
- 不带 PURGE 的 DROP TABLE 只删除那一行。metadata 和数据文件仍保留在磁盘上。
- register_table 会给一个已有的 metadata.json 赋予新名称。因为 metadata 文件包含了 schema、快照和文件列表的全部内容,所以只要有一个路径,表就能复活。同一份文档警告说,把同一个 metadata 同时注册到两个 Catalog,更新可能会丢失,表也可能损坏——因为指针有两个,裁判也就有两个。
在现场相遇的样子
迁移 Catalog。从 Hive 元存储迁移到 REST Catalog 时,数据一个字节都不用动。逐张表读取当前的 metadata 路径,注册到新的 Catalog 中,再在旧 Catalog 中不带 PURGE 地删除。关键是在迁移期间,要防止两个 Catalog 同时向同一张表写入。
误删的表。有人在 DROP TABLE 之后发现空间没有减少而感到慌张,也有人反过来认为全都没了而放弃。如果当时没有加 PURGE,只凭一个 metadata 文件路径就能把表恢复。
实际工作中真正重要的事
- Catalog 的工作就是条件式替换。在无法原子修改的存储之上,表会损坏。
- 引擎有多个时,Catalog 必须只有一个。如果两个 Catalog 指向同一张表,提交会相互覆盖。
- 名称只存在于 Catalog 中。改了名称,文件也不会被移动。
- PURGE 无法撤销。不带 PURGE 的 DROP,只要有 metadata 路径就能恢复。
下一项实验要做什么
让 Spark 在 JDBC Catalog(SQLite)中创建表,用 pyiceberg 打开同一个 Catalog,查看那张表并创建新表,再由 Spark 把两个引擎创建的表做连接。记下提交前后 Catalog 那一行的当前和之前路径是如何移动的,对表重命名并确认位置不变,不带 PURGE 删除之后数一数剩下的文件,再用 register_table 从一个 metadata 文件中恢复这张表。