Skip to content

JDBC backend validates the pooled connection on every borrow, costing a database round trip per operation #879

Description

@vharseko

Describe the bug

Every borrow from the pool validates the connection it takes out, and Connection.isValid() is a round trip to the database:

// opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java:84-97
CachedConnection con = cached.get(connectionString).poll(waitTime, TimeUnit.MILLISECONDS);
while (con != null) {
    if (!con.isValid(0)) {
        ...
    } else {
        return con;
    }
}

Every operation of the backend borrows through this path — JDBCStorage.read(), write(), the cursor of a search, the import. So one LDAP operation costs two exchanges with the database rather than one: the validation, and then the statement the operation actually came for. On PostgreSQL isValid runs an empty query, on MySQL it sends a ping, on Oracle it is a round trip of its own; all of them are a network round trip and a scheduling of the database's own worker.

The check earns its keep on a connection that has been sitting in the pool: the database may have closed it, a firewall may have dropped the flow. It earns nothing on a connection that came back to the pool a millisecond ago, which — with a pool that keeps connections for org.openidentityplatform.opendj.jdbc.ttl (15 s by default) and a backend under load — is most of them.

Impact

A fixed per-operation latency of one database round trip, and a matching amount of work on the database, on every search and every modify. On a database reached over a network rather than over loopback it is the dominant part of the cost of a small operation: the entry read of read() is a single indexed row, and it is paid for twice.

Sibling of the work in #859 / #860 / #863, which were about exactly this kind of per-operation cost in the same backend.

Expected behavior

Skip the validation for a connection returned to the pool recently — the "alive bypass window" of HikariCP (aliveBypassWindowMs, 500 ms by default) is the established shape of this: remember when a connection went back into the pool, and validate only when it has been idle longer than the window. A connection that broke inside the window still surfaces as a failure of the statement itself, which is where a connection breaking mid-operation surfaces anyway.

Worth measuring alongside it: whether the validation is needed at all on the path where the connection is handed straight back out, or whether letting the statement fail and retrying the borrow once is cheaper in aggregate.

Environment

master (5.2.x), all four JDBC dialects. #876 gives this validation the bound it was missing (isValid(0) means "no timeout" in the JDBC contract) but leaves it on every borrow, so the round trip described here is unchanged by it.

Noticed while fixing #872 (PR #876).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions