Problem:

A client running multiple Patroni-managed PostgreSQL high-availability clusters in production added PgBouncer on the database VMs. During load tests, they saw no CPU or RAM savings compared with direct application connections. They asked for the correct PgBouncer configuration approach, whether the pooler should run locally or on separate servers, and how it should fit with their existing HAProxy layer across all clusters.

Process:

Step 1: Analyze logs and connection behavior

The logs showed long-lived application connections, meaning the application already reused connections. We explained that in this case PgBouncer saves little connection setup work. Its main benefit is fewer PostgreSQL backends and less contention, while fixed memory allocations do not shrink when the connection count drops.

Step 2: Audit the PgBouncer configuration

We found two issues. First, the pool size allowed a single user/database pool to take the entire PostgreSQL connection limit, leaving no room for other pools or administrative access. Second, the PgBouncer service’s open-files limit was far below its configured maximum number of client connections.

Step 3: Design a controlled comparison

We prepared a corrected PgBouncer configuration with smaller trial pool sizes and a higher file-descriptor limit. We also provided a measurement script and a test plan comparing direct connections, the current PgBouncer setup, and the proposed one under identical workload, TLS, and warm-up conditions. The client was asked to confirm application compatibility with transaction pooling before testing.

Step 4: Advise on placement and remote architecture

We recommended keeping PgBouncer local on each eligible Patroni node, with HAProxy routing traffic to the current primary. At the client’s request, we also designed a remote option: HAProxy in front of a redundant pair of PgBouncer VMs, with a second HAProxy endpoint routing to the Patroni primary via its REST API health