@@ -6,9 +6,10 @@ import (
66 "sync"
77 "time"
88
9- "github.com/ClickHouse/ch-go"
10-
119 "github.com/jackc/puddle/v2"
10+ "go.uber.org/zap"
11+
12+ "github.com/ClickHouse/ch-go"
1213)
1314
1415// Pool of connections to ClickHouse.
@@ -23,19 +24,26 @@ type Pool struct {
2324
2425// Options for Pool.
2526type Options struct {
26- ClientOptions ch.Options
27- MaxConnLifetime time.Duration
28- MaxConnIdleTime time.Duration
29- MaxConns int32
30- MinConns int32
31- HealthCheckPeriod time.Duration
27+ ClientOptions ch.Options
28+ MaxConnLifetime time.Duration
29+ MaxConnIdleTime time.Duration
30+ MaxConns int32
31+ MinConns int32
32+ HealthCheckPeriod time.Duration
33+ HealthCheckFunc func (ctx context.Context , client * ch.Client ) error
34+ HealthCheckTimeout time.Duration
35+ }
36+
37+ func DefaultHealthCheckFunc (ctx context.Context , client * ch.Client ) error {
38+ return client .Ping (ctx )
3239}
3340
3441// Defaults for pool.
3542const (
36- DefaultMaxConnLifetime = time .Hour
37- DefaultMaxConnIdleTime = time .Minute * 30
38- DefaultHealthCheckPeriod = time .Minute
43+ DefaultMaxConnLifetime = time .Hour
44+ DefaultMaxConnIdleTime = time .Minute * 30
45+ DefaultHealthCheckPeriod = time .Minute
46+ DefaultHealthCheckTimeout = time .Second
3947)
4048
4149func (o * Options ) setDefaults () {
@@ -51,6 +59,15 @@ func (o *Options) setDefaults() {
5159 if o .HealthCheckPeriod == 0 {
5260 o .HealthCheckPeriod = DefaultHealthCheckPeriod
5361 }
62+ if o .HealthCheckFunc == nil {
63+ o .HealthCheckFunc = DefaultHealthCheckFunc
64+ }
65+ if o .HealthCheckTimeout == 0 {
66+ o .HealthCheckTimeout = DefaultHealthCheckTimeout
67+ }
68+ if o .ClientOptions .Logger == nil {
69+ o .ClientOptions .Logger = zap .NewNop ()
70+ }
5471}
5572
5673// Dial returns a pool of connections to ClickHouse.
@@ -162,16 +179,47 @@ func (p *Pool) backgroundHealthCheck() {
162179func (p * Pool ) checkIdleConnsHealth () {
163180 resources := p .pool .AcquireAllIdle ()
164181
165- now := time . Now ()
182+ wg := sync. WaitGroup {}
166183 for _ , res := range resources {
167- if now .Sub (res .CreationTime ()) > p .options .MaxConnLifetime {
168- res .Destroy ()
169- } else if res .IdleDuration () > p .options .MaxConnIdleTime {
170- res .Destroy ()
171- } else {
172- res .ReleaseUnused ()
184+ res := res
185+ wg .Add (1 )
186+ go func () {
187+ defer wg .Done ()
188+ if res .IdleDuration () > p .options .MaxConnIdleTime || ! p .connIsHealthy (res ) {
189+ res .Destroy ()
190+ } else {
191+ res .ReleaseUnused ()
192+ }
193+ }()
194+ wg .Wait ()
195+ }
196+ }
197+
198+ func (p * Pool ) connIsHealthy (res * puddle.Resource [* connResource ]) bool {
199+ logger := p .options .ClientOptions .Logger
200+ if res .Value ().client .IsClosed () {
201+ logger .Debug ("chpool: connection is closed" )
202+ return false
203+ }
204+
205+ if time .Since (res .CreationTime ()) > p .options .MaxConnLifetime {
206+ logger .Debug ("chpool: connection over max lifetime" )
207+ return false
208+ }
209+
210+ if p .options .HealthCheckFunc != nil {
211+ ctx , cancel := context .WithTimeout (context .Background (), p .options .HealthCheckTimeout )
212+ defer cancel ()
213+ if err := p .options .HealthCheckFunc (ctx , res .Value ().client ); err != nil {
214+ if logger := p .options .ClientOptions .Logger ; logger != nil {
215+ logger .Warn ("chpool: health check failed" , zap .Error (err ))
216+ }
217+ return false
173218 }
174219 }
220+
221+ res .Value ().lastHealthCheckTimestamp = time .Now ()
222+ return true
175223}
176224
177225func (p * Pool ) checkMinConns () {
0 commit comments