Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: database/sql: make free connection strategy configurable #68539

Open
kishoresenji opened this issue Jul 21, 2024 · 5 comments
Open
Labels
Milestone

Comments

@kishoresenji
Copy link

Proposal Details

The current strategy of lookup of a free connection is based on "most recently used" and this helps in closing connections as soon as they become idle. But this strategy creates traffic imbalance between multiple replicas of a cluster (behind haproxy) as it prefers recently used connections, some read replicas in a cluster get much higher qps than other replicas.

Proposal is to add a configurable option to DB type using which the free connection strategy will be using "least recently used" and picks connections from the beginning of the freeConn slice. Although this does not let idle connections to be cleaned up as connections won't be idle compared to the current strategy, this new strategy can be coupled with SetConnMaxLifetime to ensure that after ConnMaxLifetime, the optimal resources are being used.

@gopherbot gopherbot added this to the Proposal milestone Jul 21, 2024
@ianlancetaylor
Copy link
Member

CC @kardianos

@ianlancetaylor ianlancetaylor moved this to Incoming in Proposals Jul 22, 2024
@kardianos
Copy link
Contributor

@kishoresenji I'm skeptical. However, if you provide a proof of concept or sketch that shows this could be reasonably extracted from the current code base, I'd be game to re-evaluate. If the connection use strategy was able to be extracted to some internal interface, and the type is used cleanly throughout, then perhaps this could clean up existing code.

However, if any implementation resulted in more complicated or harder to understand logic surrounding this, that would be an instant no.

@kishoresenji
Copy link
Author

@kardianos As the freeConn primitive in both strategies (most recently used or least recently used) is the same (i.e both strategies will order from oldest to newest on returedAt), we can keep it very simple and not add a new interface. The only place that needs a change is in the conn method on how the free connection is picked. There should not be any change anywhere else in the code.

We can have a new enum which models which strategy to be used.

type cachedConnPickStrategy uint8
const (
	mostRecentlyUsedStrategy cachedConnPickStrategy = iota
	leastRecentlyUsedStrategy
)

and LeastRecentlyUsed strategy can be enabled with an option on the DB. By default, it will be MostRecentlyUsed strategy.

func (db *DB) SetLeastRecentlyUsedCachedConnectionStrategy() {
	db.mu.Lock()
	defer db.mu.Unlock()
	db.cachedConnPickStrategy = leastRecentlyUsedStrategy
}

The conn method would change like this:

	// Prefer a free connection, if possible.
	if strategy == cachedOrNewConn {
		if conn := db.getCachedConnDBLocked(); conn != nil {
			conn.inUse = true
			if conn.expired(lifetime) {
				db.maxLifetimeClosed++
				db.mu.Unlock()
				conn.Close()
				return nil, driver.ErrBadConn
			}
			db.mu.Unlock()

			// Reset the session if required.
			if err := conn.resetSession(ctx); errors.Is(err, driver.ErrBadConn) {
				conn.Close()
				return nil, err
			}

			return conn, nil
		}
	}

where getCachedConnDBLocked is like the following:

func (db *DB) getCachedConnDBLocked() *driverConn {
	var conn *driverConn
	if len(db.freeConn) == 0 {
		return conn
	}
	switch db.cachedConnPickStrategy {
	case leastRecentlyUsedStrategy:
		conn = db.freeConn[0]
		db.freeConn = db.freeConn[1:]
	default:
		last := len(db.freeConn)-1
		conn = db.freeConn[last]
		db.freeConn = db.freeConn[:last]
	}
	return conn
}

@kishoresenji
Copy link
Author

kishoresenji commented Jul 23, 2024

I have put the above code in a commit in my fork: 7f5dd3c?diff=split&w=1

@kishoresenji
Copy link
Author

@kardianos gentle ping to know if there is a chance on getting this proposal accepted or I should look at other alternatives. We are seeing imbalance in our clusters and that is why I started this proposal. Another approach is to have pooling outside of database/sql by setting maxIdleConns to -1 and supplying a Connector, which does the pooling, to OpenDB function. The connector will return a Connection which just returns the Connection back to the pool on Close from database/sql package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

4 participants