Skip to content

Commit f876e77

Browse files
authored
Observer: add jitter to each monitor to prevent stampedes (#8872)
Update monitor.start() to do a random sleep before probing for the first time, and to do a 20% jitter on its sleep time between each probe. This 20% jitter matches the 20% used in core.RetryBackoff. While we're at it, update start() to take a context and listen for context cancellation, to allow for nicer cleanup at shutdown. Fixes #8871
1 parent 70e308e commit f876e77

2 files changed

Lines changed: 28 additions & 7 deletions

File tree

observer/monitor.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package observer
22

33
import (
44
"context"
5+
"math/rand/v2"
56
"strconv"
67
"time"
78

@@ -14,10 +15,19 @@ type monitor struct {
1415
prober probers.Prober
1516
}
1617

17-
// start spins off a 'Prober' goroutine on an interval of `m.period`
18-
// with a timeout of half `m.period`
19-
func (m monitor) start(logger blog.Logger) {
20-
ticker := time.NewTicker(m.period)
18+
// start spins off a 'Prober' goroutine approximately once per `m.period`,
19+
// with a timeout of half `m.period`. The probe attempts start after a random
20+
// delay and have 20% jitter around the configured period, to prevent many
21+
// monitors with the same period from all waking up at the same time.
22+
func (m monitor) start(ctx context.Context, logger blog.Logger) {
23+
// Wait a random duration of at most one period before the first probe,
24+
// so that monitors don't all fire at once when the process starts.
25+
select {
26+
case <-ctx.Done():
27+
return
28+
case <-time.After(rand.N(m.period)):
29+
}
30+
2131
for {
2232
go func() {
2333
ctx, cancel := context.WithTimeout(context.Background(), m.period/2)
@@ -42,6 +52,13 @@ func (m monitor) start(logger blog.Logger) {
4252
m.prober.Kind(), err == nil, dur.Seconds(), m.prober.Name())
4353
}
4454
}()
45-
<-ticker.C
55+
56+
// This jitter is equivalent to 1 +/- 0.2*rand.Float64().
57+
jitter := 0.8 + 0.4*rand.Float64()
58+
select {
59+
case <-ctx.Done():
60+
return
61+
case <-time.After(time.Duration(float64(m.period) * jitter)):
62+
}
4663
}
4764
}

observer/observer.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ type Observer struct {
2222

2323
// Start spins off a goroutine for each monitor, and waits for a signal to exit
2424
func (o *Observer) Start() {
25+
defer o.shutdown(context.Background())
26+
27+
ctx, cancel := context.WithCancel(context.Background())
28+
defer cancel()
29+
2530
for _, mon := range o.monitors {
26-
go mon.start(o.logger)
31+
go mon.start(ctx, o.logger)
2732
}
2833

29-
defer o.shutdown(context.Background())
3034
cmd.WaitForSignal()
3135
}

0 commit comments

Comments
 (0)