77 "time"
88
99 "github.com/ClickHouse/ch-go"
10-
1110 "github.com/jackc/puddle/v2"
11+ "go.uber.org/zap"
1212)
1313
1414// Pool of connections to ClickHouse.
@@ -23,19 +23,26 @@ type Pool struct {
2323
2424// Options for Pool.
2525type Options struct {
26- ClientOptions ch.Options
27- MaxConnLifetime time.Duration
28- MaxConnIdleTime time.Duration
29- MaxConns int32
30- MinConns int32
31- HealthCheckPeriod time.Duration
26+ ClientOptions ch.Options
27+ MaxConnLifetime time.Duration
28+ MaxConnIdleTime time.Duration
29+ MaxConns int32
30+ MinConns int32
31+ HealthCheckPeriod time.Duration
32+ HealthCheckFunc func (ctx context.Context , client * ch.Client ) error
33+ HealthCheckTimeout time.Duration
34+ }
35+
36+ func DefaultHealthCheckFunc (ctx context.Context , client * ch.Client ) error {
37+ return client .Ping (ctx )
3238}
3339
3440// Defaults for pool.
3541const (
36- DefaultMaxConnLifetime = time .Hour
37- DefaultMaxConnIdleTime = time .Minute * 30
38- DefaultHealthCheckPeriod = time .Minute
42+ DefaultMaxConnLifetime = time .Hour
43+ DefaultMaxConnIdleTime = time .Minute * 30
44+ DefaultHealthCheckPeriod = time .Minute
45+ DefaultHealthCheckTimeout = time .Second
3946)
4047
4148func (o * Options ) setDefaults () {
@@ -51,6 +58,15 @@ func (o *Options) setDefaults() {
5158 if o .HealthCheckPeriod == 0 {
5259 o .HealthCheckPeriod = DefaultHealthCheckPeriod
5360 }
61+ if o .HealthCheckFunc == nil {
62+ o .HealthCheckFunc = DefaultHealthCheckFunc
63+ }
64+ if o .HealthCheckTimeout == 0 {
65+ o .HealthCheckTimeout = DefaultHealthCheckTimeout
66+ }
67+ if o .ClientOptions .Logger == nil {
68+ o .ClientOptions .Logger = zap .NewNop ()
69+ }
5470}
5571
5672// Dial returns a pool of connections to ClickHouse.
@@ -162,16 +178,46 @@ func (p *Pool) backgroundHealthCheck() {
162178func (p * Pool ) checkIdleConnsHealth () {
163179 resources := p .pool .AcquireAllIdle ()
164180
165- now := time . Now ()
181+ wg := sync. WaitGroup {}
166182 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 ()
183+ res := res
184+ wg .Add (1 )
185+ go func () {
186+ if res .IdleDuration () > p .options .MaxConnIdleTime || ! p .connIsHealthy (res ) {
187+ res .Destroy ()
188+ } else {
189+ res .ReleaseUnused ()
190+ }
191+ }()
192+ wg .Wait ()
193+ }
194+ }
195+
196+ func (p * Pool ) connIsHealthy (res * puddle.Resource [* connResource ]) bool {
197+ logger := p .options .ClientOptions .Logger
198+ if res .Value ().client .IsClosed () {
199+ logger .Debug ("chpool: connection is closed" )
200+ return false
201+ }
202+
203+ if time .Since (res .CreationTime ()) > p .options .MaxConnLifetime {
204+ logger .Debug ("chpool: connection over max lifetime" )
205+ return false
206+ }
207+
208+ if p .options .HealthCheckFunc != nil {
209+ ctx , cancel := context .WithTimeout (context .Background (), p .options .HealthCheckTimeout )
210+ defer cancel ()
211+ if err := p .options .HealthCheckFunc (ctx , res .Value ().client ); err != nil {
212+ if logger := p .options .ClientOptions .Logger ; logger != nil {
213+ logger .Warn ("chpool: health check failed" , zap .Error (err ))
214+ }
215+ return false
173216 }
174217 }
218+
219+ res .Value ().lastHealthCheckTimestamp = time .Now ()
220+ return true
175221}
176222
177223func (p * Pool ) checkMinConns () {
0 commit comments