Skip to content

Commit 99814b3

Browse files
committed
Add pool tuning options to pg client
Expose WithMinPoolSize, WithMaxConnIdleTime, and WithMaxConnLifetime to let callers tune pgxpool connection lifecycle and reduce acquire tail latency from cold connections.
1 parent 96af6c0 commit 99814b3

2 files changed

Lines changed: 86 additions & 3 deletions

File tree

pg/client.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"io"
2626
"net"
2727
"strconv"
28+
"time"
2829

2930
"github.com/jackc/pgx/v5"
3031
"github.com/jackc/pgx/v5/multitracer"
@@ -54,7 +55,10 @@ type (
5455

5556
debug bool
5657

57-
poolSize int32
58+
poolSize int32
59+
minPoolSize int32
60+
maxConnIdleTime time.Duration
61+
maxConnLifetime time.Duration
5862

5963
tlsConfig *tls.Config
6064

@@ -135,12 +139,53 @@ func WithTLS(certs []*x509.Certificate) Option {
135139
}
136140
}
137141

142+
// WithPoolSize sets the maximum number of connections the pool will
143+
// open. It maps to pgxpool.Config.MaxConns.
138144
func WithPoolSize(i int32) Option {
139145
return func(c *Client) {
140146
c.poolSize = i
141147
}
142148
}
143149

150+
// WithMinPoolSize sets the minimum number of connections the pool
151+
// keeps warm. It maps to pgxpool.Config.MinConns. Keeping a non-zero
152+
// floor avoids paying the TCP + TLS + PostgreSQL startup handshake
153+
// cost on the first acquire after an idle period, which otherwise
154+
// shows up as tail latency on pgxpool_acquire_duration_seconds.
155+
//
156+
// When unset, the client defaults to 1 (historical behaviour).
157+
func WithMinPoolSize(i int32) Option {
158+
return func(c *Client) {
159+
c.minPoolSize = i
160+
}
161+
}
162+
163+
// WithMaxConnIdleTime sets how long a connection can be idle before
164+
// it is eligible to be destroyed. It maps to
165+
// pgxpool.Config.MaxConnIdleTime. A zero value leaves the pgx default
166+
// (30 minutes) in place.
167+
//
168+
// Increasing this value reduces connection churn for bursty
169+
// workloads, at the cost of holding idle connections longer.
170+
func WithMaxConnIdleTime(d time.Duration) Option {
171+
return func(c *Client) {
172+
c.maxConnIdleTime = d
173+
}
174+
}
175+
176+
// WithMaxConnLifetime sets the maximum lifetime of a connection
177+
// before it is recycled. It maps to pgxpool.Config.MaxConnLifetime.
178+
// A zero value leaves the pgx default (1 hour) in place.
179+
//
180+
// Increasing this value reduces connection churn for long-lived
181+
// processes, at the cost of keeping the same backend connection
182+
// open for longer.
183+
func WithMaxConnLifetime(d time.Duration) Option {
184+
return func(c *Client) {
185+
c.maxConnLifetime = d
186+
}
187+
}
188+
144189
// WithTracerProvider configures OpenTelemetry tracing with the
145190
// provided tracer provider.
146191
func WithTracerProvider(tp trace.TracerProvider) Option {
@@ -182,6 +227,7 @@ func NewClient(options ...Option) (*Client, error) {
182227
user: "postgres",
183228
database: "postgres",
184229
poolSize: 10,
230+
minPoolSize: 1,
185231
logger: log.NewLogger(log.WithOutput(io.Discard)),
186232
tracerProvider: otel.GetTracerProvider(),
187233
registerer: prometheus.DefaultRegisterer,
@@ -208,8 +254,14 @@ func NewClient(options ...Option) (*Client, error) {
208254
config.ConnConfig.Password = c.password
209255
config.ConnConfig.Database = c.database
210256
config.ConnConfig.TLSConfig = c.tlsConfig
211-
config.MinConns = 1
212-
config.MaxConns = int32(c.poolSize)
257+
config.MinConns = c.minPoolSize
258+
config.MaxConns = c.poolSize
259+
if c.maxConnIdleTime > 0 {
260+
config.MaxConnIdleTime = c.maxConnIdleTime
261+
}
262+
if c.maxConnLifetime > 0 {
263+
config.MaxConnLifetime = c.maxConnLifetime
264+
}
213265

214266
c.tracer = c.tracerProvider.Tracer(
215267
tracerName,

pg/client_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,37 @@ func TestNewClient(t *testing.T) {
191191
_ = newTestClient(t, pg.WithPoolSize(2))
192192
},
193193
)
194+
195+
t.Run(
196+
"with min pool size option",
197+
func(t *testing.T) {
198+
_ = newTestClient(
199+
t,
200+
pg.WithPoolSize(5),
201+
pg.WithMinPoolSize(2),
202+
)
203+
},
204+
)
205+
206+
t.Run(
207+
"with max conn idle time option",
208+
func(t *testing.T) {
209+
_ = newTestClient(
210+
t,
211+
pg.WithMaxConnIdleTime(5*time.Minute),
212+
)
213+
},
214+
)
215+
216+
t.Run(
217+
"with max conn lifetime option",
218+
func(t *testing.T) {
219+
_ = newTestClient(
220+
t,
221+
pg.WithMaxConnLifetime(15*time.Minute),
222+
)
223+
},
224+
)
194225
}
195226

196227
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)