我们将PgBouncer扩展到4倍吞吐量

Hacker News Top 新闻

摘要

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 工作原理

Lobsters Hottest

一份详细的技术指南,解释 PgBouncer 作为 PostgreSQL 连接池的工作原理,涵盖其连接池模式、生产部署及常见陷阱。

PgDog

Product Hunt

PgDog 是一款无需修改应用程序即可扩展 PostgreSQL 的工具,提供连接池和负载均衡功能。

让Postgres队列实现可扩展性

Hacker News Top

一篇详细的技术博文,解释如何使用SKIP LOCKED和适当的事务隔离级别来扩展基于PostgreSQL的队列,实现每秒3万次工作流执行。