地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

Goで色々なリトライを実装する

  1. 最もシンプルなリトライ
func RetrySimple(fn func() error, retries int) error {
    var err error
    for i := 0; i < retries; i++ {
        if err = fn(); err == nil {
            return nil
        }
    }
    return err
}

指定回数だけリトライするだけのシンプルな仕組み。

デメリットは「すぐに連続でリトライ」してしまうため、外部リソースへの負荷が高まる点。

  1. 一定間隔のリトライ
func RetryWithDelay(fn func() error, retries int, delay time.Duration) error {
    var err error
    for i := 0; i < retries; i++ {
        if err = fn(); err == nil {
            return nil
        }
        time.Sleep(delay)
    }
    return err
}

毎回一定の delay を挟んでリトライ。

簡単ですが、障害が続いている場合には無駄に時間を浪費してしまう可能性があります。

  1. エクスポネンシャルバックオフ

クラウドや分散システムでよく推奨されるパターンです。リトライの間隔を指数関数的に増やしていきます。

func RetryExponentialBackoff(fn func() error, retries int, baseDelay time.Duration) error {
    var err error
    delay := baseDelay
    for i := 0; i < retries; i++ {
        if err = fn(); err == nil {
            return nil
        }
        time.Sleep(delay)
        delay *= 2
    }
    return err
}

最初はすぐに再試行し、失敗が続けば徐々に待ち時間を増やす。

サービス全体の負荷を抑えるのに有効。

  1. ジッターを加える

同時に大量のクライアントがリトライすると、かえって「同時集中アクセス(スパイク)」を招いてしまうことがあります。これを「スロッシング」や「スパイクストーム」と呼びます。 そこでランダムな揺らぎ(ジッター)を加えるのが有効です。

func RetryWithJitter(fn func() error, retries int, baseDelay time.Duration) error {
    var err error
    delay := baseDelay
    for i := 0; i < retries; i++ {
        if err = fn(); err == nil {
            return nil
        }
        jitter := time.Duration(rand.Int63n(int64(delay)))
        time.Sleep(delay + jitter)
        delay *= 2
    }
    return err
}

待ち時間にランダム性を加えることで一斉リトライを避けられる。

  1. コンテキスト対応リトライ

リトライ処理自体をキャンセルできるようにするのも実践的です。

func RetryWithContext(ctx context.Context, fn func() error, retries int, baseDelay time.Duration) error {
    var err error
    delay := baseDelay
    for i := 0; i < retries; i++ {
        if err = fn(); err == nil {
            return nil
        }
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(delay):
        }
        delay *= 2
    }
    return err
}

ユーザーがキャンセル操作をしたら即座に終了。

バックグラウンド処理やAPIサーバー実装で役立ちます。