alitrack

DuckDB 文件只能单进程写,官方在代码里铺路拆这个限制

11 月 4 日深夜合进主干的 PR #28314,看描述是普通重构,正文最后一句却预告了「跨客户端共享事务」——单写者模型这堵墙,官方开始从地基上拆了。

DuckDB 有个老限制:一个数据库文件,同一时刻只允许一个进程读写;别的进程只能只读。想在两个进程里同时写一个文件,官方文档至今只给一条路——起一个 Quack 服务端,别的进程当客户端连上去。

很多人以为这是文件锁没做好。不是。锁是表现,病根在进程内:事务、缓冲、设置,七年来全都长在 ClientContext 这个类上,换一个客户端进来,等于要把半个引擎搬过去。而昨天合入的 #28314 干的事,就是把这些状态从「客户端」身上剥下来,放进一个新的 Context 类。

这一步不改变你今天能做的事。但它解释了 Quack 服务端那 5,400 tx/s 的天花板从哪来,也预告了接下来要动哪里。

DuckDB 并发模型三层演进

DuckDB 并发模型三层演进

● ● ●

先把病根讲清楚

DuckDB 里几乎每个内部函数都接收一个 ClientContext,靠它读设置、打日志、记指标、拿文件系统。合并说明里原话这么写:ClientContext 是个「catch-all」——查询执行、事务上下文、各种服务访问,全裹在一个类里。

这个设计在单进程时代没问题,甚至很好:一个进程一个上下文,缓存函数指针、缓存 catalog,后续查询越来越快。

到了服务端时代就别扭了。Quack 服务端是单个 DuckDB 进程,每个连进来的客户端在服务端各自生成一个 ClientContext,事务状态、缓冲管理这些东西,天然跟着「客户端」走,而不是跟着「数据库」走。社区里想要多进程共享事务的呼声一直有,合并记录里那位贡献者看到预告后第一句回复就是「Very excited for sharing transactions across clients!」

说白点:引擎的共享设施和服务对象,被一个「每客户端一份」的类绑在一起。想拆墙,得先把这两者解开。

● ● ●

昨天合进去的到底是个什么 PR

数字先摆出来:1,517 行新增、612 行删除、132 个文件改动,作者 Mytherin(Mark Raasveldt,DuckDB 两位创始人之一),11 月 3 日开 PR,次日合入主干。

PR #28314 做了什么

PR #28314 做了什么

它引入了一个新的 Context 类,把原先挂在 ClientContext 上的服务收拢过去:设置、指标和日志、文件系统,以及 DatabaseInstance 和任务调度器、缓冲管理器这些内部件。PR 里说得直白:这是第一遍,目标是让更多代码改用 Context,别再无脑传 ClientContext。

评审记录里有个细节。第一版类名不叫 Context,协作者 Maxxen 提议改名:他正在做优化器和类型系统的工作,很多地方要查类型构造器和 cast 规则,希望这些地方也能接收一个通用的 Context。Mytherin 同意了,还补了句「我想过好几个名字,但既然目标是塞进尽可能多的代码,泛化一点更合适」。

一句话总结这个 PR:把「哪些服务是整个数据库共享的」从代码结构上划清楚。这本身不产生任何用户可见功能。

真正的信息量在 PR 说明的最后一句——「这种分离还有另一个目的:它会让跨客户端共享事务变得容易得多(coming in a follow-up)」。follow-up,就是还没发的后续 PR。

● ● ●

这堵墙为什么七年没拆

单写者不是偷懒,是一套自洽的取舍。官方文档写明:读写模式下,多线程写靠 MVCC 加乐观并发控制撑着,但都得待在同一个写进程里。理由也给了——数据缓存在内存里跑分析查询,不用每条查询都回磁盘;函数指针、catalog 这些也能缓存,同连接后续查询更快。

多进程同写一个文件,就得回答一个问题:每个进程内存里那份缓存,谁来保证一致?DuckDB 的答案是干脆别多进程。文件锁拦在门口,宁可让用户去架 Quack。

今年 5 月 Quack 发布,把「多进程想写同一个库」这个需求接住了大半:起一个服务端进程持有数据库,其他 DuckDB 实例通过 quack: 协议连进来。2.0 版本又打磨了一轮。实测数字摆在那:8 线程小事务吞吐 5,434 tx/s,同一表并发插入没锁冲突。

但那篇公告也自己认了账:超过 8 线程之后,撞上的是 DuckDB 自身在并发插入上的限制,PostgreSQL 在更高并发下扩展性更好。每个客户端一个上下文这套结构,就是那个限制的来源之一。

11 月 2 日 PostHog 宣布把数仓迁出 DuckDB,单节点多租户撑不住是原因之一(我们前天聊过)。Context 重构当然不是给那个场景的答案,但方向对得上:让「多人共写」在引擎内部不再是一个进程内的妥协。

● ● ●

接下来看什么

Mytherin 明确说了 follow-up。顺着这个思路,值得盯的是:事务上下文挪出 ClientContext 之后,多个客户端怎么共享同一个事务——这是 Quack 服务端并发能力往上走的前提。

DuckDB 2.1 按日历要等 2027 年上半年,跨客户端共享事务会不会赶上这一班,官方没承诺,路线图上也还没有这一项。但 PR 都合主干了,follow-up 的优先级可想而知。

节奏上有个巧合:同一天,duckdb.org 的路线图页面更新到 Nov 2026,新增了「many-core and NUMA-aware optimizations」一条。并发扩展是同一条主线上的活。

对今天就要多进程写 DuckDB 的人,现实没变:Quack 服务端、DuckLake、或者外置一个编排进程,三选一。但下次有人跟你说「DuckDB 天生单写者,改不了」,你可以告诉他:改的 PR 已经合进去了,缺的只是下一块。

● ● ●

参考来源

  1. 01
    PR #28314 合并记录与讨论(2026-11-04 合入) https://github.com/duckdb/duckdb/pull/28314
  2. 02
    DuckDB 官方并发模型文档 https://duckdb.org/docs/2.0/connect/concurrency.html
  3. 03
    Quack 协议发布公告(2026-05-12,含两份基准测试) https://duckdb.org/2026/05/12/quack-remote-protocol.html
  4. 04
    DuckDB 2.0.0 LTS 发布公告(2026-10-21) https://duckdb.org/2026/10/21/announcing-duckdb-200.html
  5. 05
    DuckDB 开发路线图(Nov 2026 更新) https://duckdb.org/docs/dev/roadmap
  6. 06
    PostHog 迁出 DuckDB 的技术复盘 https://posthog.com/blog/warehouse-off-duckdb