DEV Community

Juan Vega
Juan Vega

Posted on

A golang library to retry operations with exponential backoff

I have created go-again, a small library to retry operations using different timing algorithms. By default, it uses exponential backoff with a jitter to generate various delays for each retry.

GitHub logo jdvr / go-again

A set of utility algorithms to retry operations, again and again.

Go Again


A simple and configurable retry library for go, with exponential backoff, and constant delay support out of the box Inspired by backoff.

Features

  • Configurable delay calculation algorithm
  • Support for exponential backoff and constant delay out of the box
  • Support for generics
  • Simple and clean interface

There are two main concepts:

  • Retry: Given an operation and a ticks calculator keeps retrying until either permanent error or timeout happen
  • TicksCalculator: Provide delay for retryer to wait between retries

Examples:

Call an API using exponential backoff

package main
import (
    "context"
    "errors"
    "fmt"
    "net/http"

    "github.com/jdvr/go-again"
)


func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    apiResponse, err := again.Retry[*http.Response](ctx, func(ctx context.Context) (*http.Response, error) {
        fmt.Println("Running Operation"
Enter fullscreen mode Exit fullscreen mode

Retry any function using exponential backoff:

package main

import (
    "context"
    "errors"
    "fmt"
    "net/http"

    "github.com/jdvr/go-again"
)


func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    apiResponse, err := again.Retry[*http.Response](ctx, func(ctx context.Context) (*http.Response, error) {
        fmt.Println("Running Operation")

        resp, err := http.DefaultClient.Get("https://sameflaky.api/path")
        if err != nil {
            // operation will be retried
            return nil, err
        }

        if resp.StatusCode == http.StatusForbidden {
            // no more retries
            return nil, again.NewPermanentError(errors.New("no retry, permanent error"))
        }

        if resp.StatusCode > 400 {
            return nil, errors.New("this will be retry")
        }

        // do whatever you need with a valid response ...

        return resp, nil // no retry
    })
    if err != nil {
        panic(err)
    }

    fmt.Printf("Finished with response %v\n", apiResponse)
}
Enter fullscreen mode Exit fullscreen mode

Exponential backoff and jitter

Quoting Wikpedia about Exponential Backoff:

Exponential backoff is an algorithm that uses feedback to multiplicatively decrease the rate of some process to gradually find an acceptable rate.

The idea is to generate longer wait periods between each retry, assuming the system will work at some point because the issue is just a matter of time.

Suppose you have an API to get the user profile that might fail. In that case, you can introduce a retry function to keep trying the request, and this retry will wait longer between each period. Now imagine that your API crashed due to workload.

Backoff won't help. Although the request will be triggered with a delay period, all the clients follow the same algorithm with the same delays, so your workload issue is even worse.

The algorithm uses a jitter to solve the issue of the exact delays by generating a small random delay gap between different clients. Instead of having all clients waiting for 500ms, they pick a random number between 450 and 550, distributing the server workload wisely.

Go Again

I created this library for fun, but it is production ready, and of course, I will use it as soon as I have the opportunity. It is inspired by backoff, the library I am using now.

go-again goes beyond exponential backoff and offers another algorithm for a constant delay in case you want to keep it simple. Furthermore, you can define your own algorithm easily by implementing an interface.

Top comments (1)

Collapse
 
manuartero profile image
Manuel Artero Anguita 🟨

Sounds very cool Juan!