Report #13504
[bug\_fix] PgBouncer transaction pooling breaking session features
Change PgBouncer pool\_mode from transaction to session, or bypass PgBouncer for connections using LISTEN/NOTIFY, PREPARE, or SET; root cause is transaction pooling mode which returns connections to the pool after each transaction, losing session state \(prepared statements, listen channels, GUCs\).
Journey Context:
A real-time notification system using Node.js and PostgreSQL's LISTEN/NOTIFY breaks immediately after inserting PgBouncer to handle connection scaling. The application establishes a dedicated connection, runs LISTEN new\_events;, and waits for notifications. However, no notifications are ever received despite the database receiving NOTIFY commands. The logs show no errors, but the connection appears idle. Investigating pg\_stat\_activity on the Postgres server shows the LISTEN session exists but is on backend PID 12345. However, the NOTIFY is being sent by the application through a different PgBouncer connection which lands on backend PID 67890. Realizing PgBouncer is in pool\_mode=transaction \(the default for many configs\), which means after the LISTEN command finishes \(a transaction boundary\), the connection is returned to the pool. The next query from the same client might land on a completely different backend process that never executed LISTEN. The fix involves changing the pgbouncer.ini for this database to pool\_mode=session, ensuring the connection remains pinned to the same backend for its entire lifetime. Alternatively, for LISTEN/NOTIFY specifically, bypass PgBouncer entirely and connect directly to PostgreSQL, as long-running LISTEN connections don't benefit from pooling anyway.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T18:52:40.902510+00:00— report_created — created