Skip to content

Commit

Permalink
feat: cap exponential backoff to 24s and add jitter
Browse files Browse the repository at this point in the history
  • Loading branch information
jooola committed Jun 24, 2024
1 parent a7636bd commit 72bc103
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 5 deletions.
26 changes: 21 additions & 5 deletions hcloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"math"
"math/rand"
"net/http"
"net/url"
"strconv"
Expand Down Expand Up @@ -40,13 +41,28 @@ func ConstantBackoff(d time.Duration) BackoffFunc {
}

// ExponentialBackoff returns a BackoffFunc which implements an exponential
// backoff.
// It uses the formula:
// backoff, capped to 24 seconds with an added 50% of jitter.
// See [ExponentialBackoffWithOpts] for more details.
func ExponentialBackoff(multiplier float64, baseDuration time.Duration) BackoffFunc {
return ExponentialBackoffWithOpts(multiplier, baseDuration, 24*time.Second, 0.5)
}

// ExponentialBackoffWithOpts returns a BackoffFunc which implements an exponential
// backoff, capped to a provided maximum, with an additional jitter ratio.
//
// b^retries * d
func ExponentialBackoff(b float64, d time.Duration) BackoffFunc {
// It uses the formula:
// - backoff = min(capDuration, baseDuration * (multiplier ^ retries))
// - backoff = backoff + (rand(0, backoff) * jitterRatio).
func ExponentialBackoffWithOpts(multiplier float64, baseDuration time.Duration, capDuration time.Duration, jitterRatio float64) BackoffFunc {
baseSeconds := baseDuration.Seconds()
capSeconds := capDuration.Seconds()

return func(retries int) time.Duration {
return time.Duration(math.Pow(b, float64(retries))) * d
backoff := baseSeconds * math.Pow(multiplier, float64(retries)) // Exponential backoff
backoff = math.Min(capSeconds, backoff) // Cap backoff
backoff += rand.Float64() * backoff * jitterRatio // #nosec G404 Add jitter

return time.Duration(backoff * float64(time.Second))
}
}

Expand Down
19 changes: 19 additions & 0 deletions hcloud/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/hetznercloud/hcloud-go/v2/hcloud/schema"
)

Expand Down Expand Up @@ -378,3 +380,20 @@ func TestBuildUserAgent(t *testing.T) {
})
}
}

func TestExponentialBackoff(t *testing.T) {
// Turning off jitter for testing
backoffFunc := ExponentialBackoffWithOpts(2, 500*time.Millisecond, 24*time.Second, 0.0)

count := 8
sum := 0.0
result := make([]string, 0, count)
for i := 0; i < count; i++ {
backoff := backoffFunc(i)
sum += backoff.Seconds()
result = append(result, backoff.String())
}

require.Equal(t, []string{"500ms", "1s", "2s", "4s", "8s", "16s", "24s", "24s"}, result)
require.Equal(t, 79.5, sum)
}

0 comments on commit 72bc103

Please sign in to comment.