为什么我们构建了另一个 Postgres 连接池
2026年7月6日 Lev Kokotov PgDog 是一个用于扩展 Postgres 的代理。它的一个功能是连接池,这允许多个客户端应用程序使用同一个数据库,而不会超出其连接限制。市面上有很多连接池工具,特别是 PgBouncer、RDS Proxy、Pgpool-II、Supavisor 等等。那么,为什么还要再构建一个呢? 泄露的抽象 许多 Postgres 生态系统中的工具非常认真地遵循 UNIX 哲学:做好一件事。通常来说,这是一种构建可靠软件的好方法,这也是 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 语句的算法非常快速。如果有多个变量不同,我们使用查询管道技术将它们在一次往返中更新。这样可以将性能影响降到最低,并且你在扩展时可以继续使用你一直依赖的 Postgres 特性。 LISTEN/NOTIFY LISTEN 和 NOTIFY 是实现 Postgres 内部发布/订阅队列的命令。这是一个很好的特性,它在不增加其他数据库到你的栈中的情况下工作得相当不错。当你增加一个连接池时,你不得不放弃这个特性,至少如果你想使用事务模式的话。如果你在过去的10年里构建了一个应用(PostgreSQL 10 于 2017 年推出),你可能在迁移到 SQS 或 Redis 之前使用过它。PgDog 也让这个功能正常工作。它在内部处理这两个命令,同时在多个 PgDog 进程之间移动消息。对于客户端来说,这看起来像 PgDog 是代理,但实际上,它仍然是 Postgres。我们还确保保留了所有 NOTIFY 的事务语义,甚至是那些导致一些我们的朋友关闭他们的数据库的语义(顺便说一下,我们修复了这个问题)。其内部实现相当有趣。我们使用 Tokio 的广播通道在同一个 PgDog 进程之间移动消息。为了支持多个 PgDog 进程(例如,在生产中,你会运行多个容器),我们还通过专用连接将所有 LISTEN 和 NOTIFY 命令发送到 Postgres。因此,PgDog 实际上充当发布/订阅客户端,代理其他发布/订阅客户端,使用 Postgres 作为代理。这是一种创造性的工程,旨在使我们在扩展 Postgres 时“只是工作”的特性正常运行。 多线程 PgDog 基于 Tokio 构建,它是一个具有多线程工作者的 Rust 异步运行时。每个客户端都由其自己的异步任务处理,随着连接数量的增加而线性扩展。使用 Tokio 使我们能够利用多个 CPU,能够从一个 PgDog 进程中为更多客户端和每秒更多查询提供服务。然而,随着 PgBouncer 支持 SO_REUSEPORT 和 RDS Proxy 的“无服务器”自动扩展,这有什么关系呢?这两个工具都要求你“分片”连接池。每个代理进程都有自己专用的 Postgres 连接集。一旦客户端连接,它就不能再更改实例,因此如果它变得过载,所有其他客户端也会卡住。通过在多个 CPU 上进行多线程,PgDog 进程可以处理更多流量。这使它能够在更少的服务中集中更多客户端。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡