⚙️

Go言語でエクスポネンシャルバックオフ処理を実装する

2024/01/24に公開

エクスポネンシャルバックオフ(Exponential Backoff)とは

エクスポネンシャルバックオフとは、何らかの処理が失敗した時に、1回目リトライは1秒後、2回目は2秒後、3回目は4秒後、5回目は8秒後といったように指数関数的にリトライ間隔を伸ばしていくアルゴリズムのことです。

それでは実際のコードを見てみましょう。

実装パターン1

// リトライしたい関数
func doOperation() bool {
	// 0〜2の乱数を生成
	randInt := rand.Intn(3)

	ret := randInt == 0
	if ret {
		fmt.Println("処理成功")
	} else {
		fmt.Println("処理失敗")
	}

	// randIntが0の場合はtrueとする
	return ret
}

// エクスポネンシャルバックオフを使用した再試行ロジック
func retryWithExponentialBackoff(maxRetries int, maxDelay time.Duration) bool {
	// 初回の遅延時間
	delay := 1 * time.Second

	for i := 0; i < maxRetries; i++ {
		if doOperation() {
			return true
		}

		if i < maxRetries-1 {
			fmt.Printf("%d秒後に再試行します...\n", delay/time.Second)
			time.Sleep(delay)
			// 次の遅延時間を計算(最大遅延時間を超えないようにする)
			delay = time.Duration(float64(delay) * 2)
			if delay > maxDelay {
				delay = maxDelay
			}
		}
	}

	fmt.Printf("最大再試行回数に達しました。\n")
	return false
}

func main() {
	rand.Seed(time.Now().UnixNano())

	// 試行回数
	maxRetries := 4
	// 最大遅延時間
	maxDelay := 10 * time.Second

	retryWithExponentialBackoff(maxRetries, maxDelay)
}

実行結果:成功

処理失敗
1秒後に再試行します...
処理失敗
2秒後に再試行します...
処理成功

実行結果:失敗

処理失敗
1秒後に再試行します...
処理失敗
2秒後に再試行します...
処理失敗
4秒後に再試行します...
処理失敗
最大再試行回数に達しました。

リトライさせたい関数の返り値がbool値の場合で実装してみました。
doOperation関数が返すbool値を元に、 retryWithExponentialBackoff関数でリトライ処理をしています。

ただ実際は、リトライさせたい関数の返り値がbool値ではなく、何らかの値かエラーを返す場合が多いと思います。次はその場合で実装してみます。

実装パターン2

// エラーを返す可能性がある操作を表す関数
func doOperation() (*string, error) {
	// 実際の操作をここに実装します。
	// ここでは、単純化のために常にエラーだけを返します。
	return nil, errors.New("操作失敗")
}

// エクスポネンシャルバックオフを使用した再試行ロジック
func retryWithExponentialBackoff(maxRetries int, initialDelay time.Duration) (*string, error) {
	delay := initialDelay

	for i := 0; i < maxRetries; i++ {
		str, err := doOperation()
		if err == nil {
			fmt.Printf("操作成功!\n")
			return str, nil
		}

		if i < maxRetries-1 {
			fmt.Printf("エラー: %v。%d秒後に再試行します...\n", err, delay/time.Second)
			time.Sleep(delay)
			delay *= 2
		} else {
			fmt.Printf("操作失敗。最大再試行回数に達しました。\n")
			return nil, err
		}
	}

	return nil, errors.New("未知のエラー")
}

func main() {
	maxRetries := 4
	initialDelay := 1 * time.Second

	str, err := retryWithExponentialBackoff(maxRetries, initialDelay)
	if err != nil {
		fmt.Printf("最終的なエラー: %v\n", err)
	}
	fmt.Printf("最終的な結果: %s\n", *str)
}

実装パターン1より少し複雑にはなりましたが実装できました。
if err !=nilに見慣れているせいで if err == nilの部分に違和感がありますが、ここで処理の成功判定をしています。

まとめ

今回は自前で実装してみましたが、cenkalti/backoffというリトライ用のライブラリもあるので、次回はそちらを使った記事を書いてみようと思います。

レスキューナウテックブログ

Discussion