PgDog:当 PgBouncer 不再是唯一解——Rust 写的 Postgres 连接池新玩家

PgDog:当 PgBouncer 不再是唯一解——Rust 写的 Postgres 连接池新玩家

PostgreSQL数据库基础设施连接池Rust

数据源:HN · HN

7 月 6 日,PgDog 的作者 Lev Kokotov 发表了一篇博客文章,标题本身就带着防御姿态——「Why we built yet another Postgres connection pooler」。在一个已经被 PgBouncer 统治了二十年的领域里,任何一个新入场者都绕不开一个问题:为什么还需要另一个?

这篇文章在 Hacker News 上收获了 194 分和 47 条评论,社区反馈相当活跃。标题里的「yet another」自带了几分自觉的调侃,但读完文章和讨论后会发现,PgDog 并不是一个简单的「我们用 Rust 重写了一遍」项目。

一、连接池的「漏抽象」

要理解 PgDog 的动机,先要理解 PgBouncer 的局限性。

PgBouncer 遵循 Unix 哲学——做一件事并把它做好。这件事就是连接池:应用客户端数以千计,Postgres 数据库能承受的连接数有限(通常几百到一千),连接池站在中间复用数据库连接。

问题是,这种复用制造了一个漏抽象(leaky abstraction)。当你把 PgBouncer 部署到生产环境时,它会改变你使用数据库的方式。最直接的代价是 SET 语句。

SET statement_timeout TO '5m';
SELECT * FROM users WHERE banned IS true;

SET 语句的作用是临时覆写数据库会话参数。但在连接池模式下,连接在多个客户端之间共享,一个客户端的 SET 会「泄漏」到另一个客户端的连接状态中。最好的情况是某个慢查询跑得太久引发一次数据库事故;最坏的情况是,依赖会话变量的行级安全策略(RLS)静默失效,数据行悄悄消失。

所以业界的标准建议是:用了连接池就别用 SET

SET 是 Postgres 的原生特性。如果你不能用某个数据库特性,你就是在做取舍——要么改代码(可能涉及数千行业务逻辑),要么放弃连接池。而如果你的应用依赖 RLS,连接池这条路就彻底走不通了。

PgDog 的论点很简洁:不应该让基础设施的局限性倒逼应用层改代码。连接池应该对应用透明。

二、PgDog 的三个关键技术差异

2.1 内置 SQL 解析器处理 SET 语句

PgDog 内置了一个 SQL 解析器。它能够检测客户端发送的 SET 语句,提取变量名和值,并在代理层为每个客户端连接维护一份状态快照。

当一个客户端发起查询时,PgDog 先检查客户端状态与服务器连接状态是否一致。如果不一致,PgDog 会自己向 Postgres 发送一系列 SET 语句来同步状态。如果涉及多个变量,则利用查询管道(query pipelining)在一次往返中完成所有更新。

这样做的结果是:对应用来说 SET 照常工作,对数据库来说连接状态始终正确,而对性能的影响很小。开发者不需要在部署连接池之前发动一次「消灭 SET」的代码清理运动。

HN 用户 AdieuToLogic 还指出了一个容易被忽略的点——PgDog 支持预处理语句(prepared statements),这在老版本的 Pgpool-II 中是一个长期限制,导致许多场景无法使用。

2.2 LISTEN/NOTIFY 在连接池下可用

LISTENNOTIFY 是 Postgres 内置的发布/订阅机制——数据库里自带的消息队列,无需引入 Redis 或 SQS 就能实现轻量级的异步通知。

这也是一个一加连接池就罢工的特性,至少在事务模式下如此。如果你在最近十年里构建过应用(PostgreSQL 10 于 2017 年发布),很可能用过它,然后在引入连接池时被迫迁移到外部消息中间件。

PgDog 在代理层内部处理 LISTENNOTIFY 命令。它在同一个 PgDog 进程内使用 Tokio 的 broadcast channel 在客户端之间转发消息;对于跨多个 PgDog 进程的场景(生产环境通常会部署多个容器),PgDog 通过一条专用连接将所有 LISTENNOTIFY 命令发送到 Postgres,由 Postgres 本身充当实际的 broker。

也就是说,PgDog 充当了一个 pub/sub 客户端,代理着其他 pub/sub 客户端,而 Postgres 仍然是消息的真实来源。从应用视角看,PgDog 就是 broker;从架构视角看,Postgres 才是最终的消息总线。

关于 NOTIFY 的事务语义,作者坦率地承认了其局限性——按照严格的 CAP 理论定义,这个实现并不是完全事务性的,但在实践中能够交付绝大多数消息。Kokotov 在 HN 回复中表示,这还达不到 Kafka 那样的持久化工作队列级别,但已经可以在不破坏数据库或应用的前提下「足够好」地工作。

PgDog LISTEN/NOTIFY 架构

2.3 Tokio 多线程 vs SO_REUSEPORT 多进程

PgDog 基于 Rust 的 Tokio 异步运行时构建,采用多线程工作模型。每个客户端连接由独立的异步任务处理,随连接数线性扩展。

PgBouncer 的做法不同——它依赖 SO_REUSEPORT 启动多个独立进程,每个进程拥有自己专属的那份 Postgres 连接。RDS Proxy 则走「serverless」自动扩缩容的路线。但两者本质上都要求你将连接池「分片」——每个代理进程独占一组数据库连接,客户端一旦连接到某个进程就无法切换。如果某个进程过载,挂在该进程上的所有客户端一起遭殃。

多线程的 PgDog 进程可以在单进程中利用多个 CPU 核心,用更少的数据库连接服务更多的客户端,连接利用率和效率都更高。在面对突发查询流量时,多线程进程也不需要等待自动扩缩容的响应时间——如果你的应用有延迟 SLA,你不会希望代理层成为瓶颈。

另外,运维手册更短也是一个实际的好处:只需要监控一个进程的指标和健康检查,而不需要把复杂度推到 Linux 内核或 AWS RDS 控制面板这种难以调试的地方。

三、PgDog 不只是连接池

虽然这篇博客聚焦于连接池的特性差异,但 PgDog 的定位远不止于此。根据 GitHub README,它是一个「用于扩展 PostgreSQL 的代理」,功能涵盖三个层面:

  • 连接池:事务模式和会话模式,支持 SET 语句和预处理语句
  • 负载均衡:应用层(OSI 第 7 层)负载均衡,支持轮询、随机和最少活跃连接三种策略。内置健康检查,自动摘除故障节点。通过解析 SQL(使用 pg_query,内含 PostgreSQL 原生解析器)识别读写,将写操作路由到主库、读操作分发到副本
  • 数据库分片:支持不依赖 Postgres 扩展的数据库分片,以及分片再平衡和数据迁移

认证方面也下了功夫:除了标准的 SCRAM-SHA-256 密码认证,还支持 AWS RDS IAM、Azure Workload Identity、以及 HashiCorp Vault 的动态和静态凭据。

HN 讨论中有一位用户询问了分片功能的多租户场景——能否通过插件架构动态添加分片或租户?作者 levkk 回复说插件架构已经支持这类需求。

四、社区反馈:技术被认可,商业化存疑

HN 上的讨论整体积极。获得高赞的几条评论指向了几个不同的关注点:

写作质量abrookewood 留言说:「这篇文章在解释你们为什么不同、为什么这很重要方面写得非常好。」在技术推广中,把「为什么」讲清楚往往比罗列功能更重要。

许可协议27183 赞赏了 AGPL 许可的选择,认为这比近期流行的 BSL 变体好得多。levkk 回复说他选择 AGPL 是因为它是 GPL 的延伸,而 GPL 是他自学编程的重要基础,「只是想回馈社区」。后续的讨论则转向了 AGPL 与云厂商商业模式之间的张力——有人指出云厂商普遍回避 AGPL 本身就说明了很多问题。

实用限制。一位 Django 用户分享了他们最近迁移到 PgBouncer 事务池的实际经历——出乎意料的是,SET 没出问题,queryset.iterator() 依赖的服务端游标(server-side cursors)却因连接池而失效了。Kokotov 坦率地回应说游标是非常会话级的对象,即使 PgDog 通过「固定」客户端来处理游标,也会降低连接池的整体性能——这是一个需要在「让应用立即崩溃以暴露问题」和「静默降级」之间做权衡的场景。

与竞品的对比。讨论中自然提到了 Supabase 的 Multigres 和 PlanetScale 的 Neki——两者都在做「Postgres 的 Vitess」。PgDog 在定位上更偏向轻量级代理,而 Multigres 和 Neki 则瞄准了完整的分布式数据库解决方案。有用户指出,PgDog 的自动查询路由和自动分片能力可能让 Postgres 在功能上更接近 Vitess。

五、一个需要回答的根本问题

回到最初的问题:这样一个被 PgBouncer 深耕了二十年的领域,真的需要一个新工具吗?

PgDog 给出的答案是:Pgbouncer 做得很好,但它要求你适应它。SET 不能用,LISTEN/NOTIFY 不能用,预处理语句可能有问题,连接池被分片在多进程之间。这些都是真实的摩擦,只是开发者们已经习惯了。

PgDog 试图换一个方向:让连接池适应你。通过内置 SQL 解析器智能处理会话状态,通过 Tokio 多线程提高单进程的高并发能力,通过与云平台 IAM 系统的深度集成降低运维复杂度。它宣称已经在生产中运行了一年多,在 2M 查询/秒的负载下验证过。

值得注意的是,PgDog 不是要取代 PgBouncer——至少在当前阶段,它面向的是那些在「应用改造」和「引入连接池」之间二选一的团队,以及那些除了连接池之外还需要负载均衡和分片能力的场景。

当然,一个年轻项目要替换一个在生产环境中验证了二十年的基础设施组件,本身就意味着巨大的信任鸿沟。技术上的差异点再合理,也需要时间的检验——特别是当它涉及会话状态、事务语义和消息可靠性这些数据库领域中最敏感的部分时。

PgDog 的存在至少说明了一件事:Postgres 生态的基础设施层远未定型,新的语言(Rust)、新的并发模型(async/await)和新的架构假设正在催生新一代工具。对开发者来说,多一个选择从来不是坏事。

参考链接:

本文的素材来自公开信息和社区讨论。如果你对这个话题有更深入的一手经验,欢迎指出文中的不足。