Skip to content

Feature: pool connectionLifeTime (max connection age) option for connection recycling #4384

Description

@aistrati

mysql2's pool can only recycle connections when they're idle (idleTimeout + maxIdle).
There's no way to cap a connection's maximum age. Under sustained load a connection never
goes idle, so it lives indefinitely and stays pinned to whichever backend it first connected to.

This is a problem when the pool sits in front of a load-balanced / elastic MySQL topology:

  • Aurora / RDS reader endpoints: each pooled connection is routed to one reader for its
    lifetime. When a reader is added (auto-scaling) or after a failover, existing
    long-lived connections don't move — the new capacity stays underused until connections churn.
    (RDS Proxy's reader endpoint doesn't fix this; it also pins a connection to a single reader.)
  • Any HAProxy / NLB / proxy setup where you want connections to periodically re-resolve and
    rebalance.

A max-lifetime (with jitter) is the standard remedy — e.g. HikariCP maxLifetime, and the
legacy mysqljs/mysql had this via a community fork - https://github.com/vita-mojo/mysql. mysql2 has no equivalent as far as i know.

Describe the solution you'd like

A pool option that discards a connection once it exceeds a maximum age, replacing it on next
borrow, with small random jitter to avoid a synchronized die-off:

mysql.createPool({
  // ...
  maxLifetime: 600000, // ms; 0 = unlimited (default). Recycle connections older than this.
});
  • Name maxLifetime (ms) to match HikariCP and mysql2's existing ms conventions
    (idleTimeout). (The mysqljs/mysql fork called it connectionLifeTime in seconds — either
    is fine, but ms + maxLifetime is more consistent here.)
  • Apply built-in jitter (e.g. up to 5–10%) so pooled connections don't all expire at once.

Proposed implementation

We've run this behaviour in production for a long time via a fork of the legacy mysqljs/mysql
driver that adds a connectionLifeTime option (in seconds)
(mysqljs/mysql@master...vita-mojo:mysql:master), and we're now
carrying the same logic on mysql2 as a patch-package patch. We'd like to see it upstream, as
mysql2 has no equivalent.

The sketch below is the same ~30-line change, but renamed to the proposed upstream API
maxLifetime in milliseconds (to match idleTimeout and HikariCP) instead of the fork's
connectionLifeTime in seconds. Naming/units are of course open to whatever maintainers prefer.
Mapped onto mysql2's current internals:

lib/pool_config.js — parse the option:

this.maxLifetime = isNaN(options.maxLifetime) ? 0 : Number(options.maxLifetime);

lib/pool_connection.js — stamp an expiry on creation (with jitter):

if (pool.config.maxLifetime > 0) {
  const jitter = Math.floor(Math.random() * (pool.config.maxLifetime / 20 + 1)); // up to 5%
  this._expiresAt = Date.now() + pool.config.maxLifetime - jitter;
}

lib/base/pool.js — in getConnection, skip/close expired free connections instead of
handing them out:

while (this._freeConnections.length > 0) {
  connection = this._freeConnections.pop();
  if (connection._expiresAt && Date.now() > connection._expiresAt) {
    connection._pool = null;
    spliceConnection(this._allConnections, connection);
    connection.destroy();       // frees the slot; a fresh connection is created below
    continue;
  }
  this.emit('acquire', connection);
  return process.nextTick(() => { connection._released = false; cb(null, connection); });
}

Plus: register maxLifetime so it isn't flagged by the "invalid configuration option"
validation in lib/connection_config.js. (Optional enhancement: also proactively close expired
connections in the existing _removeIdleTimeoutConnections sweep so they don't linger until the
next borrow.)

Alternatives considered

  • idleTimeout / maxIdle — idle-based only; busy connections never recycle.
  • App-level recycling / scheduled process restarts — works but coarse and app-specific.
  • RDS Proxy — helps failover / writer pooling, but its reader endpoint still pins a
    connection to a single reader, so it doesn't rebalance reads after scaling.

Prior art

  • HikariCP maxLifetime (de-facto standard for JDBC pools).
  • mysqljs/mysql community fork adding connectionLifeTime.

I'm happy to open a PR with the implementation above + tests if maintainers are open to it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions