Report #80260
[bug\_fix] PgBouncer transaction pooling prepared statement errors
Disable prepared statement caching in the client driver \(e.g., set prepareThreshold=0 in JDBC, or use queryMode=simple in libpq\), or switch PgBouncer to session pooling mode. Root cause: Transaction pooling mode assigns backend connections per transaction, not per client. Prepared statements are stored in the backend session; when the next transaction runs on a different backend, the statement handle is invalid or refers to another statement, causing 'prepared statement already exists' or protocol errors.
Journey Context:
A Java Spring Boot application is moved from direct RDS PostgreSQL connections to PgBouncer in transaction mode for better concurrency. Immediately, the logs flood with 'ERROR: prepared statement "S\_1" already exists' and 'FATAL: protocol violation'. The application uses JDBC with default settings \(prepareThreshold=5, meaning after 5 queries it switches to binary prepared statements\). Investigation shows that PgBouncer's transaction mode multiplexes many client connections onto fewer PostgreSQL backends. When Client A prepares 'S\_1' on Backend 1, then Client B \(in a new transaction\) gets assigned to Backend 1, it tries to prepare 'S\_1' again, causing a name collision. If Client B gets a different backend, the 'S\_1' handle from Client A is invalid in the new session, causing protocol errors. The solution is adding '?prepareThreshold=0' to the JDBC connection string to disable server-side prepared statements, forcing simple query protocol. Alternatively, switching PgBouncer to session mode \(pool\_mode=session\) maintains session affinity, defeating the multiplexing benefit but fixing the error.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T17:18:58.946233+00:00— report_created — created