alitrack

浏览器里的 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 包模型变化:1.x 三包到 2.0 两包

旧版 DuckDB-Wasm 按浏览器能力发三个包:mvp(WebAssembly 1.0 基线)、eh(加异常处理)、coi(加多线程,需要跨域隔离)。你的页面用哪个,由 selectBundle 自动挑。

2.0 把这套砍掉了。现在只剩两个:

包
能力
要求
base
单线程,兜底基线
支持 Wasm 异常处理(现代浏览器全支持)
threads
SIMD + 多线程并行
页面跨域隔离

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 上的产物统计做了对比(都是未压缩体积):

版本
产物
体积
1.32.0(旧)
duckdb-mvp.wasm
37.5 MB
1.32.0(旧)
duckdb-eh.wasm
32.7 MB
1.32.0(旧)
duckdb-coi.wasm
32.4 MB
2.0 dev114(新)
duckdb-base.wasm
32.2 MB
2.0 dev114(新)
duckdb-threads.wasm
32.2 MB

拿旧的全功能包(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 溢写机制:内存放不下自动落盘

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,同一份代码单线程照跑。

● ● ●

升级前看这里

破坏性变更集中在一处——包的命名和选择:

  1. 01mvp
    、eh、coi 三个包没了。原来的 coi 换成 threads,其余场景用 base。只用 getJsDelivrBundles() 加 selectBundle()、不手写包名的项目,不用改。
  2. 02
    产物文件名全部换成新命名,硬编码路径要跟着改。
  3. 03
    扩展平台改名 wasm_base / wasm_threads,wasm_mvp 和 wasm_eh 不复存在。
  4. 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 的墙——它没有被推倒,但现在有了绕行的路,而且这条路默认开着。

● ● ●

参考来源

  1. 01
    Announcing DuckDB-Wasm 2.0(官宣博客,PR 中) https://github.com/duckdb/duckdb-web/pull/7539
  2. 02
    DuckDB-Wasm 2.0 文档更新 PR https://github.com/duckdb/duckdb-web/pull/7538
  3. 03
    DuckDB Wasm Client 官方文档(已上线,标注最新稳定版 2.0.0) https://duckdb.org/docs/2.0/clients/wasm/overview.html
  4. 04
    duckdb-wasm 仓库 https://github.com/duckdb/duckdb-wasm
  5. 05
    Announcing DuckDB 2.0.0 LTS "Cinnamon Teal" https://duckdb.org/2026/10/21/announcing-duckdb-200.html
  6. 06
    Persistent Databases in the Browser with DuckDB-Wasm and OPFS https://duckdb.org/2026/09/18/opfs-wasm.html