May 26, 2026 by Rahim Kanji · Tech

Part 4 - PgBouncer to ProxySQL: Extended and Prepared Protocols

Picture a normal PostgreSQL request path. The driver parses a statement, binds parameters, executes it, and then often comes back for the same statement again. That is the traffic pattern that matters in production, because it is the one that decides whether a proxy stays invisible or becomes the bottleneck.

Part 3 answered the simple-mode question. Part 4 asks the one that feels closer to real life: if we switch to PostgreSQL’s extended and prepared query paths, does ProxySQL still hold up against PgBouncer?

Using the same hardware, backend pool, and pgbench workload as Part 3, the answer is yes. ProxySQL stays ahead in both -M extended and -M prepared, and the gap widens as it uses more CPU inside a single instance.

TL;DR ProxySQL stays ahead in both extended and prepared mode. With 2 workers it delivers 1.30x to 1.71x PgBouncer; with 4 workers it reaches 1.53x to 2.30x.

For one of the optimizations behind ProxySQL’s prepared-mode numbers, read how the prepared statement cache refactor improved ProxySQL performance.

This post closes the loop from Part 1: Rethinking the PostgreSQL Middle Tier, Part 2: A Brief Feature Comparison, and Part 3: Simple-Mode Benchmark.


Benchmark setup

Nothing changed except the protocol. Hardware, TLS, backend pool size, and workload stay fixed so we can see what PostgreSQL’s extended and prepared paths do on their own.

ItemValue
HardwareAMD Ryzen 9 5950X, 32 vCPU threads, 125 GiB RAM
PostgreSQL17.10
PgBouncer1.25.1
pgbench18.3
ProxySQLpre-release of 3.0.9
TLSEnabled on every hop: client -> pooler -> PostgreSQL
WorkloadSELECT * FROM pgbench_accounts WHERE aid = $1 returning a 4 KB result set
Backend pool50 connections for both PgBouncer and ProxySQL
PgBouncer setuppool_mode = transaction, max_prepared_statements = 256
Clients1, 50, 100, 500, 1000
ProxySQL workers1, 2, 4
Timing20 s warmup, 60 s measured run
Reporting3 iterations per cell, reported as 3-run averages

If you want the full configuration and harness details, they are the same as Part 3. This post focuses on the protocol change: simple text versus PostgreSQL’s extended and prepared paths.


The first chart

Pooler throughput across concurrency in extended and prepared mode

The shape is easy to read. At one client, the poolers sit close together. There is not enough pressure yet for the proxy design to matter much.

At c=50 and c=100, the path starts to separate. ProxySQL with one worker already stays competitive, and two or four workers start turning CPU into more throughput.

By c=500 and c=1000, the backend pool is fully saturated. At that point the proxy is mostly managing queues, and ProxySQL’s multi-threaded design keeps moving more requests per second than a single-threaded process.

Extended mode

The raw numbers below show the same pattern in extended mode.

c=1c=50c=100c=500c=1000
PgBouncer8.8K36.5K35.9K33.4K32.7K
ProxySQL 1 worker12.6K38.5K42.6K31.5K28.4K
ProxySQL 2 workers12.2K59.3K53.1K46.8K42.6K
ProxySQL 4 workers11.9K78.9K77.6K51.3K50.1K

Prepared mode

Prepared mode tells the same story again, but it is a little more interesting because it leans harder on ProxySQL’s PostgreSQL prepared-statement work.

c=1c=50c=100c=500c=1000
PgBouncer11.4K35.9K35.2K30.8K29.7K
ProxySQL 1 worker12.8K40.6K40.2K32.5K29.5K
ProxySQL 2 workers12.5K61.4K55.8K48.0K43.8K
ProxySQL 4 workers12.3K82.5K79.7K54.3K53.2K

The heatmap below makes the same point another way. PgBouncer is the 1.00x baseline in every column.

ProxySQL throughput relative to PgBouncer

The important part is not the exact color in any one cell. It is the pattern: one worker stays close, two workers pull ahead, four workers widen the gap. That holds in both extended and prepared mode.


Why ProxySQL wins here

Part 2 showed the functional side of the story: ProxySQL keeps routing, monitoring, and query policy in the proxy itself. Part 4 shows the performance side: it can do that while staying competitive on the protocols PostgreSQL applications actually use.

PgBouncer is intentionally lean and single-threaded per process. That is good when you only need pooling. ProxySQL is built to do more at the proxy layer: parse PostgreSQL traffic, apply query rules, track backend role and lag, and keep those decisions inside a single operational surface.

The benchmark numbers show that this extra capability is not paid for with a throughput penalty on these workloads. In simple terms, ProxySQL is not just the more capable proxy here. It is also the one that turns more CPU into more throughput when the proxy becomes the bottleneck.


What this means in practice

If your applications use PostgreSQL’s extended or prepared protocol, ProxySQL still looks like the better choice. It keeps more policy in the proxy, and on this workload it turns that into throughput rather than overhead. With two workers the gain is already substantial; with four workers the gap is obvious.


Bottom line

Part 4 strengthens the answer from Part 3 instead of softening it. Once the protocol matches how PostgreSQL applications actually behave, ProxySQL still leads, and the lead grows as you give it more worker threads.

That is the real takeaway from Part 4: moving from PgBouncer to ProxySQL is not a trade between capability and speed. In this benchmark, ProxySQL gives you the more capable proxy and, in most of the meaningful concurrency range, more throughput too.