0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

#0189(2025/07/08) Exponential Backoff

0
Last updated at Posted at 2025-07-07

Exponential Backoff で信頼性を高めるリトライ戦略

1️⃣ 背景 ― なぜ指数的後退が必要なのか

  • 瞬間的な混雑や障害が発生したとき、全クライアントが即座に再試行すると "⚡️リトライ嵐" が起こり、システムはさらに不安定になる。
  • 指数的後退 (exponential backoff)待機時間 = base × 2^n で広げ、衝突を自然にばらけさせる戦略。
  • TCP の衝突回避、AWS/GCP SDK、Kafka、分散ロック取得など、実運用で定番。
Time →
Req1: ─┐   ─────┐        ─────────────────┐
Req2: ─┐ ─┐     ─────┐           ─────────┐
          1s  2s      4s              8s

図1: 2 クライアントが指数的に待機→衝突を回避


2️⃣ アルゴリズムの基本形

wait = min(base_delay × (factor ** attempt), max_delay)
wait = wait × (1 ± jitter)
  • base_delay: 初回待機 (例: 0.5–1 s)
  • factor: 2 が一般的。1.5〜3 でチューニング。
  • max_delay: 上限 (ユーザ体験/タイムアウトに合わせて)
  • jitter: 同期衝突を避けるランダムゆらぎ (0–50 % 推奨)

3️⃣ 設計チェックリスト

  • 🔢 試行回数上限: 例: 5–7 回。超えたら失敗を返す or circuit breaker 発火。
  • 全体タイムアウト: API SLA が 30 s なら 合計待機 + 処理時間 を必ず 30 s 未満に。
  • ♻️ 処理の冪等性: リトライしても重複副作用が起きない設計を徹底。
  • 🎲 jitter 方式: full-jitter, equal-jitter, decorrelated-jitter などがある。
  • 📈 メトリクス: 成功率 / 試行回数分布 / 待機総量をモニタリングし過剰リトライを検出。

4️⃣ Python 実装サンプル(デコレータ版)

import random, time, functools

class Backoff:
    def __init__(self, base=1.0, factor=2, jitter=0.5, max_delay=32, max_attempts=5):
        self.base = base; self.factor = factor; self.jitter = jitter
        self.max_delay = max_delay; self.max_attempts = max_attempts

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(self.max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == self.max_attempts - 1:
                        raise  # 最後も失敗
                    sleep = min(self.base * (self.factor ** attempt), self.max_delay)
                    sleep *= random.uniform(1 - self.jitter, 1 + self.jitter)
                    print(f"retry {attempt+1}: wait {sleep:.2f}s ({e})")
                    time.sleep(sleep)
        return wrapper
@Backoff(base=0.5, factor=2, jitter=0.3, max_delay=8, max_attempts=6)
def unreliable_api_call():
    # ダミー: 50% の確率でエラー
    if random.random() < 0.5:
        raise RuntimeError("503 Service Unavailable")
    return "🎉 success"

使う側は通常の関数呼び出しと同じ。リトライロジックが隠蔽され、可読性を保てる。


5️⃣ 実践 Tips & 落とし穴

  • バックオフ vs. 同期リトライ: "3 回だけ即リトライ→指数的後退" のハイブリッドも◎。
  • 並列クライアント数が多い場合は jitter 幅を広げ、thundering herd をさらに緩和。
  • 長大な max_delay は UI をブロックする。フロントエンドは短め + プログレッシブ UI を検討。
  • Backoff だけでは不十分: 恒久的エラーには サーキットブレーカフェイルオーバ を組み合わせる。

6️⃣ まとめ

Exponential Backoff = "待つことで衝突を避ける" シンプルだが強力なリトライ戦略。

  • 基本式 base × 2^n + jitter で同期リトライを分散
  • base / factor / max_delay / jitter / attempts を明示的に設計
  • Python ではデコレータ化すると実装がスッキリ

本稿を参考に、自身の API クライアントや分散処理で 負荷をかけず、かつ信頼性の高い リトライを設計してみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?