PgBouncer 吞吐量提升 4 倍的工程实践
PgBouncer 是单线程架构,单个进程仅占用一个 CPU 核心,无论服务器配备多少核心都是如此。以一台 16 核虚拟机为例,所有连接池工作都由这一个核心承担,其余 15 个核心完全闲置,而且远在 Postgres 达到性能上限前,PgBouncer 就会先出现吞吐量瓶颈。
在 ClickHouse 托管 Postgres 服务中,我们运行一组 PgBouncer 进程,数量与服务器可用核心数成正比。
所有进程均启用 so_reuseport 并绑定同一端口,由内核负责将入站连接均衡分配给各个进程。对客户端而言,只需连接单一端点,完全感知不到背后有多台 PgBouncer 在协同工作。这正是 PgBouncer 官方文档推荐的多核利用方案:进程本身保持单线程,通过 so_reuseport 让每一个核心都参与工作。

问题点:查询取消机制
Postgres 的取消请求通过全新连接发送,携带专属取消密钥,与执行查询的连接相互独立。在 so_reuseport 模式下,内核可能将这个新连接分配给持有会话的进程之外的其他进程,导致取消请求被发送到一个完全不了解该查询的进程,最终无法生效。
我们通过进程间通信(peering)解决了这个问题:所有进程彼此感知,当取消请求被分配到错误进程时,会被转发至真正持有会话的进程。如此一来,无论请求抵达哪个进程,整个集群的查询取消功能都能正常工作。
我们采用事务模式进行连接池管理,事务提交后,服务器连接会立即归还至连接池。同时,连接配额会在集群中均分:max_client_conn(最大客户端连接数)和 max_db_connections(最大数据库连接数)会按进程数量拆分,确保整个集群不会超出 Postgres 的连接上限。
真实硬件环境测试
我们在相同的 AWS EC2 实例上对比两种配置:一台 16 核 c7i.4xlarge 作为连接池服务器,一台独立服务器运行 Postgres,第三台服务器通过 pgbench 发起只读事务负载,采用事务池模式。其中一台连接池服务器运行单个 PgBouncer 进程,另一台运行 16 个进程。实例类型、Postgres 配置、工作负载完全一致,唯一变量是进程数量。
我们将客户端连接数从 8 逐步增加到 256,同时测量吞吐量,以及连接池服务器实际使用的核心资源占比。

单个进程的吞吐量在约 8.7 万次事务/秒时达到峰值,之后负载越高性能越差,当连接数增至 256 时,吞吐量降至 7.7 万次事务/秒——所有资源都在争抢唯一的核心。而集群模式下吞吐量持续攀升至约 33.6 万次事务/秒,是单进程的 4 倍左右,这得益于更多核心的并行处理能力。

单进程模式下,资源利用始终局限于一个核心:负载高峰时,pidstat 显示 PgBouncer 进程 CPU 占用率稳定在约 97%,完全占满一个核心,但整台 16 核服务器的整体 CPU 利用率始终低于 10%。集群模式下,负载均匀分布在多个核心上,最多有约 8 个核心处于忙碌状态,直到 Postgres 和负载生成器成为性能瓶颈时,服务器仍有剩余资源可用。

保持 256 个客户端连接数不变:单进程服务器全程 CPU 利用率约为 9%,而集群服务器约为 52%。相同的实例类型、相同的 Postgres、相同的工作负载,一种配置让服务器大量资源闲置,另一种则充分发挥了硬件性能。

EC2 自带的 CloudWatch 监控指标从外部视角验证了这一结果:负载测试期间,单进程实例的平均 CPU 利用率约为 16%,集群实例约为 60%。CloudWatch 的数值比服务器内部监测结果略高,但两者的差距一致:在你付费购买的 16 核服务器上,单个 PgBouncer 几乎浪费了所有闲置核心。
连接上限的表现也类似。单进程模式下,max_client_conn 由单个进程独立执行,一旦超出上限,新客户端会被拒绝连接:
FATAL: no more connections allowed (max_client_conn)
而通过在集群中均分连接配额,既能提高整体连接上限,又能确保每个进程和 Postgres 都处于安全的资源限制范围内。
| 客户端连接数 | 单进程事务数/秒 | 单进程服务器CPU利用率 | 集群事务数/秒 | 集群服务器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 托管 Postgres 服务器默认采用这一配置。创建一台 Postgres 实例即可体验这一架构的优势。
试用 ClickHouse 托管 Postgres
ClickHouse + Postgres 已成为高可扩展应用的统一数据栈。如今 ClickHouse Cloud 正式推出托管 Postgres 服务,让这一数据架构从项目启动之初就能为你所用。