我们将PgBouncer扩展到4倍吞吐量
摘要
ClickHouse Managed Postgres通过运行一组使用SO_REUSEPORT的进程,将PgBouncer扩展到4倍吞吐量,实现了多核利用率,并通过对等连接解决了取消转发问题。
暂无内容
查看缓存全文
缓存时间: 2026/07/11 16:24
# 我们如何在 ClickHouse Managed Postgres 中扩展 PgBouncer | ClickHouse
来源:https://clickhouse.com/blog/pgbouncer-clickhouse-managed-postgres
PgBouncer 是单线程的。单个进程只占用一个 CPU 核心,无论机器有多少核心。在一台 16 vCPU 的实例上,这意味着一个核心负责所有连接池操作,而其他十五个核心处于空闲状态,连接池在 Postgres 资源耗尽前就已达到吞吐瓶颈。
在 ClickHouse Managed Postgres 中,我们运行着一组 PgBouncer 进程,其规模与可用核心成比例。
这一组进程中的每个进程都绑定同一个端口,并启用了 `so_reuseport`。内核在新连接建立时对这些进程进行负载均衡,因此客户端仍然连接到一个单一端点,且永远不会知道背后有多个 PgBouncer 进程。这正是 PgBouncer 官方文档指出的利用多核机制:每个进程是单线程的,而 `so_reuseport` 让你能够充分利用每个核心。
pgbouncer_jul2026_image5.png
Postgres 的取消请求通过一个全新的连接携带取消密钥到达,与正在运行查询的连接是分开的。使用 `so_reuseport` 时,内核可能会将这个新连接分配给一个不同于持有会话的进程。取消请求被传递到一个从未听说过该查询的进程,从而导致无法生效。
进程间协作解决了这个问题。各个进程彼此感知,因此落入错误进程的取消请求会被转发到真正持有该会话的进程。即使在任意请求可能到达任一个进程的情况下,取消功能在整个进程中也能正常工作。
连接池运行在事务模式,因此一旦事务提交,服务器连接就会立即返回池中。连接预算在整个进程中均分:`max_client_conn` 和 `max_db_connections` 按进程数量进行分割,从而确保整个进程组永远不会过度占用 Postgres 资源。
我们在相同的 AWS EC2 实例上运行了两种配置:16 vCPU 的 `c7i.4xlarge` 用作连接池节点,另一台机器运行 Postgres,第三台机器使用 `pgbench` 在只读、事务池模式下驱动负载。其中一个池节点运行单个 PgBouncer 进程;另一个运行 16 个进程。相同的实例类型、相同的 Postgres、相同的工作负载。唯一变量是一个进程与十六个进程。
我们将客户端连接从 8 逐步增加到 256,测量吞吐量以及每个池节点对 16 核实例的实际利用率。
单个进程的峰值大约为 87k 笔事务/秒,然后在负载增加时性能反而下降,当客户端数达到 256 时,降至 77k 笔事务/秒,所有请求都在争夺一个核心。而进程组持续攀升至大约 336k 笔事务/秒,约为前者的 4 倍,因为它有更多核心可以利用。
单个进程始终无法使用超过一个核心的工作量:在负载下,`pidstat` 显示 PgBouncer 进程的 CPU 占用率锁定在 ~97%,占满一个核心,而整个 16 vCPU 实例的利用率却保持在 10% 以下。进程组则散布在整台机器上,达到了大约 8 个核心繁忙的状态,在 Postgres 和负载生成器成为瓶颈之前,仍有上升空间。
对每个节点施加稳定的 256 个客户端连接:单进程节点在整个运行期间 CPU 接近 9%,而进程组维持在约 52%。相同的实例类型、相同的 Postgres、相同的工作负载。一种配置让机器闲置,另一种配置则让它高效运转。
EC2 的 CloudWatch 指标从宿主机外部也反映了同样的情况:在负载期间,单进程实例的 CPUUtilization 平均约为 16%,而进程组约为 60%。CloudWatch 的读数比实例内指标略高,但差距仍然存在:在你支付了 16 vCPU 费用的实例上,单个 PgBouncer 几乎浪费了全部资源。
连接上限也遵循相同的规律。单个进程自行强制执行 `max_client_conn`,一旦超过限制,新客户端就会被拒绝:
```
FATAL: no more connections allowed (max_client_conn)
```
将预算分配给整个进程组,可以在保持每个进程以及 Postgres 处于安全范围内的同时,提高总连接上限。
| 客户端数 | 单进程 TPS | 单进程实例 CPU | 进程组 TPS | 进程组实例 CPU |
|----------|------------|----------------|-------------|----------------|
| 8 | 8,910 | 0.8% | 6,450 | 2.9% |
| 32 | 54,203 | 5.2% | 64,244 | 12.3% |
| 64 | 86,570 | 8.3% | 219,439 | 31.9% |
| 128 | 83,463 | 8.1% | 320,547 | 45.9% |
| 256 | 76,893 | 7.7% | 336,469 | 48.9% |
在少量连接时,单进程实际上表现也不错,甚至略微更快,因为此时无需并行处理,而进程组的连接被分散得较稀。差距恰好出现在关键场景:在高并发下,一个核心成为了瓶颈。
单个 PgBouncer 在默认情况下表现尚可,直到连接池(而非 Postgres)成为吞吐量的天花板。根据核心数调整进程组规模、通过 `so_reuseport` 共享一个端口、以及利用进程间协作将各进程连接起来,这些做法将连接池器重新变回“管道”而非瓶颈。
每个 ClickHouse Managed Postgres 服务器默认都附带这一配置。部署一个 Postgres 实例,即可看到实际效果。
相似文章
PgBouncer 工作原理
一份详细的技术指南,解释 PgBouncer 作为 PostgreSQL 连接池的工作原理,涵盖其连接池模式、生产部署及常见陷阱。
有人在不使用 PgBouncer 的情况下运行 Postgres 吗?
一位开发者回顾了一篇十年前的关于 Postgres 连接管理的文章,调查了流行的托管 Postgres 提供商,并认为 PgBouncer 式的连接池实际上是一个强制性的核心功能。
PgDog
PgDog 是一款无需修改应用程序即可扩展 PostgreSQL 的工具,提供连接池和负载均衡功能。
让Postgres队列实现可扩展性
一篇详细的技术博文,解释如何使用SKIP LOCKED和适当的事务隔离级别来扩展基于PostgreSQL的队列,实现每秒3万次工作流执行。
PostgresBench: 一个可复现的 Postgres 服务基准测试
ClickHouse 发布了 PostgresBench,这是一个公开且可复现的基准测试,用于比较托管式 Postgres 服务,它使用标准的 pgbench 工具,在多个缩放因子下运行类似 TPC-B 的工作负载。