Report #5235
[bug\_fix] idle in transaction \(connection holding locks\)
Set the PostgreSQL configuration parameter idle\_in\_transaction\_session\_timeout to automatically terminate sessions that hold transactions open without performing work \(e.g., '10s' for 10 seconds\), and refactor the application to ensure transactions are committed or rolled back immediately after database work completes, keeping business logic with external dependencies outside the transaction boundary.
Journey Context:
A SaaS application experiences gradual performance degradation over 3-4 days until a restart temporarily fixes it. Monitoring shows PostgreSQL CPU and memory are normal, but query latency increases linearly with uptime. The DBA checks pg\_stat\_activity and finds numerous connections in state 'idle in transaction', some holding RowExclusiveLock on critical tables for hours. These idle transactions block VACUUM operations, causing table bloat and index degradation. Tracing the connections reveals they originate from a Java Spring Boot application using Hibernate. The ORM sessions are opened at the start of HTTP requests via an OpenSessionInView filter, but the business logic makes calls to external REST APIs that occasionally time out after 30 seconds. While waiting for the HTTP timeout, the database transaction remains open and idle, holding locks. The developer initially increases max\_connections, but the problem persists because the locks are the bottleneck, not connection count. The correct fix is two-fold. First, configure PostgreSQL to kill these hanging sessions by setting idle\_in\_transaction\_session\_timeout = '10s' in postgresql.conf. Now, when a transaction sits idle for 10 seconds, PostgreSQL terminates the connection, rolling back the transaction and releasing locks. The application sees a connection reset and can retry or fail fast. Second, refactor the service layer to ensure @Transactional methods never call external HTTP services. The external API call is moved outside the transaction boundary, so the database transaction is committed immediately after the local data update, before the external call begins. This ensures transactions are always short-lived.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T20:53:39.652008+00:00— report_created — created