一、一个 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 行数据"的规则,值得你花半小时试一下。
热门跟贴