我们为什么又构建了一个Postgres连接池
摘要
PgDog是一个新的Postgres连接池,它通过处理SET语句和LISTEN/NOTIFY来保留应用程序代码,与需要权衡的现有连接池不同。
暂无内容
查看缓存全文
缓存时间: 2026/07/07 20:14
# 为什么我们又造了一个Postgres连接池
来源:https://pgdog.dev/blog/why-yet-another-connection-pooler
2026年7月6日
PgDog 是一个用于扩展 Postgres 的代理。它的特性之一就是连接池化,让多个客户端应用能够使用同一个数据库,而不会超过其连接限制。
市面上已经有很多连接池工具,比如 PgBouncer、RDS Proxy、Pgpool-II、Supavisor 等等。那么,为什么我们还要再做一个呢?
## 不完善的抽象
Postgres 生态中的大多数工具都非常信奉 UNIX 哲学(https://en.wikipedia.org/wiki/Unix_philosophy):只做一件事,并把它做好。总的来说,这是构建可靠软件的好方法,这也是 PgBouncer 今天如此流行的原因——它确实管用。
然而,它的工作原理引入了一种行业里称为“不完善抽象”的东西。
当你部署 PgBouncer 或 RDS Proxy 时,你很快就会发现,你多了一样基础设施,它改变了你使用数据库的方式。这迫使你做出取舍,通常还要修改应用代码。
如果你已经构建应用一段时间了(通常这就是你需要添加连接池的时候),这可能意味着要修改数千行生产代码,而且这些代码往往几乎没有测试覆盖。
有了 PgDog,这就不再是必需的了。
## 连接状态
添加连接池后,第一个受影响的是会话控制,即 `SET` 命令。这些命令用于临时覆盖数据库设置,例如执行较慢的查询:
```
SET statement_timeout TO '5m';
SELECT * FROM users WHERE banned IS true;
```
由于连接池会在客户端之间复用连接,一个客户端的连接状态会“泄漏”到另一个客户端的连接状态中。
在生产环境中,这可能会很糟糕。最好的情况是,一个慢查询运行了很长时间,引发数据库事故。最坏的情况下,依赖会话变量的行级安全(RLS)策略会失效,行数据悄悄消失。
因此,迁移到连接池时的典型建议就是:别再使用 `SET` 了。
然而,`SET` 是 Postgres 的一个真实特性。如果你不被允许使用一个数据库特性,那你就做了取舍,必须改变编写应用的方式。而如果你用它来做像 RLS 这样重要的事情,那么你根本就不能使用连接池。
## 处理 SET 语句
PgDog 内置了一个 SQL 解析器。它可以检测 `SET` 语句,提取变量名和值,并将它们存储在代理中每个客户端连接上。
当客户端执行查询时,PgDog 会先检查其状态是否与服务器匹配,如果不匹配,它会通过自行执行一系列 `SET` 语句来更新状态。
SET 语句在 PgDog 中的工作方式
我们用来检测 `SET` 语句的算法非常快速。如果有多个变量不同,我们会使用查询流水线(query pipelining)在一次往返中更新它们。
这使得性能影响很小,你可以在扩展时继续使用一直依赖的 Postgres 特性。
## LISTEN/NOTIFY
`LISTEN` 和 `NOTIFY` 是 Postgres 的命令,它们在 Postgres 内部实现了一个发布/订阅队列。这是一个很酷的特性,无需向你的技术栈中添加另一个数据库就能很好地工作。
这个特性在你添加池化器时也曾经不得不放弃——至少如果你想使用事务模式的话。
如果你在过去 10 年(PostgreSQL 10 发布于 2017 年)中构建过应用,你很可能用过它,然后才迁移到 SQS 或 Redis。
PgDog 也能让这个特性正常工作。它在内部处理这两个命令,同时在多个 PgDog 进程之间传递消息。对于客户端来说,看起来 PgDog 就是消息代理,但实际上它依然是 Postgres。
我们还确保保留了 `NOTIFY` 的所有事务语义,甚至包括那些导致我们的一些朋友宕掉(https://news.ycombinator.com/item?id=44490510)其数据库的语义(顺便说一下,我们已经修复(https://docs.pgdog.dev/features/connection-pooler/pub_sub/#transactions)了这个问题)。
内部实现还挺有意思的。我们使用 Tokio 的 `broadcast` 通道在同一 PgDog 进程内的客户端之间传递消息。为了支持多个 PgDog 进程(例如在生产环境中你会运行多个容器),我们还将所有 `LISTEN` 和 `NOTIFY` 命令通过一个专用连接发送给 Postgres。
因此,实际上 PgDog 扮演了一个发布/订阅客户端,代理其他发布/订阅客户端,而 Postgres 作为消息代理。这算是一种创造性的工程,只是为了让我们在扩展 Postgres 时能够“开箱即用”地使用一个特性。
SET 语句在 PgDog 中的工作方式
## 多线程
PgDog 基于 Tokio 构建,后者是一个带有工作线程的 Rust 异步运行时。每个客户端由自己的异步任务处理,并且随连接数线性扩展。
使用 Tokio 让我们能够利用多 CPU,从单个 PgDog 进程服务更多客户端和更多每秒查询数。
然而,既然 PgBouncer 已经支持 `SO_REUSEPORT`,RDS Proxy 也支持“无服务器”自动伸缩,那为什么这很重要呢?
这两个工具都要求你“分片”你的连接池。每个代理进程拥有自己专用的一组 Postgres 连接。一旦客户端连接,就不能再更换实例,所以如果某个实例过载,其他所有客户端也会被卡住。
通过在多个 CPU 上进行多线程处理,PgDog 进程能够处理大得多的流量。这使得它可以用更少的服务器连接池化更多客户端,从而提高连接利用率和效率。
多线程进程也能更好地应对突发的查询峰值,因为它们不需要等待自动伸缩。如果你的应用有延迟 SLA,你肯定不希望代理成为瓶颈。
最后同样重要的是,多线程进程的运维手册更短:只需一个指标和健康检查源,不必把复杂性推到难以调试的地方,比如 Linux 内核或 AWS RDS 控制平面。
## 总结
替换像 PgBouncer 这样有 20 年历史的项目可能并不容易,但当我们觉得某些地方不太对劲时,我们就会去修复它。PgDog 已经投入生产超过一年,以每秒 200 万次查询的速度进行连接池化。
它是免费的(使用和修改的自由),并且是开源的(https://github.com/pgdogdev/pgdog),你可以将其部署在任何地方(https://docs.pgdog.dev/installation/)。
相似文章
PgDog
PgDog 是一款无需修改应用程序即可扩展 PostgreSQL 的工具,提供连接池和负载均衡功能。
有人在不使用 PgBouncer 的情况下运行 Postgres 吗?
一位开发者回顾了一篇十年前的关于 Postgres 连接管理的文章,调查了流行的托管 Postgres 提供商,并认为 PgBouncer 式的连接池实际上是一个强制性的核心功能。
PgDog 获得融资,即将登陆您的数据库
PgDog 是一个开源代理,使 Postgres 实现水平扩展,已从 Basis Set、YC 等机构获得 550 万美元融资。该工具已在生产环境中每秒处理超过 200 万次查询。
PgBouncer 工作原理
一份详细的技术指南,解释 PgBouncer 作为 PostgreSQL 连接池的工作原理,涵盖其连接池模式、生产部署及常见陷阱。
受污染的Postgres连接池
本文解释了在使用PgBouncer时PostgreSQL中受污染的连接池问题,其中过时的会话状态可能导致写入错误,并提供了一种使用DISCARD ALL来重置连接的解决方案。