Skip to content
atom®technologies
Engineering notes

PgBouncer rolling restarts: why idle clients delay exit

A local PgBouncer 1.26.0 study shows why a committed transaction can leave the process waiting for an idle client to disconnect.

A finished transaction can leave PgBouncer waiting

In our local PgBouncer 1.26.0 study, a retiring process stayed alive after its client's write had committed. The client was idle, and the study database had no active server connection in that process. Closing the client connection let the process exit. An operator planning a restart needs to know when the application will release that connection.

PgBouncer 1.26.0, released September 23, 2026, removes the old -R online restart option. Its changelog directs operators to rolling restarts with so_reuseport. We examined one part of that procedure: the relationship between completing work and disconnecting the client.

What WAIT_FOR_CLIENTS waits for

The documented SHUTDOWN WAIT_FOR_CLIENTS operation stops accepting new application connections and waits for existing clients to disconnect. With another PgBouncer process accepting connections on the shared port, new clients can reach that process while the first retires. In transaction pooling, completing a transaction releases its assigned server connection. The application can keep its client connection open.

Each measured retirement of A began with one SIGTERM, which has the documented WAIT_FOR_CLIENTS behavior in 1.26.0. A second signal during shutdown changes that behavior to immediate termination. The experiment did not repeat the signal while A was shutting down.

Three client lifetimes, repeated in fresh databases

We ran two PgBouncer processes, A and B, using transaction pooling and a shared TCP port on one Linux aarch64 container. Both processes and their replacements ran the same pinned distributor build of 1.26.0, with PostgreSQL and libpq 16.13. This was a normal restart study; compatibility between different PgBouncer versions was outside its scope.

A small libpq client controlled connection lifetime directly, without automatic retries, reconnects, or a production application pool. Client idle, transaction, and idle-transaction timeouts were disabled. Peering was configured, but cancellation was not tested. The publisher documents platform differences in so_reuseport; this result belongs to the recorded Linux environment.

Each case began with a client verified as connected to A. After A acknowledged shutdown, three new clients connected to B and completed one identified insert each. The original client also performed one insert, giving four expected writes per case.

The short-lived case closed its original connection after its autocommitted write and the three new-client probes. The retained-idle case kept that connection open for at least two more seconds. The transaction case had work in progress when shutdown was acknowledged, finished that work, received the commit acknowledgement, and then deliberately held the idle connection for at least another two seconds.

Across three fresh runs on October 5, all nine cases completed. All 36 expected insert IDs returned to the clients and were independently read as committed through a direct PostgreSQL connection. The records contain no application errors or unresolved IDs. Every new-client probe belonged to B, and both processes completed their planned restarts.

In all six retained-client cases, A was still alive at the idle observation. Its original client was idle and its study pool had zero active server connections. A subsequently exited after that client closed. The deliberately imposed two-second hold demonstrates the retained-client condition; it supplies no maximum shutdown duration or recommended timeout.

The retained client keeps A waitingObserved in the retained-idle and in-flight-transaction cases. Both PgBouncer processes run 1.26.0; client lifetime is controlled by the experiment.
  1. Shutdown acknowledged
    A begins retirement

    The original client remains connected to A. The harness has sent one graceful signal.

  2. New clients
    Three probes reach B

    Fresh clients complete their identified writes through the surviving process.

  3. Retained client
    Work is finished; A is alive

    The original client is idle. A's study pool has zero active server connections. The harness keeps the client open.

  4. Client closes
    A exits normally

    After the original connection closes, the harness observes A exit and verifies the replacement process.

Rehearse the application's connection retirement

Before assigning a restart deadline, establish what makes each application release an idle connection and how it treats a connection still carrying work. Rehearse that policy with the actual driver and pool. The hand-controlled client in this study cannot establish a named pool's behavior.

Observe the retiring process separately from the surviving one. Record which clients remain, whether their work has finished, and whether new work reaches the survivor. Check the service manager's grace period and termination policy as a separate part of the deployment. The Python supervisor used here waited for normal exit; it did not model a service manager enforcing a production deadline.

Changing a PgBouncer timeout also needs its own reasoning. The configuration reference advises keeping client_idle_timeout above the client-side lifetime settings and reserving it for network problems. Our disabled-timeout fixture does not justify a new value for an application's deployment.

These were sparse local probes with synthetic rows. They do not establish production availability, throughput, or crash durability: the database was disposable and lived on tmpfs. TLS, production authentication, network failure, cancellation routing, and forced termination remain outside the observed result.

If a maintenance window depends on behavior your team cannot yet explain, an Atom Critical Systems Assessment can investigate that connection path. It is a paid, bounded engagement with agreed access, acceptance evidence, and a staged execution plan your team can use.