浏览器里的 DuckDB 2.0:超内存查询自动落盘
在浏览器里跑分析 SQL 的同学,多半撞过同一堵墙:数据集一大,DuckDB-Wasm 就 OOM。WebAssembly 单实例最多 4GB 内存,部分浏览器给得更紧,原生 DuckDB 轻松跑完的查询,到了浏览器里直接报错。
这堵墙正在被拆掉。DuckDB-Wasm 2.0 把浏览器引擎换到上个月刚发布的 DuckDB 2.0 LTS 内核:三个安装包砍成两个,超出内存的查询默认自动落盘,HTTP 请求能带鉴权头了。官宣博客已经写好放在仓库里,文档页面已经更新,npm 的正式标签还没翻牌——发布进入最后冲刺。
我把能提前验证的部分都跑了一遍,下面是实测结果。
● ● ●
三个包砍成两个
DuckDB-Wasm 包模型变化:1.x 三包到 2.0 两包
旧版 DuckDB-Wasm 按浏览器能力发三个包:mvp(WebAssembly 1.0 基线)、eh(加异常处理)、coi(加多线程,需要跨域隔离)。你的页面用哪个,由 selectBundle 自动挑。
2.0 把这套砍掉了。现在只剩两个:
base | ||
threads |
mvp 被扔掉的理由很直接:所有现代浏览器都已支持 WebAssembly 异常处理,DuckDB 靠它把 C++ 核心里的错误正常传出来,没有理由再维护一个不带异常处理的基线包。老浏览器跑不了就是跑不了,selectBundle 会直接抛错,不再降级。
我在本地把 @duckdb/duckdb-wasm@next 拉下来跑了一下,getJsDelivrBundles() 返回的确实只剩 base 一个键,指向新命名的 duckdb-base.wasm。产物文件名全部跟着改了:duckdb-browser-base.worker.js、duckdb-threads.wasm、duckdb-browser-threads.pthread.worker.js。手写过 webpack 配置、硬编码过旧文件名的项目,升级时这些路径都得改。
● ● ●
包体小了,Arrow 换成 nanoarrow
包体大小的变化,我拿 jsdelivr 上的产物统计做了对比(都是未压缩体积):
duckdb-mvp.wasm | ||
duckdb-eh.wasm | ||
duckdb-coi.wasm | ||
duckdb-base.wasm | ||
duckdb-threads.wasm |
拿旧的全功能包(coi)比,新包只小了 0.2 MB,谈不上瘦身。真正省掉的是 mvp 那个 37.5 MB 的大包袱——以后不用再发它了。
变小的另一个来源在内部:DuckDB-Wasm 以前链接完整的 Arrow C++ 库来读写 Arrow IPC 格式,2.0 换成了极简实现 nanoarrow。查询结果的序列化、Arrow IPC 的插入读取都走 nanoarrow,CSV 和 JSON 则直接用 DuckDB 自己的解析器。JavaScript 侧的数据协议没变,代码照旧交换 apache-arrow 的表。依赖版本还是得钉在 ^17——我看了 package.json,这条没变,新版 arrow 大版本仍然会导致插入失败。
压缩后实际下载体积会好不少:我对新的 duckdb-base.wasm 跑了一遍 gzip -9,8.0 MB。另外我把两个 wasm 二进制里的版本字符串都抠出来看了,里面嵌的就是 v2.0.0,和文档口径一致。
● ● ●
内存不够,写盘去
DuckDB-Wasm 2.0 溢写机制:内存放不下自动落盘
这是这版最实用的变化。2.0 起,超出内存的中间数据默认溢写到临时目录,很多以前直接 OOM 的查询现在能跑完。
溢写默认开着,写到 js_buffer://tmp——一块由运行时在 Wasm 堆外面持有的缓冲区。两个新的配置项管这件事,我在类型定义里都确认过:
spill: false可以关掉溢写 temporaryDirectory可以把溢写目录指到别处,比如浏览器的 OPFS
后者值得展开:把 temporaryDirectory 指向 opfs:// 路径,溢写数据就落到 Origin Private File System,配合同样是 opfs:// 的数据库路径,整个引擎的持久化都收进浏览器文件系统。上个月官方写过一篇 OPFS 持久化数据库的博客,2.0 在那基础上又加了目录支持、同步文件创建,以及 homeDirectory 配置——设成 opfs://home 后,DuckDB 存在用户旁边的数据(包括持久化的 secrets)跨会话保留。
● ● ●
带鉴权读远端数据
浏览器里读远程文件有个老问题:请求是浏览器发出的,得守浏览器的规矩。以前 DuckDB 通过 httpfs 扩展配置的 Authorization 头传不过去,要鉴权的数据源在浏览器里基本没法直连;请求失败也只报一个笼统的网络错误。
2.0 把 Wasm 的 HTTP 客户端重做了:httpfs 设置的请求头(包括通过 secret 配置的 Authorization)会随请求转发,失败时返回真实的 HTTP 状态码和响应体。官方博客点名了受益场景:Iceberg REST catalog 这类需要鉴权的 API,在浏览器里调试会容易得多。CORS 的限制仍然在,这一点没有魔法。
● ● ●
多线程版能装扩展了
多线程在浏览器里仍是实验特性、默认关闭,但 threads 包现在能加载扩展了——以前做不到。两个包各有自己的扩展平台:threads 用 wasm_threads,base 用 wasm_base,各自加载匹配的构建。
要开多线程,得手动把 threads 包加进 bundle 列表(getJsDelivrBundles() 只返回 base),页面做跨域隔离,再设 maximumThreads。页面没做跨域隔离时 selectBundle 自动回落到 base,同一份代码单线程照跑。
● ● ●
升级前看这里
破坏性变更集中在一处——包的命名和选择:
- 01
mvp、 eh、coi三个包没了。原来的coi换成threads,其余场景用base。只用getJsDelivrBundles()加selectBundle()、不手写包名的项目,不用改。 - 02
产物文件名全部换成新命名,硬编码路径要跟着改。 - 03
扩展平台改名 wasm_base/wasm_threads,wasm_mvp和wasm_eh不复存在。 - 04
异常处理变成硬要求,太老的浏览器直接不支持。
还有一个时间差要提醒:我写这篇时(11 月 4 日),npm 的 latest 标签还停在 1.33.1-dev57.0,2.0 的产物挂在 next 标签上(dev114,昨天刚发)。官方文档页面已经写着"最新稳定版是 2.0.0",官宣博客的 PR 还开着。也就是说你今天 npm install @duckdb/duckdb-wasm,大概率拿到的还是 1.x;要尝鲜得装 @next。等你读到这篇时 latest 可能已经翻牌,装之前看一眼版本号就行。
对在浏览器里做数据分析的开发者,这版值得跟进的排序大概是:超内存自动落盘解决的是最痛的 OOM,鉴权头转发打开了一批以前读不了的数据源,包模型简化则是升级时要付的一次性成本。回到开头那堵 4GB 的墙——它没有被推倒,但现在有了绕行的路,而且这条路默认开着。
● ● ●
参考来源
- 01
Announcing DuckDB-Wasm 2.0(官宣博客,PR 中) https://github.com/duckdb/duckdb-web/pull/7539 - 02
DuckDB-Wasm 2.0 文档更新 PR https://github.com/duckdb/duckdb-web/pull/7538 - 03
DuckDB Wasm Client 官方文档(已上线,标注最新稳定版 2.0.0) https://duckdb.org/docs/2.0/clients/wasm/overview.html - 04
duckdb-wasm 仓库 https://github.com/duckdb/duckdb-wasm - 05
Announcing DuckDB 2.0.0 LTS "Cinnamon Teal" https://duckdb.org/2026/10/21/announcing-duckdb-200.html - 06
Persistent Databases in the Browser with DuckDB-Wasm and OPFS https://duckdb.org/2026/09/18/opfs-wasm.html