Skip to content

Commit 56ff85e

Browse files
committed
Add jitter and health check options to pg client
Add WithMaxConnLifetimeJitter and WithHealthCheckPeriod so callers can spread out connection recycle events and tune how often the pool reaps and tops up idle connections. The jitter option defaults to 5 minutes to avoid synchronized reconnect storms out of the box; the health check period leaves the pgx default in place when unset.
1 parent 7cb1900 commit 56ff85e

3 files changed

Lines changed: 79 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- **pg**: `WithMaxConnLifetimeJitter` option to smear connection recycle events and avoid synchronized reconnect storms. Defaults to 5 minutes; pass `0` to disable.
13+
- **pg**: `WithHealthCheckPeriod` option to tune how often the pool checks idle connections and enforces MinConns/MaxConnIdleTime/MaxConnLifetime. A zero value leaves the pgx default (1 minute) in place.
14+
1015
## [0.7.0] - 2026-04-22
1116

1217
### Added

pg/client.go

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ type (
5555

5656
debug bool
5757

58-
poolSize int32
59-
minPoolSize int32
60-
maxConnIdleTime time.Duration
61-
maxConnLifetime time.Duration
58+
poolSize int32
59+
minPoolSize int32
60+
maxConnIdleTime time.Duration
61+
maxConnLifetime time.Duration
62+
maxConnLifetimeJitter time.Duration
63+
healthCheckPeriod time.Duration
6264

6365
tlsConfig *tls.Config
6466

@@ -198,6 +200,39 @@ func WithMaxConnLifetime(d time.Duration) Option {
198200
}
199201
}
200202

203+
// WithMaxConnLifetimeJitter sets the duration of randomness added to
204+
// each connection's MaxConnLifetime, smearing recycle events across
205+
// time. It maps to pgxpool.Config.MaxConnLifetimeJitter.
206+
//
207+
// Without jitter, connections opened around the same time (for
208+
// example after a deploy) tend to be recycled together, producing
209+
// synchronized reconnect storms that show up as latency spikes on
210+
// pgxpool_acquire_duration_seconds. A non-zero jitter spreads those
211+
// recycles out.
212+
//
213+
// When unset, the client defaults to 5 minutes. Pass a zero value
214+
// explicitly via this option to disable jitter.
215+
func WithMaxConnLifetimeJitter(d time.Duration) Option {
216+
return func(c *Client) {
217+
c.maxConnLifetimeJitter = d
218+
}
219+
}
220+
221+
// WithHealthCheckPeriod sets how often the pool checks the health of
222+
// its idle connections and enforces MinConns/MaxConnIdleTime/
223+
// MaxConnLifetime. It maps to pgxpool.Config.HealthCheckPeriod. A
224+
// zero value leaves the pgx default (1 minute) in place.
225+
//
226+
// Lowering the period makes the pool react faster to dropped
227+
// connections (for example after a failover) and refill below
228+
// MinConns more promptly, at the cost of slightly more background
229+
// work.
230+
func WithHealthCheckPeriod(d time.Duration) Option {
231+
return func(c *Client) {
232+
c.healthCheckPeriod = d
233+
}
234+
}
235+
201236
// WithTracerProvider configures OpenTelemetry tracing with the
202237
// provided tracer provider.
203238
func WithTracerProvider(tp trace.TracerProvider) Option {
@@ -235,14 +270,15 @@ func WithDebug() Option {
235270
// }
236271
func NewClient(options ...Option) (*Client, error) {
237272
c := &Client{
238-
addr: "localhost:5432",
239-
user: "postgres",
240-
database: "postgres",
241-
poolSize: 10,
242-
minPoolSize: 1,
243-
logger: log.NewLogger(log.WithOutput(io.Discard)),
244-
tracerProvider: otel.GetTracerProvider(),
245-
registerer: prometheus.DefaultRegisterer,
273+
addr: "localhost:5432",
274+
user: "postgres",
275+
database: "postgres",
276+
poolSize: 10,
277+
minPoolSize: 1,
278+
maxConnLifetimeJitter: 5 * time.Minute,
279+
logger: log.NewLogger(log.WithOutput(io.Discard)),
280+
tracerProvider: otel.GetTracerProvider(),
281+
registerer: prometheus.DefaultRegisterer,
246282
}
247283

248284
for _, o := range options {
@@ -274,6 +310,12 @@ func NewClient(options ...Option) (*Client, error) {
274310
if c.maxConnLifetime > 0 {
275311
config.MaxConnLifetime = c.maxConnLifetime
276312
}
313+
if c.maxConnLifetimeJitter > 0 {
314+
config.MaxConnLifetimeJitter = c.maxConnLifetimeJitter
315+
}
316+
if c.healthCheckPeriod > 0 {
317+
config.HealthCheckPeriod = c.healthCheckPeriod
318+
}
277319

278320
c.tracer = c.tracerProvider.Tracer(
279321
tracerName,

pg/client_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,26 @@ func TestNewClient(t *testing.T) {
223223
},
224224
)
225225

226+
t.Run(
227+
"with max conn lifetime jitter option",
228+
func(t *testing.T) {
229+
_ = newTestClient(
230+
t,
231+
pg.WithMaxConnLifetimeJitter(2*time.Minute),
232+
)
233+
},
234+
)
235+
236+
t.Run(
237+
"with health check period option",
238+
func(t *testing.T) {
239+
_ = newTestClient(
240+
t,
241+
pg.WithHealthCheckPeriod(30*time.Second),
242+
)
243+
},
244+
)
245+
226246
t.Run(
227247
"exposes expected pgxpool metrics",
228248
func(t *testing.T) {

0 commit comments

Comments
 (0)