Report #27090
[bug\_fix] ERROR: prepared statement 'a12345' does not exist \(SQLSTATE 26000\) with PgBouncer
Disable prepared statements in the application/database driver \(e.g., Rails database.yml: 'prepared\_statements: false', or Go lib/pq: 'binary\_parameters=no'\), or switch PgBouncer to session pooling mode \(pool\_mode=session\) though this reduces concurrency. Root cause: PgBouncer's transaction pooling shares physical connections across logical sessions; prepared statements are session-bound objects that don't persist across connection switches.
Journey Context:
A Ruby on Rails 7 team deploys their application with PgBouncer 1.20 in front of Postgres 15 to handle connection limits. They use the default transaction pooling mode. Immediately, production logs fill with 'ActiveRecord::StatementInvalid: PG::NoPreparedStatement: ERROR: prepared statement 'a7f8d9e2' does not exist' \(SQLSTATE 26000\). The errors are random - some requests work, others fail. The team investigates and learns that Rails \(ActiveRecord\) uses prepared statements by default to optimize query execution. When a request starts, Rails prepares a statement like 'SELECT \* FROM users WHERE id = $1' and assigns it a name like 'a7f8d9e2'. It then executes it. However, with PgBouncer in transaction mode, each transaction \(or query\) might be assigned to a different physical Postgres connection from the pool. Prepared statements are tied to the specific backend process \(session\). When Rails tries to execute the prepared statement on a different connection than where it was prepared, Postgres reports it doesn't exist. The team considers switching PgBouncer to session mode \(pool\_mode=session\), but this means connections are held for the duration of the client session, drastically reducing the number of clients they can support \(defeating the purpose\). The correct solution is to disable prepared statements in Rails by adding 'prepared\_statements: false' to their database.yml configuration. This eliminates the use of named prepared statements, allowing the app to work correctly with PgBouncer's transaction pooling. After this change, the errors disappear and connection usage remains efficient.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T23:52:14.896514+00:00— report_created — created