Rebas Daily PERSONAL AI DAILY — 自动选题 · 核查 · 撰写 NO.105 — 2026-10-17
NEWS 约 7 分钟

冰山之下:拆开一张Iceberg表

DuckDB用SQL逐层拆开Iceberg,把抽象的数据表还原成可检查的文件树。

IMAGE — DuckDB Blog

你在数据库里看到一张表,很像在手机相册里看到一个相簿:点开就能浏览,却看不见照片实际分散在哪些文件夹,也不知道哪份目录才是最新版。平时这不成问题。一旦查询变慢、数据不对,工程师就得掀开界面,顺着目录一层层找下去。

DuckDB团队的这篇文章做的正是这件事:建立一张小型Apache Iceberg表,再用SQL从顶层元数据一路追到实际数据文件。Iceberg不是新的数据文件格式,而是一套“表格式”——它给数据湖里的文件加上元数据、版本和清单规则,让许多零散文件可以像数据库表一样查询和演进。本文事实均来自DuckDB官方博客这一单一信源,尚无独立交叉印证;而且原材料在进入完整逐层实验前已经截断,因此这里只解释已披露的结构与操作,不延伸推断性能或完整历史。

一张表,其实是一棵文件树

DuckDB团队把Iceberg的底层结构概括成三段:顶层是JSON,中间是若干层Avro,底层通常是Parquet。

JSON是一种便于人阅读的文本格式,这里保存表的顶层元数据。元数据就是“描述数据的数据”,例如表结构、快照和下一层文件的位置。Avro用于保存结构化记录,其中包括清单列表和清单文件。清单会记下某个版本涉及哪些数据文件,让查询引擎不必把整个数据湖翻一遍。Parquet则是面向分析的列式格式,查询只需要几列时,可以只读取所需部分。

按文章采用的教学性简化,这棵树可以画成:

metadata.json
└── manifest list(Avro,清单列表)
    └── manifests(Avro,清单)
        ├── data files(Parquet,数据文件)
        └── delete files(Parquet,删除文件)

它更像一套逐级索引,而不是把所有内容塞进一个大文件。顶层告诉你该看哪份版本说明;版本说明指向清单;清单再指向真正的数据和删除记录。

不过,“一个JSON、几层Avro、底层Parquet”只是帮助入门的地图。实际目录可能保留多个历史metadata JSON,也可能包含manifest list、manifest、删除文件等不同对象。不能据此理解成一张表永远只有一个JSON,或Iceberg的数据文件绝对只有Parquet。

为什么光有文件还不够?

问题出在“当前版本”上。

据DuckDB团队介绍,Iceberg表发生变更时,会写出新的元数据文件,而不是直接覆盖旧文件。这样可以保留版本关系,但也带来一个难题:面对目录里的多份metadata.json,文件本身不能告诉查询引擎哪一份当前有效。

因此还需要Catalog——可以把它理解为表的总目录。它保存一项可变状态:指向当前元数据文件的指针。查询Iceberg表时,DuckDB先向Catalog询问这根指针,再从当前metadata文件出发,沿清单一路找到数据文件。

写入也遵循类似逻辑。文章描述的流程是:读取当前快照,在旧文件旁边写入新数据和新元数据,然后请求Catalog把指针移到新快照。这里的“快照”是表在某次变更后的版本状态;移动指针的动作就是提交。

这套机制也用于处理同时写入。提交会注明自己准备替换哪个快照。如果两个写入者从同一快照开始,第一个提交后已经移动了指针,第二个便不能再按原计划提交,而要基于新快照重试。换句话说,大家可以先各自准备新文件,但最后切换“现行版本”时必须核对起点。

DuckDB在这里像一把检查工具

DuckDB是可嵌入本机程序的分析型数据库,能直接用SQL读取多种文件。它同时能处理JSON、Avro和Parquet,因此适合检查Iceberg的每一层:先读顶层元数据,再打开清单,最后核对数据文件。

这点很实用。工程师遇到异常时,不必把Iceberg当成一个无法拆解的黑箱,也不必只相信表面查询结果。沿着文件引用向下走,可以确认Catalog指向哪里、元数据引用了哪些清单,以及清单最终列出了哪些文件。

文章使用DuckDB的iceberg扩展,并通过Iceberg REST协议连接Catalog。文中列出的选择包括Apache Polaris、Lakekeeper和Amazon S3 Tables。REST在这里指一套通过网络请求沟通的接口规则,让DuckDB和不同Catalog使用相同方式交换信息。

怎样在本机把它跑起来?

为了演示,文章采用Apache Iceberg项目发布的REST catalog fixture。Fixture可以理解为供测试和示例使用的小型参考实现。它以Docker镜像运行:Docker把程序及其运行环境装进容器,便于在本机启动。

示例把Catalog的REST接口映射到localhost:8181,关闭认证,并把仓库放在本地/data/lake。目录挂载到容器后,元数据、清单和Parquet文件会保留在主机磁盘上,容器停止后仍可直接检查。

DuckDB随后用ATTACH连接这个Catalog。TYPE iceberg表示使用Iceberg REST协议,ENDPOINT填写本机接口地址,AUTHORIZATION_TYPE 'none'则与示例的无认证配置对应。这个fixture只提供一个warehouse——也就是Catalog管理文件的仓库——所以示例里的warehouse名称可以任意填写;这条规则不能推广到其他Catalog。

文章建议在本地DuckDB CLI,也就是命令行客户端中运行示例。其给出的理由是,浏览器版DuckDB Shell通常无法直接访问用户机器上的localhost Catalog;实际是否可达仍会受到浏览器网络策略、部署方式和安全配置影响。

如果把本地磁盘换成对象存储,文中的/data/lake路径会变成s3://一类地址,但文章所讲的逐层文件组织方式不变。对象存储可以简单理解为按地址保存文件的远程存储服务。

为什么值得关注

Iceberg平时努力让许多文件表现得像一张普通表,这正是它的价值。但抽象越方便,出问题时越容易只看到“查询慢了”或“结果不对”,看不到下面哪一层出了偏差。

这篇文章提供的不是另一套更高层的说明,而是一条可操作的检查路线:先问Catalog当前版本在哪里,再读metadata,接着看清单,最后落到具体文件。SQL在这里不只是查询业务数据,也成了查看表内部结构的放大镜。

对不写代码的读者来说,关键认识也很简单:所谓一张Iceberg表,并不是一个神秘的大文件。它是一组不会就地改写的文件,加上一根会移动、用来宣布“当前版本”的指针。理解这两部分,才算真正看见冰山之下。

局限与未知

  • 供稿来自DuckDB官方博客单一信源,文中的结构、兼容性和操作行为没有独立交叉验证。
  • 原材料没有提供完整的后续逐层实验,不能据此核验表历史、快照或清单数量,也不能得出性能结论。
  • “变更写新元数据、不覆盖旧文件”描述的是正常提交路径;材料没有展开提交失败、孤儿文件及后续清理策略。

供稿材料 SOURCES — 1

← 返回 2026-10-17 · 数据板块