DeepSeek总结的Quack:DuckDB 客户端-服务器协议
来源https://duckdb.org/2026/05/12/quack-remote-protocolQuackDuckDB 客户端-服务器协议作者:DuckDB 团队日期:2026-05-12摘要:DuckDB 实例现在可以使用 Quack 远程协议相互通信。这让你可以在客户端-服务器设置中运行 DuckDB并支持多个并发写入者。秉承 DuckDB 的精神Quack 设置简单并建立在 HTTP 等成熟技术之上。它速度也很快能够支持从批量操作到小型事务的各种工作负载。背景数据库架构当数据库最初出现时并没有客户端和服务器之分整个数据库就运行在一台计算机上。在 80 年代的某个时候Sybase 首次引入了数据库服务器和运行在不同计算机上的客户端的概念。从那以后人们就假设每个数据库系统都使用客户端-服务器架构以及用于它们之间通信的协议。这很方便因为单一的可变状态保持在服务器控制下的单一位置并且可以有许多客户端同时读写数据。当然这种方法也有缺点最明显的是这些协议可能会增加大量开销。如果你想了解更多我们之前写了一篇关于数据库协议的研究论文。当然一直有反对客户端/服务器架构的人最著名的是 2000 年无处不在的 SQLite当然还有 2019 年首次发布的 DuckDB。我们大力宣传了实现进程内架构即没有客户端/服务器没有协议只有底层 API 调用。这对于交互式用例例如数据科学非常有效分析师可以在 Python notebook 等环境中与数据进行交互而他们的数据在同一个进程中运行的 DuckDB 实例中进行管理。对于许多仅将 DuckDB 粘合到现有应用程序以对该应用程序中的数据提供 SQL 功能的用例来说它也非常有效。当尝试从多个进程同时修改同一个数据库文件时进程内系统的效果不佳。有很多用例与此相关例如当从一堆收集遥测数据的进程插入到同一个数据库同时查询同一个表来驱动仪表板时。有一些很好的技术原因导致我们无法实现这一点最显著的是DuckDB 在主内存中保存了大量状态如果多个进程同时开始进行更改则必须同步该状态。是的有一些变通方法。当然你可以设计一个自定义的远程过程调用RPC解决方案其中一个进程持有 DuckDB 数据库实例并向其他进程提供查询和插入数据的服务。也有多个项目为 DuckDB 改装了客户端/服务器能力例如使用 Arrow Flight SQL 协议。MotherDuck 有自己的自定义客户端-服务器协议。当然你总是可以天哪切换到一个更传统的、支持客户端-服务器的数据库系统例如同样无处不在的 PostgreSQL。然后你甚至可以通过使用各种启用此功能的扩展之一例如 pg_duckdb来运行所谓的EleDucken即在上述 PostgreSQL 中运行 DuckDB。人们为将客户端-服务器解决方案绑定到 DuckDB 而构建的大量变通方法至少让我们相信这是人们关心的事情。我们将 DuckDB 视为一个通用的数据整理工具。如果这意味着除了进程内功能之外还需要一个客户端-服务器协议——那也没问题。如果这最终解锁了大量新的、DuckDB 可以发挥作用的场景——那太好了最终我们深切关心用户体验也许对架构的最终话语权不那么在意。所以我们咬紧牙关最终今天我们非常高兴地宣布结果为 DuckDB 引入 Quack 协议如果两只或更多鸭子想互相交谈它们会做什么它们会嘎嘎叫因此我们很自然地也需要将两个 DuckDB 实例可以用来相互通信的协议称为Quack我们有机会在 2026 年从头开始设计一个数据库协议而无需考虑任何遗留问题这是一种奢侈。我们能够从现有协议中学习包括较新的 Arrow Flight SQL 等。在我们深入探讨 Quack 内部工作原理之前让我们先从用户的角度看看它是如何工作的。首先你需要两个 DuckDB 实例。没错DuckDB 将同时充当客户端和服务器这两个实例可以位于相隔万里的不同计算机上或在太空中或者只是你笔记本电脑上的两个不同终端窗口。首先我们需要在两个 DuckDB 实例中安装 Quack 扩展。目前Quack 位于core_nightly仓库中并在当前发布版本 DuckDB v1.5.2 中可用-- DuckDB #1INSTALL quackFROMcore_nightly;LOADquack;CALLquack_serve(quack:localhost,tokensuper_secret);CREATETABLEhelloASFROMVALUES(world)v(s);quack:-- DuckDB #2INSTALL quackFROMcore_nightly;LOADquack;CREATESECRET(TYPEquack,TOKENsuper_secret);ATTACHquack:localhostASremote;FROMremote.hello;这应该在 DuckDB #2 中显示远程表hello的内容也就是world。魔法我们还可以将数据从本地实例复制到远程实例-- DuckDB #1-- 第二步FROMhello2;quack:-- DuckDB #2-- 第一步CREATETABLEremote.hello2ASFROMVALUES(world2)v(s);同样你应该在 DuckDB #1 的输出中看到world2。显然这些是我们能想到的最基本的例子。表可以复杂得多查询可以复杂得多数据量可以非常大见下文。还有一种方法可以使用query函数将整个逐字的查询发送到远程端这对于大型数据集上的非常复杂的查询更好并且可以更精确地控制远程执行的内容-- DuckDB #1-- 等待提供服务数据quack:-- DuckDB #2FROMremote.query(SELECT s FROM hello);当然还有很多东西可以看。请参阅我们的文档以获取更多详细信息。协议设计基于 HTTPQuack 直接构建在古老的 HTTP超文本传输协议之上。从其在 CERN 的卑微开端开始HTTP 已成为 TCP 及其下所有协议栈之上的事实上的协议层。整个堆栈都经过优化可以高效地传输 HTTP 消息流。如果实现得当该协议的开销出奇地低。每个人和他们的弟弟都知道如何在负载均衡、身份验证、防火墙、入侵检测等方面处理 HTTP。在 2026 年不将数据库协议构建在 HTTP 之上将是相当不明智的。HTTP 还允许 DuckDB-Wasm 发行版原生地使用 Quack 协议因此在浏览器中运行的 DuckDB 可以例如使用 Quack 直接连接到在 EC2 服务器中运行的 DuckDB 实例。请求-响应模式Quack 上的交互始终由客户端以请求-响应模式驱动。Quack 消息例如是连接请求使用令牌进行身份验证如上所示。有关身份验证和授权如何详细处理请参见下文。后续消息是执行查询并返回结果第一部分的请求以及检索大型结果的后续获取消息可能来自多个并行线程。序列化请求和响应使用新的 MIME 类型application/duckdb进行编码。这种编码利用 DuckDB 内部高效的序列化原语来处理复杂结构如数据类型和结果集。例如我们在预写日志WAL文件中已经使用相同的原语多年这意味着它们经过了相当好的优化和实战测试。加密虽然我们希望 Quack 能正常工作但我们也警惕直接将数据库连接到邪恶互联网的安全噩梦这以前发生过。这就是为什么默认情况下 Quack 会在服务器启动时生成一个随机身份验证令牌然后必须将其提供给客户端。此外Quack 服务器默认只绑定到 localhost当然可以被覆盖。Quack 默认不使用 SSL因为仅仅为了 localhost 通信而引入所有基础设施并添加依赖项有点愚蠢。我们不建议直接将 DuckDB Quack 端点开放到互联网。相反我们强烈建议如果你选择将 Quack 暴露给万维网则应使用常见的 HTTP 端点如 nginx并让该代理终止 SSL例如使用 Let’s Encrypt。Quack 客户端将假定对非本地连接启用了 SSL这可以被覆盖。我们在文档中提供了相关指南。往返次数我们一直很注意优化查询的协议往返次数或请求/响应对的数量。一旦连接一个查询可以在单次往返中完全处理完毕。这对于延迟敏感的环境来说是一个关键的优化。同时我们为高效的批量响应传输认真优化了 Quack。据我们所知Quack 目前是通过套接字推送表的最快方式数百万行可以在几秒钟内传输完成。下面是一些基准测试结果。身份验证和授权数据库查询的身份验证和授权是无穷无尽的欢乐和复杂性之源。我们可能无法捕捉到每个人的用例尤其是在第一个版本中。因此明智的做法是不去尝试。对于 Quack我们选择了一种与 DuckDB 可扩展性理念相结合的身份验证模型。目前已经有数百个 DuckDB 扩展。Quack 附带一个默认的身份验证方法并且没有授权限制但两者都可以被用户提供的代码覆盖。正如你上面看到的Quack 服务器在启动时会生成一个默认的随机身份验证令牌。当客户端连接时它提供一个身份验证字符串。服务器端将调用一个身份验证回调函数。默认情况下它会将客户端提供的令牌与之前随机生成的令牌进行比较。但是这个回调可以通过配置来改变你可以带上你自己的身份验证函数例如查询 LDAP 目录、读取文本文件或者只是掷骰子。由你决定。类似地授权函数也可以改变。默认的授权函数对所有事情都说是但你可以检查客户端试图执行的每个查询将查询与先前使用的身份验证字符串关联起来等等。这些回调甚至可以是普通的 SQL 宏请参阅我们的文档以获取更多详细信息。默认端口默认情况下Quack 服务器监听 9494 端口数字 94 很容易记住因为 Netscape Navigator 在那一年发布。基准测试我们设置了两个基准测试来展示 Quack 协议。这些基准测试在运行 Ubuntu on Arm 的 AWS 虚拟机上运行。我们选择了m8g.2xlarge实例类型它有 8 个 vCPU 和 32 GB 内存重要的是网络带宽高达 15 Gbps。我们重现了一个真实场景其中客户端和服务器位于同一数据中心但在不同的机器上。我们确保两个实例都在同一个可用区中。实例之间的 Ping 时间平均约为 0.280 毫秒。批量传输第一个基准测试测试批量传输即应该通过数据库协议传输相当大量行的情况。如果你读过我们上面链接的论文你就会知道这是传统数据库协议难以应对的情况。我们将 Quack 与两个系统进行比较广泛使用的 PostgreSQL 协议和较新的 Arrow Flight SQL 协议。Arrow Flight 由 GizmoSQL 服务器提供该服务器也内部使用 DuckDB。我们传输不断增加的行数的 TPC-Hlineitem表一直到高达 6000 万行CSV 格式为 76 GB并报告 5 次运行的中位墙钟时间。我们期望现代面向批量的协议将远远超过 PostgreSQL 协议。结果如下[此处假设有图表原文标题为批量传输操作的运行时间越低越好和批量传输性能]想看表格形式的结果吗点击这里。我们可以看到 Quack 在批量结果集传输方面表现出色在 5 秒内传输了 6000 万行即使是专门构建的 Arrow Flight SQL 协议在这方面也无法与之竞争而 Postgres 的基于行的协议总体上相当无望。公平地说我们必须提到标准 PostgreSQL 客户端不会在多线程上并行化读取但 Quack 和 Arrow 可以。厚颜无耻的自我宣传DuckDB 的 PostgreSQL 客户端在某些情况下也可以做到这一点小规模写入第二个基准测试测试小规模追加。这是一个常见的用例例如将可观测性数据集中到一个中央 DuckDB 实例中。这以不同的方式考验数据库协议例如完成单个事务需要客户端和服务器之间的多次往返将是一个劣势。我们通过创建一个与 TPC-Hlineitem表具有相同结构的空表来测试这一点然后插入随机值每个行都在其自己的INSERT事务中。插入的值在一定程度上遵循了通常基准生成器的分布。我们运行了五分钟内不断增加数量的并行线程。我们重复了五次实验并报告了中位数每秒事务数。我们期望像 PostgreSQL 这样高度事务优化的系统能够主导这个基准测试。我们也期望针对批量优化的 Arrow Flight 表现得不会特别好。[此处假设有图表原文标题为小规模写入的吞吐量越高越好和小规模写入性能]想看表格形式的结果吗点击这里。相当令人惊讶的是我们看到 Quack 在高达 8 个并行线程时优于 PostgreSQL达到大约每秒 5,500 个事务的最大事务率。超过这个限度我们遇到了 DuckDB 本身在同一个表上每秒并发插入的当前限制。PostgreSQL 在这里扩展性更好这是我们近期需要研究的问题。正如预期的那样Arrow Flight 表现不佳大约比 Postgres 慢一半。基准测试脚本可在线获取。结论今天我们发布了 Quack一个 DuckDB 的客户端/服务器协议以及作为 DuckDB 扩展的初始实现。Quack 解锁了 DuckDB 的完整多人体验多个独立的进程——本地或远程——现在可以并行修改表的内容而不会互相锁死。虽然这些功能部分已经可以通过 DuckLake 实现但 Quack 使其简单得多并提供更高的性能。用例有了 QuackDuckDB 现在可以在广泛的新用例中发挥作用这些用例中集中化状态比超本地查询更重要。随着数据湖的兴起我们已经认识到数据并不总是本地的。说到湖Quack 也将被集成到 DuckLake 中以便 DuckDB 本身可以成为一个可远程访问的目录服务器。这将解锁新的能力例如用于数据内联。如果你对此有更多疑问请查阅 Quack FAQ。总体而言DuckDB 正在从其最初的交互式分析进程内数据库的利基市场进一步发展为现代数据架构的核心构建块。我们使用 Quack 已经有一段时间了并且非常期待听到你将用它构建什么。如果你对如何改进 Quack 有任何建议请告诉我们嘿《流言终结者》已经证明鸭子的嘎嘎叫声会产生回声所以让我们看看这会引发什么样的噪音。下一步计划当然还有很多事情要做。首先我们将把 Quack 集成到 DuckLake 中这样就有可能使用远程 DuckDB 服务器作为 DuckLake 的目录我们期望这将大大提高性能尤其是在内联方面。接下来我们将在未来几个月内完善 Quack并在今年秋天与 DuckDB v2.0 一起发布第一个生产版本。例如我们计划在需要时启用 Quack 扩展的自动安装和自动加载。我们还将使用新的解析器改进从 DuckDB 与远程 SQL 数据库通信的语法。在 DuckDB 核心方面我们计划致力于大幅提高每秒可达成的事务数以便我们能够将事务扩展到远远超过八个并行线程。此外我们正在考虑允许在身份验证和授权之外扩展 Quack 协议例如允许 DuckDB 扩展添加新的协议消息和代码来处理它们。我们也在考虑在 Quack 之上添加一个复制协议以便对 DuckDB 实例的更改可以复制到其他服务器例如设置一个只读副本集群。如果你想了解关于 Quack 的更多信息——并了解它的初步采用情况——请于 6 月 24 日参加我们的社区会议 DuckCon #7。DuckCon 将由 DuckDB 的联合创始人发表State of the Duck演讲。你可以亲临现场也可以在 YouTube 上观看在线直播。PS我们为 Quack 项目设置了一个单独的页面请务必访问一下。致谢我们要感谢 MotherDuck 的 Boaz Leskes 与我们分享他们在构建 MotherDuck 协议方面的经验教训。我们还要感谢 GizmoSQL / GizmoData 的 Philip Moore他已经为我们开辟了这条道路并证明了客户端-服务器 DuckDB 是一件非常有价值的事情。附录为什么不选择 Arrow Flight SQL我们还需要解决房间里的大象之一我们到底为什么不使用现有的 Arrow Flight SQL 协议它就在那里。它是可用的。有现有的实现。我们看到了 Arrow 和相关项目如 ADBC的价值它们是像它们之前的 ODBC 和 JDBC 一样的交换 API旨在减少系统间交换数据的摩擦。这效果很好。然而我们也对在 DuckDB 内部使用像 Arrow 这样的交换格式持谨慎态度。虽然 DuckDB 用于查询中间结果的内部结构在某些方面与 Arrow 接近但在其他方面却截然不同。我们觉得为了能够持续在数据系统领域进行创新我们不能让自己受到外部控制的格式的限制。这就是为什么我们在 Quack 中使用自己的序列化。如果我们想添加一个新的数据类型或协议消息我们可以明天就发布。在更深层次上Arrow Flight SQL 中还有一个致命的设计决策每个查询至少需要两次协议往返即CommandStatementQuery和DoGet。这对于像我们上面的第二个实验那样的小规模更新来说并不理想尤其是在高延迟环境中。如前所述我们将 Quack 设计为能够对小型查询执行单次往返的查询执行和结果获取。