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 クライアントや分散処理で 負荷をかけず、かつ信頼性の高い リトライを設計してみてください。