From 6603ddfbe446491f948edcb31bed97e835eb3b28 Mon Sep 17 00:00:00 2001 From: Darius Jahandarie Date: Sat, 15 Mar 2025 19:14:26 +0900 Subject: [PATCH] add MinIdleConns --- pgxpool/common_test.go | 1 + pgxpool/pool.go | 28 ++++++++++++++++++++++++++-- pgxpool/pool_test.go | 3 ++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/pgxpool/common_test.go b/pgxpool/common_test.go index b2797027..009616a1 100644 --- a/pgxpool/common_test.go +++ b/pgxpool/common_test.go @@ -147,6 +147,7 @@ func assertConfigsEqual(t *testing.T, expected, actual *pgxpool.Config, testName assert.Equalf(t, expected.MaxConnIdleTime, actual.MaxConnIdleTime, "%s - MaxConnIdleTime", testName) assert.Equalf(t, expected.MaxConns, actual.MaxConns, "%s - MaxConns", testName) assert.Equalf(t, expected.MinConns, actual.MinConns, "%s - MinConns", testName) + assert.Equalf(t, expected.MinIdleConns, actual.MinIdleConns, "%s - MinIdleConns", testName) assert.Equalf(t, expected.HealthCheckPeriod, actual.HealthCheckPeriod, "%s - HealthCheckPeriod", testName) assertConnConfigsEqual(t, expected.ConnConfig, actual.ConnConfig, testName) diff --git a/pgxpool/pool.go b/pgxpool/pool.go index 270b7617..e22ed289 100644 --- a/pgxpool/pool.go +++ b/pgxpool/pool.go @@ -17,6 +17,7 @@ import ( var defaultMaxConns = int32(4) var defaultMinConns = int32(0) +var defaultMinIdleConns = int32(0) var defaultMaxConnLifetime = time.Hour var defaultMaxConnIdleTime = time.Minute * 30 var defaultHealthCheckPeriod = time.Minute @@ -87,6 +88,7 @@ type Pool struct { afterRelease func(*pgx.Conn) bool beforeClose func(*pgx.Conn) minConns int32 + minIdleConns int32 maxConns int32 maxConnLifetime time.Duration maxConnLifetimeJitter time.Duration @@ -144,6 +146,13 @@ type Config struct { // to create new connections. MinConns int32 + // MinIdleConns is the minimum number of idle connections in the pool. You can increase this to ensure that + // there are always idle connections available. This can help reduce tail latencies during request processing, + // as you can avoid the latency of establishing a new connection while handling requests. It is superior + // to MinConns for this purpose. + // Similar to MinConns, the pool might temporarily dip below MinIdleConns after connection closes. + MinIdleConns int32 + // HealthCheckPeriod is the duration between checks of the health of idle connections. HealthCheckPeriod time.Duration @@ -189,6 +198,7 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) { afterRelease: config.AfterRelease, beforeClose: config.BeforeClose, minConns: config.MinConns, + minIdleConns: config.MinIdleConns, maxConns: config.MaxConns, maxConnLifetime: config.MaxConnLifetime, maxConnLifetimeJitter: config.MaxConnLifetimeJitter, @@ -271,7 +281,8 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) { } go func() { - p.createIdleResources(ctx, int(p.minConns)) + targetIdleResources := max(int(p.minConns), int(p.minIdleConns)) + p.createIdleResources(ctx, targetIdleResources) p.backgroundHealthCheck() }() @@ -334,6 +345,17 @@ func ParseConfig(connString string) (*Config, error) { config.MinConns = defaultMinConns } + if s, ok := config.ConnConfig.Config.RuntimeParams["pool_min_idle_conns"]; ok { + delete(connConfig.Config.RuntimeParams, "pool_min_idle_conns") + n, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("cannot parse pool_min_idle_conns: %w", err) + } + config.MinIdleConns = int32(n) + } else { + config.MinIdleConns = defaultMinIdleConns + } + if s, ok := config.ConnConfig.Config.RuntimeParams["pool_max_conn_lifetime"]; ok { delete(connConfig.Config.RuntimeParams, "pool_max_conn_lifetime") d, err := time.ParseDuration(s) @@ -472,7 +494,9 @@ func (p *Pool) checkMinConns() error { // TotalConns can include ones that are being destroyed but we should have // sleep(500ms) around all of the destroys to help prevent that from throwing // off this check - toCreate := p.minConns - p.Stat().TotalConns() + + // Create the number of connections needed to get to both minConns and minIdleConns + toCreate := max(p.minConns-p.Stat().TotalConns(), p.minIdleConns-p.Stat().IdleConns()) if toCreate > 0 { return p.createIdleResources(context.Background(), int(toCreate)) } diff --git a/pgxpool/pool_test.go b/pgxpool/pool_test.go index 90428931..4cc7f47e 100644 --- a/pgxpool/pool_test.go +++ b/pgxpool/pool_test.go @@ -43,10 +43,11 @@ func TestConnectConfig(t *testing.T) { func TestParseConfigExtractsPoolArguments(t *testing.T) { t.Parallel() - config, err := pgxpool.ParseConfig("pool_max_conns=42 pool_min_conns=1") + config, err := pgxpool.ParseConfig("pool_max_conns=42 pool_min_conns=1 pool_min_idle_conns=2") assert.NoError(t, err) assert.EqualValues(t, 42, config.MaxConns) assert.EqualValues(t, 1, config.MinConns) + assert.EqualValues(t, 2, config.MinIdleConns) assert.NotContains(t, config.ConnConfig.Config.RuntimeParams, "pool_max_conns") assert.NotContains(t, config.ConnConfig.Config.RuntimeParams, "pool_min_conns") }