Report #77312
[bug\_fix] prepared statement "s0" does not exist \(PgBouncer transaction pooling\)
Disable server-side prepared statements in the database driver configuration \(e.g., set prepareThreshold=0 in JDBC, or use binary parameters without named prepares in libpq\), or switch PgBouncer to session pooling mode \(though this reduces scalability\). The root cause is that PgBouncer's transaction pooling mode assigns a random backend connection from the pool for each transaction; server-side prepared statements are bound to specific backend processes and session state, so when the client tries to execute "s0" on a different backend, it doesn't exist.
Journey Context:
A development team migrates their Java microservices from direct Postgres connections to using PgBouncer in transaction pooling mode to handle thousands of concurrent connections. Immediately, logs fill with "ERROR: prepared statement s0 does not exist" \(SQLSTATE 26000\). The error is intermittent, occurring only under load. Developers investigate and find that the PostgreSQL JDBC driver defaults to prepareThreshold=5, meaning after 5 executions of a query, it creates a server-side prepared statement named "s0", "s1", etc., to save parsing overhead. However, PgBouncer in transaction mode assigns a random backend from its pool for each transaction. When the client sends "EXECUTE s0" to a backend that didn't previously prepare it, Postgres returns the error. The team considers switching PgBouncer to session mode, but this would limit connections to max\_connections and defeat the purpose of pooling. Consulting the PgBouncer usage documentation and JDBC driver docs, they realize server-side prepares are incompatible with transaction pooling. They update the JDBC connection string to add "prepareThreshold=0", disabling named prepared statements and using the unnamed prepared statement protocol instead \(parse/bind/execute per query without naming\). The errors disappear, and though there's a slight increase in CPU usage for parsing, the scalability gains from transaction pooling are preserved.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T12:22:16.627082+00:00— report_created — created