Skip to content

Configurable idle keep-alive probe for pooled SQL connections - #1702

Open
vpelikh wants to merge 1 commit into
eclipse-vertx:masterfrom
vpelikh:feature/idle-keepalive
Open

vpelikh wants to merge 1 commit into
eclipse-vertx:masterfrom
vpelikh:feature/idle-keepalive

Conversation

@vpelikh

@vpelikh vpelikh commented Sep 18, 2026

Copy link
Copy Markdown

Summary

Adds an application-level keep-alive mechanism to the SQL connection pool so connections that are silently dropped while idle (for example by an intermediate load balancer or the database itself) are detected and replaced with a fresh connection instead of failing the next user query with a Connection timed out error.

The mechanism works in two parts:

  1. Probe — when a pooled connection has been idle for at least a configurable interval, the pool issues a lightweight live statement against it before handing it to a caller.
  2. Recovery — if the probe fails because the connection is stale, the pool closes it (removing it from the pool) and transparently retries the command on a fresh connection.

Changes

Core (vertx-sql-client)

  • PoolOptions.idleKeepAlive / setIdleKeepAlive (with unit, default 0 = disabled). Configures how long a pooled connection can be idle before it is probed.
  • spi/protocol/PingCommand — a probe command carrying the lightweight statement used to verify liveness.
  • Connection.keepAliveQuery() SPI — the pool reads the probe statement from the connection, keeping the pool database-agnostic.
  • Pool (PoolImpl / SqlConnectionPool):
    • probeIfNeeded issues the PingCommand on checkout when the idle threshold is exceeded; on failure it closes the stale connection, which removes it from the pool via the connector onRemove callback (ConnectionPool.evict only removes unused connections, so a leased stale one must be closed instead).
    • execute transparently retries the command on a fresh connection, bounded by MAX_KEEPALIVE_RETRIES.

PostgreSQL (vertx-pg-client)

  • PgConnectOptions.keepAliveQuery — the probe statement is moved to configuration (default SELECT 1).
  • PgSocketConnection implements keepAliveQuery() and encodes a PingCommand as a simple query on the wire.

Tests

  • PgPoolKeepAliveTest:
    • a healthy idle connection is kept alive (same backend PID);
    • a connection dropped at the network level (via ProxyServer, simulating a load-balancer silent close) recovers transparently on a fresh backend.

Why an application-level probe

TCP keep-alive alone is often insufficient: load balancers can drop idle connections without sending a reset, and OS keep-alive intervals are long. An application-level probe actively verifies the connection is usable right before it is used, directly addressing idle stale-connection failures.

Design notes

  • Off by default (idleKeepAlive = 0), so no behaviour change for existing users.
  • The probe statement is configurable per driver (PgConnectOptions.keepAliveQuery), so each database can provide a correct, portable no-op statement.

Interaction with the vertx-core connection pool

The stale-connection handling is scoped to the SQL client and deliberately does not change the vertx-core connection pool:

  • ConnectionPool.evict(...) only removes unused connections (usage == 0). That is an intentional contract of vertx-core, so evicting a stale leased connection there would change public pooling semantics.
  • Instead, recovery uses the existing vertx-core mechanism: when the probe fails, the SQL pool closes the stale connection, which fires the connector onRemove() callback. vertx-core's Remove action handles a leased connection correctly — it drops it and, if a waiter is queued, immediately connects a replacement.

This keeps the fix localized to vertx-sql-client and preserves all vertx-core pool guarantees.

Add an application-level keep-alive mechanism to the SQL connection pool so
connections silently dropped while idle (e.g. by a load balancer or the
database) are detected and replaced, instead of failing the next query with a
connection error.

When a pooled connection has been idle for at least a configurable interval,
the pool issues a lightweight probe statement against it before handing it to
a caller. If the probe fails because the connection is stale, the pool closes
it (removing it via the connector onRemove callback, since ConnectionPool.evict
only removes unused connections) and transparently retries the command on a
fresh connection, bounded by MAX_KEEPALIVE_RETRIES.

Core (vertx-sql-client):
- PoolOptions.idleKeepAlive / setIdleKeepAlive (default 0 = disabled)
- PingCommand carrying the configurable probe statement
- Connection.keepAliveQuery() SPI; the pool issues it on checkout and recovers
  from stale connections in execute()

PostgreSQL (vertx-pg-client):
- PgConnectOptions.keepAliveQuery (default 'SELECT 1'), implemented by
  PgSocketConnection and encoded as a simple query on the wire

Tests (PgPoolKeepAliveTest):
- a healthy idle connection is kept alive (same backend PID)
- a connection dropped at the network level via ProxyServer recovers
  transparently on a fresh backend

Signed-off-by: Vasily Pelikh <vasily.pelikh@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant