Report #77811
[bug\_fix] Idle in transaction timeout \(broken pipe\)
Refactor application logic to ensure transactions are committed or rolled back immediately after database operations complete, never holding them open during external I/O, user input, or computation. If long transactions are unavoidable, either increase the \`idle\_transaction\_timeout\` in PgBouncer \(or \`idle\_in\_transaction\_session\_timeout\` in Postgres\) to accommodate the duration, or implement application-level reconnection logic that gracefully handles closed connections by starting a new transaction.
Journey Context:
A production Rails application using PgBouncer in transaction pooling mode experiences sporadic 'PG::ConnectionBad: server closed the connection unexpectedly' errors. The pattern is consistent: the error always occurs exactly 60 seconds after a transaction was opened. Checking PgBouncer logs reveals \`idle\_transaction\_timeout: 60\`. Investigating the application code finds a background job that starts a transaction, queries a record, then performs a lengthy external API call that averages 90 seconds, then updates the record and commits. PgBouncer, seeing the server backend connection idle in transaction for 60 seconds, forcibly closes it to prevent resource exhaustion. The client \(Rails\) still thinks the connection is valid and attempts to commit on the dead socket, resulting in the broken pipe. The 'aha' moment comes from realizing that PgBouncer's timeout is a safeguard against exactly this anti-pattern. The fix works because it aligns the transaction lifetime strictly with database work, eliminating the idle window where the proxy would rightfully kill the connection.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T13:12:21.330180+00:00— report_created — created