alitrack

DuckDB 悄悄变客户端-服务端数据库,UPDATE 有坑

2026 年 5 月 12 日,DuckDB 悄悄发布了一个叫 Quack 的东西:它把一直"只在你进程里"的 DuckDB,变成了一个可以网络访问、多客户端并发读写的客户端-服务端数据库。目前 beta(v1.5.3 起可用,计划 2026 年 9 月随 v2.0 转 stable)。

今天这篇,是真机实测。我会把官方宣传里没说透的地方,用真实报错给你打出来。

● ● ●

一、Quack 是什么,一句话

任何两个 DuckDB 进程,一个调 quack_serve() 变服务器,另一个用 quack: 协议连过去——就成了客户端-服务端架构。协议跑在 HTTP 上(防火墙、负载均衡、反向代理全兼容),请求响应用的是 DuckDB 内部序列化(和 WAL 同一条代码路径),复杂类型跨网络无损失。

对数据团队最实际的三个场景:

  1. 01
    数据在服务器上,你想把计算挪到数据旁边,本地只看结果;
  2. 02
    多个进程要并发读写同一个库(原生 DuckDB 文件不支持多进程并发写);
  3. 03
    服务器很强,本地很弱,把重查询甩过去。

官方 benchmark:8 核 32G 的机器上,Quack 能扛几千 writes/s,够中小 OLTP 用了。

● ● ●

二、30 秒跑起来

服务器端(任意一个已有 DuckDB 会话):

CALL quack_serve('quack:localhost:9494', token = 'your_token_here');

客户端:

CREATE SECRET (TYPE quack, TOKEN 'your_token_here');
ATTACH 'quack:localhost:9494' AS remote;

SELECT * FROM remote.some_table;

两个新手坑,我替你踩过了:

  • token 至少 4 个字符
    ,短了直接报 Quack server token must be at least 4 characters long;
  • 本机 localhost 自动走 HTTP,连远端默认自动切 HTTPS(可用 DISABLE_SSL 覆盖)。

到这里,看起来一切都和宣传一致:"Quack protocol supports the full DuckDB feature set – over the wire."

完整功能,对吧?往下读。

● ● ●

三、反转:ATTACH 完了,我 UPDATE 了一下

我按最直觉的用法,把远端库挂上来,开始标准 CRUD:

CREATE TABLE remote.orders (id INTEGER, product VARCHAR, total DOUBLE);   -- ✅
INSERT INTO remote.orders VALUES (1,'widget',42.0),(2,'gadget',7.5);      -- ✅
SELECT * FROM remote.orders;                                              -- ✅
UPDATE remote.orders SET total=49.99 WHERE id=1;                          -- 💥

真实报错(DuckDB 1.5.5,原样):

Binder Error: Can only update base table

DELETE 也一样:

Binder Error: Can only delete from base table

也就是说:用 ATTACH 挂上来的远端表,能建、能插、能查、能删表,唯独不能改行、不能删行。 在 DuckDB 的 binder 眼里,远端表不是 "base table"。

所以宣传里那句 "full feature set over the wire",在 ATTACH 这条路径上,是不成立的。我猜这是 beta 阶段的限制,但官方文档和 FAQ 里,我通读之后没有找到任何一处提到这一点。

(顺带一提,挂上去的远端 catalog 也比较"裸":remote.information_schema.tables 和 remote.duckdb_tables() 都不存在,元数据内省能力目前有限。)

● ● ●

四、正确姿势:quack_query

好消息是 Quack 提供了第二条路:不挂载,直接用无状态的 quack_query(uri, sql) 表函数,SQL 在服务器端执行:

FROM quack_query('quack:localhost:9494',
    'UPDATE orders SET total = 49.99 WHERE id = 1',
    token = 'your_token_here');

实测,完整 CRUD 全通:

操作
ATTACH 路径
quack_query 路径
CREATE TABLE
✅
✅
INSERT
✅
✅
SELECT
✅
✅
UPDATE
❌ Binder Error
✅
DELETE
❌ Binder Error
✅
DROP TABLE
✅
✅
Quack 双路径架构图

Quack 双路径架构图

所以结论是:读和分析走 ATTACH(可以 JOIN、聚合、当数据源用),行级改删走 quack_query。 两条路配合着用,才是当前 beta 的完整玩法。

● ● ●

五、事务:能,但姿势有讲究

quack_query 是无状态的——每次调用都是独立请求,不共享会话状态。所以别这么写:

-- ❌ 这样不行:ROLLBACK 会报
-- "cannot rollback - no transaction is active"
quack_query(..., 'BEGIN TRANSACTION');
quack_query(..., 'INSERT INTO t VALUES (1)');
quack_query(..., 'ROLLBACK');

正确做法:把事务打包进同一个字符串,在服务器端原子执行:

FROM quack_query('quack:localhost:9494',
    'BEGIN TRANSACTION;
     INSERT INTO t VALUES (1, 10), (2, 20);
     COMMIT;',
    token = '...');

实测 COMMIT / ROLLBACK 都正常,原子性成立。

● ● ●

六、边界清单(beta 阶段的实话)

  • beta,会 break。
     官方原话:协议、函数名、默认值都还可能变。生产别押,原型随便玩;稳定版等 2026-09 的 v2.0。
  • 不支持分布式查询。
     计算要么在客户端要么在服务器,不跨节点拆分。
  • 无状态调用不共享会话
    ,事务要打包。
  • 远端 catalog 能力有限
    ,内省靠 SELECT * FROM quack_query(...) 之类的绕行。
  • token ≥ 4 字符;默认端口 9494;对外暴露务必上反向代理 + TLS(官方有专门的安全文档)。

● ● ●

七、谁该现在就用,谁该再等等

现在就用:

  • 被" DuckDB 文件不能多进程并发写"卡住的(多个服务要写同一份数据);
  • 想把重查询从弱客户端甩到强服务器的;
  • 想给内部工具加个"轻量数据库 API"又不想引入 Postgres 运维成本的。

再等等:

  • 生产关键路径(等 v2.0 stable);
  • 需要跨节点分布式计算的;
  • 依赖完整 catalog 内省 / 工具链生态的(目前 ATTACH 路径对元数据工具不太友好)。

● ● ●

写在最后

Quack 的方向我很看好——DuckDB 一直赢在"零运维 + SQL 能力",Quack 补上的正是"多客户端"这块最硬的短板,而且选了 HTTP 这种最无聊也最稳的传输。

但它现在最需要的不是更多宣传,而是把行为边界写清楚。"full feature set" 这句话,在 beta 阶段,是会坑人的。

以上全部基于 DuckDB 1.5.5 实机复现,服务器端 quack_serve + 独立客户端进程。踩坑细节和完整脚本,欢迎来评论区对拍。


参考来源:

  1. 01
    DuckDB Quack 官方概览 - https://duckdb.org/docs/extensions/quack/overview
  2. 02
    DuckDB Quack 参考手册 - https://duckdb.org/docs/extensions/quack/reference
  3. 03
    DuckDB Quack 安全指南 - https://duckdb.org/docs/extensions/quack/security
  4. 04
    DuckDB Quack FAQ - https://duckdb.org/docs/extensions/quack/faq
  5. 05
    2026-05-12 发布博客 - https://duckdb.org/2026/05/12/quack