打开网易新闻 查看精彩图片

一、一个 7.4MB 的仓库,想动数据湖的地基

先说一个数字:404。

不是网页报错。DuckLake 这个项目,GitHub 仓库的根目录下没有 README.md——直接去抓 raw 路径会拿到一个 404,我实测过。它不在根目录,它躺在 docs/ 里面。

这个细节本身就是它的自我介绍:它不打算做第二个数据湖产品,它要做的是把数据湖和目录格式焊在一起的那一层。根目录里躺着的是 CMakeLists.txt、extension_config.cmake 和一堆测试目录——这是一个 DuckDB 扩展的骨架,不是一个能 clone 下来读介绍的项目。项目地址是纯文本形式:github.com/duckdb/ducklake。

GitHub API 的实测数据摆在这里:

指标

实测值

星标

3,269

Fork

275

主语言

C++

仓库体积

7,427 KB(约 7.4MB)

许可

MIT

建仓

2025-03-03

最近 push

2026-10-09

这里的 7.4MB 取的是 GitHub API 的 size 字段,是官方可复现的压缩后仓库体积,不是克隆到本地解压后的大小。

7.4MB 是什么概念?它想去替代的那套目录体系,不用比别的——光是它自己官方文档里的一个演示,用 Iceberg 连续插 100 次数据,就能产出 401 个文件。

而它在 Hacker News 上的热度是这样的:2026 年 10 月 7 日,有人把仓库地址丢上去,拿到 233 分、33 条评论。分数不低,但评论只有 33 条。作个对照——2025 年 5 月它首次亮相时是 276 分、107 条评论。一年半过去,分数几乎没动,讨论量掉了三分之二。老读者早就聊过了,新读者还没被说服。

二、拆开看,它把目录从对象存储里搬走了 两件事,存进两个地方

DuckLake 的自述是集成式的数据湖与目录格式。它把传统数据湖拆成两半,放进两个完全不同的地方:

• 元数据(目录)——放进一个普通的 SQL 数据库。PostgreSQL、MySQL、SQLite,或者 DuckDB 本体都行。

• 数据——还是 Parquet 文件,放本地磁盘或者对象存储。

官方 FAQ 的说法是:既需要数据湖、又需要目录的时候,它是一个轻量的一站式方案,不需要自建目录服务。

Iceberg 和 Delta Lake 把元数据也当成文件写进对象存储:Iceberg 每提交一次写一组 Avro manifest 加一个 metadata.json,Delta 每提交一次往 _delta_log 里追加一条 JSON。目录服务因此变成了另一套需要单独运维的东西,而且这些元数据文件的体积会随着提交次数一路增长。

401 个文件是怎么来的

DuckLake 官方在讲数据内联的那篇博文里,给了一个特别直观的对照。用 pyiceberg——Iceberg 的 Python 客户端——连续插入 100 次,每次一行观测数据,结果是:

产出

数量

Parquet 数据文件

100 个

Avro 元数据文件

200 个

JSON 元数据文件

101 个

合计

401 个文件

官方贴出的目录树末尾写得很清楚:5 directories, 401 files。

100 行数据,401 个文件,平均一行数据摊上 4 个文件。这还只是这一张表。它带来的连锁反应是:查询要先沿着一长串元数据文件走一遍才知道该读谁;存储账单按对象数量涨;你还得定期跑合并任务把碎文件收起来。这就是数据湖圈子里常说的"小文件问题"。

DuckLake 的解法叫数据内联:既然目录本身就是一个数据库,那小的写入直接写进目录表里就行,不必立刻落成 Parquet,等积累到一定量再统一刷盘。

我在本机实测了一遍,结果是 0

这一条不是转述官方文档。我在本机装了 DuckDB 1.5.6,装上 ducklake 扩展,跑了官网首页那段最短的示例:

INSTALL ducklake;ATTACH 'ducklake:metadata.ducklake' AS my_ducklake (DATA_PATH 'file_path/');USE my_ducklake;CREATE TABLE my_ducklake.my_table(id INTEGER, val VARCHAR);INSERT INTO my_ducklake.my_table VALUES (1, 'Hello'), (2, 'World');FROM my_ducklake.my_table;

两行数据插进去,然后看磁盘上到底发生了什么:

项目

实测结果

Parquet 数据文件

0 个

磁盘上的文件总数

2 个

整个数据集占用

25,628 字节

其中目录文件 metadata.ducklake

12,288 字节

一个 Parquet 文件都没写。我去查了 snapshots() 这张表,原因写得明明白白——那次插入被记录成 inlined_insert,数据直接存在了目录库里。

那到底什么时候才落盘?阈值我也试出来了:

单次插入行数

生成的 Parquet 文件

1 行

0 个

5 行

0 个

10 行

0 个

11 行

1 个

50 行

2 个

阈值来自一个可以直接查的设置项:ducklake_default_data_inlining_row_limit = 10。默认 10 行以内不落盘,第 11 行才写出第一个 Parquet 文件。

用起来还是普通 SQL

DuckLake 没有发明新语法。挂载之后的读写、时间旅行、变更捕获,全是标准 SQL:

-- 更新一行UPDATE my_ducklake.my_table SET val = 'DuckLake' WHERE id = 2;-- 回看第 2 个快照,也就是时间旅行FROM my_ducklake.my_table AT (VERSION => 2);-- 拉出快照 2 的变更流FROM my_ducklake.table_changes('my_table', 2, 2);

换目录库也只是改 ATTACH 的字符串:

-- 用 PostgreSQL 当目录ATTACH 'ducklake:postgres:dbname=ducklake_catalog host=your_postgres_host' AS my_ducklake (DATA_PATH 'data/');-- 用 SQLite 当目录ATTACH 'ducklake:sqlite:metadata.sqlite' AS my_ducklake (DATA_PATH 'data/');

版本和许可也交代清楚:DuckLake v1.0 在 2026 年 4 月发布,是承诺向后兼容的正式版本,需要 DuckDB v1.5.2 以上;规范和扩展都是 MIT 许可。GitHub 仓库里没有 Release 页,它跟着 DuckDB 的扩展分发体系走,而不是自己发安装包。

有代价要提前讲:作为数据湖格式,它不支持索引、主键、外键,也不支持 UNIQUE 和 CHECK 约束。这是格式层面的取舍,不是实现没做完。

贡献者名单也很集中:pdet 1,903 次提交、Mytherin 664 次、PeterVanHolland 223 次,其余都在 160 次以下。前两位是 DuckDB 的核心开发者 Pedro Holanda 和 Mark Raasveldt。换句话说,DuckLake 是 DuckDB 团队自己的作品,不是社区外挂。

三、926 倍的说法,和第三方实测的 2 到 4 倍

DuckLake 团队在宣布数据内联时给了一个很响的宣称:查询快 926 倍,写入快 105 倍,对比对象是 Iceberg。

这个数字值得看,但不该直接抄进结论。先看口径,官方自己写得很坦率:这个基准测的场景就是"100 次小批量插入"。也就是说,926 倍是特定写模式下的数字,不是"什么都快 926 倍"。

第三方验证给出了更收敛的答案。有位工程师在单机上做了完整复现,用对象存储后端,对比"10 万行一次性批量提交"和"100 次流式提交":

指标

Iceberg

DuckLake

写入耗时

0.44 秒 → 16.3 秒,约 37 倍

2.9 秒,约快 5.6 倍

查询规划

8.7 毫秒 → 181 毫秒,约 21 倍

稳定在约 7 毫秒

元数据规模

4 个文件 8.9KB →

301 个文件 4,579KB

无按提交次数增长的元数据文件

开启内联后的小提交

不适用

写入 0 个 Parquet 文件

同一个人还测了另一个方向:10 亿行、两边读同一批字节完全相同的 Parquet,四个查询里有三个速度基本打平。他的原话意思是,DuckLake 赢在平的目录元数据和被内联的小提交,这是一个写路径的故事,不是读取速度的倍数。

第三份数据来自一个生产环境的实现方。做 AI 智能体基础设施的公司 Hotdata,把 DuckLake 实现到了 Apache DataFusion 这个 Rust 写的查询引擎上,已经跑在生产里,后来把代码捐给了 Apache DataFusion 社区仓库。他们用全部 22 条 TPC-H 查询、三个数据规模,对比"DuckLake 管理的表"和"直接读 Parquet":

数据规模

DuckLake 管

直接读 Parquet

比值

SF0.2

1,440 毫秒

1,474 毫秒

0.98 倍

SF1

4,398 毫秒

4,461 毫秒

0.99 倍

SF10

50,932 毫秒

53,942 毫秒

0.94 倍

他们的结论是"元数据层没有带来可测量的性能损耗",甚至还略快一点——因为 DuckLake 已经知道一个快照对应哪几个文件,省掉了通用 Parquet 表那一步元数据发现。

三份数据放在一起,画面就清楚了:

• 官方的 926 倍,是小批量写入这个最有利场景下的上限;

• 第三方复现把它压到了 2 到 4 倍,但方向完全一致;

• 生产环境的实现方测出来的是查询侧几乎没有代价。

没有任何一个人在说"DuckLake 读得快"。三个人都在说同一件事:它的目录不再拖后腿。这才是这个格式真正的卖点,也是它比宣传口径更可信的地方。

它也不是没有代价。并发就是那个代价。同一位工程师把写入进程从 1 个加到 16 个,让它们并发提交小批次:

写入进程数

提交延迟 p95

1 个

约 14 毫秒

16 个

约 662 毫秒,拉长约 45.8 倍

吞吐守住了每秒约 9,500 行,全程零重试、零硬错误。逻辑也不难理解:目录变成了一个大家都要排队的 SQL 数据库,这正是它换来 ACID 和跨表原子性的代价。把元数据从一堆文件换成一个数据库,就是把一致性问题交给了数据库去解决,连同它的排队一起交了出去。

四、这件事和你有什么关系

先给一句判断:DuckLake 想解决的不是"我的查询慢了",而是"我不想再养一套目录服务了"。

该看的场景。如果你手上只有 DuckDB 一个引擎,又需要多个进程读写同一份数据——DuckDB 原版并不支持这种并发,DuckLake 恰好把这个缺口补上了。如果你的写入是小批量、持续性的,比如传感器数据、日志、智能体落库,那么"10 行以内不落盘"这个特性直接帮你省掉了小文件合并这个维护动作。

该等的场景。如果你的数据湖要同时被 Spark、Trino、Flink 读,DuckLake 目前是以 DuckDB 为中心的。但这条正在松动:2026 年 7 月,Apache DataFusion 已经出现了 DuckLake 实现并捐进了社区仓库。再加上它的数据文件本身就是 Iceberg 兼容的 Parquet,迁移的门是留着的。

另一条必须知道的消息。2026 年 8 月 26 日,DuckDB 背后的公司 DuckLabs 宣布加入 AWS,作为子公司运营。官方的说法是项目"不会有任何变化"——DuckDB、DuckLake、Quack 以及所有扩展继续由非营利的 DuckDB 基金会管理,保持 MIT 许可。一个格式的长期命运,往往不取决于它写得好不好,而取决于它背后那个组织的结构。这一条值得记在心里。

最后说一个和选题本身有关的观察。半个月前,同一个方向刚出过一条热点:Polars 2.0 扶正 SQL,跑赢了 DuckDB。那条讲的是别人在 DuckDB 的主场赢了它。这条正好相反——DuckDB 团队自己动手,把数据湖的目录层换了地基。一个是在别人的赛道上被追上,一个是自己在拆自己的旧房子。两条对照着看,比单看任何一条都有意思。

五、你会为少写几个文件换掉 Iceberg 吗

回到最开始那个数字:一个 7.4MB 的扩展,去替一套"100 行数据摊成 401 个文件"的目录体系。听起来不成比例,但这笔账其实算得通——它没有去比谁读得快,而是把"元数据也当数据存进对象存储"这个历史包袱直接卸掉了。

它当然有软肋:目录就是单点,写入要排队,生态还没铺开。

所以问题留给你:你现在这套数据湖,元数据到底搁在哪儿?有没有为小文件跑过合并任务?如果这两件事你都答不上来,那你可能还没到需要换格式的时候;如果你每个季度都在跑合并,那 DuckLake 那条"第 11 行数据"的规则,值得你花半小时试一下。