Skip to content

Commit f92fcc1

Browse files
authored
discovery: cap health check reconnect backoff at 10s (#19967)
Signed-off-by: Gwanho Kim <khkim6040@gmail.com>
1 parent c7336d8 commit f92fcc1

2 files changed

Lines changed: 59 additions & 5 deletions

File tree

go/vt/discovery/tablet_health_check.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,25 @@ func (thc *tabletHealthCheck) checkConn(hc *HealthCheckImpl) {
323323
return
324324
case <-time.After(retryDelay):
325325
// Exponentially back-off to prevent tight-loop.
326-
retryDelay *= 2
327-
// Limit the retry delay backoff to the health check timeout
328-
if retryDelay > hc.healthCheckTimeout {
329-
retryDelay = hc.healthCheckTimeout
330-
}
326+
retryDelay = nextHealthCheckRetryDelay(retryDelay)
331327
}
332328
}
333329
}
334330

331+
// maxHealthCheckRetryDelay caps the exponential back-off between healthcheck
332+
// reconnection attempts. Capping well below healthCheckTimeout (default 1m)
333+
// ensures vtgate rediscovers a recovered tablet promptly instead of sleeping
334+
// out a back-off that had grown to the silence timeout. See #19894.
335+
const maxHealthCheckRetryDelay = 10 * time.Second
336+
337+
// nextHealthCheckRetryDelay doubles the current back-off delay, capping it at
338+
// maxHealthCheckRetryDelay. A delay already above the cap (operator-configured)
339+
// is left unchanged, never reduced. See #19894.
340+
func nextHealthCheckRetryDelay(current time.Duration) time.Duration {
341+
next := current * 2
342+
return min(next, max(current, maxHealthCheckRetryDelay))
343+
}
344+
335345
func (thc *tabletHealthCheck) closeConnection(ctx context.Context, err error) {
336346
thc.logger.Warningf("tablet %v healthcheck stream error: %v", thc.Tablet, err)
337347
thc.setServingState(false, err.Error())
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
Copyright 2026 The Vitess Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package discovery
18+
19+
import (
20+
"testing"
21+
"time"
22+
23+
"github.com/stretchr/testify/assert"
24+
)
25+
26+
// TestNextHealthCheckRetryDelay verifies the back-off doubles below the cap,
27+
// caps at maxHealthCheckRetryDelay, and leaves an already-above-cap delay
28+
// unchanged rather than reducing it. See #19894.
29+
func TestNextHealthCheckRetryDelay(t *testing.T) {
30+
// Doubles while comfortably below the cap.
31+
assert.Equal(t, 20*time.Millisecond, nextHealthCheckRetryDelay(10*time.Millisecond))
32+
assert.Equal(t, 2*time.Second, nextHealthCheckRetryDelay(time.Second))
33+
34+
// Caps at maxHealthCheckRetryDelay once doubling would exceed it.
35+
assert.Equal(t, maxHealthCheckRetryDelay, nextHealthCheckRetryDelay(6*time.Second))
36+
assert.Equal(t, maxHealthCheckRetryDelay, nextHealthCheckRetryDelay(maxHealthCheckRetryDelay))
37+
38+
// A delay already above the cap is left unchanged, not reduced.
39+
assert.Equal(t, time.Hour, nextHealthCheckRetryDelay(time.Hour))
40+
41+
// The cap stays well under the default health check timeout, so a recovered
42+
// tablet is not stranded behind a back-off grown to the silence timeout.
43+
assert.Less(t, maxHealthCheckRetryDelay, DefaultHealthCheckTimeout)
44+
}

0 commit comments

Comments
 (0)