backoff and retry in go

April 16, 2017    go retry backoff

Failure is a way of life. Requests(http or others..) can fail for many reasons. Decision to stop or retry can be very critical for applications. backoff algorithms provide a way to backoff and retry on a failure. There are two popular methods to backoff, constant backoff and exponential backoff.

I use backoff library, which is a Go port of exponential backoff algorithm from Google’s HTTP Client Library for Java.

backoff provides 4 main functionalities. Constant Backoff, Exponential Backoff, Retry, and Retry with Notify

Based on application needs, we can use either constant backoff or exponential backoff. Here are some examples of both.

Constant Backoff:

Constant BackOff is a backoff policy that always returns the same backoff delay.


package main

import "github.com/cenkalti/backoff"

func main(){
	b := backoff.NewConstantBackOff(1*time.Second)
	err = backoff.Retry(doSomething(), b)
	if err != nil{
		log.Fatalf("error after retrying: %v", err)
	}
}

func doSomething() error {
	//do something and return error
	var err error
	return err
}

Exponential BackOff:

ExponentialBackOff is a backoff implementation that increases the backoff period for each retry attempt using a randomization function that grows exponentially.


package main

import "github.com/cenkalti/backoff"

func main(){
	b := backoff.NewExponentialBackOff()
	b.MaxElapsedTime = 3 *time.Minute

	err = backoff.Retry(doSomething(), b)
	if err != nil{
		log.Fatalf("error after retrying: %v", err)
	}
}

func doSomething() error {
	//do something and return error
	var err error
	return err
}

That is fine but what if your function returns a tuple of value and error and you also want to log the initial errors. How do you retry that function using backoff library.

Exponential BackOff with notify:

package main

import "github.com/cenkalti/backoff"

func main(){
	b := backoff.NewExponentialBackOff()
	b.MaxElapsedTime = 3 *time.Minute

	var (
		var int64
		err error
	)

	retryable := func() error {
		val, err = doSomething()
	}

	notify := func(err error, t time.Duration){
		log.Printf("error: %v happened at time: %v", err, t)
	}

	err = backoff.RetryNotify(retryable, b, notify)
	if err != nil{
		log.Fatalf("error after retrying: %v", err)
	}
}

func doSomething() (int64, error) {
	return 6, nil
}