APIのリトライは「指数バックオフ+ジッター」で考える

開発をしていると、外部 API がほんの一瞬だけ応答を返さない場面に出くわします。

多くは「もう一度叩けば成功する」たぐいの、ごく一時的なつまずき。

ところが、その「もう一度」を雑に書くと、かえってシステム全体を巻き込む事故につながります。

「リトライって、ただ繰り返せばいいんじゃないの?」と感じたことはありませんか。

この素朴な疑問をほどくほど、頑丈なリトライの設計図が見えてくる。

単純なリトライが、かえって危ない理由

まず、なぜ素朴な再送が危ないのか、その理屈から押さえます。

鍵になるのは 失敗するときは多数のリクエストが同時に失敗する という事実です。

失敗が重なると何が起きるか

サーバーが一時的に弱ったとき、多くのクライアントが同じ瞬間に失敗を受け取ります。

そして全員が申し合わせたように、すぐ再送する。

弱ったサーバーへ回復する間も与えず、波状攻撃を仕掛ける形になってしまうのです。

これこそ 「リトライ嵐」と呼ばれる、自分で自分の首を絞める状態

復旧しかけたサーバーが、集中する再送でまた倒れる悪循環に陥ります。

しかも利用者に見える症状は「たまに遅い」程度で、本当の原因にたどり着きにくい。

だからこそ、再送のしかたそのものを設計対象として捉える必要があります。

指数バックオフという考え方

そこで登場するのが、待ち時間を賢く伸ばす指数バックオフです。

待ち時間を倍々にしていく

仕組み自体は驚くほどシンプル。

1 回目の失敗で 1 秒、2 回目で 2 秒、3 回目で 4 秒と、待ち時間を指数関数的に倍々で伸ばしていきます

再送が重なるほど間隔が開くため、弱ったサーバーに息つく暇が生まれる。

結果として、全員で一斉に叩く状態を時間軸でばらけさせられる のが、この方式の肝です。

ジッターで足並みを崩す

ただし、バックオフだけでは抜け切らない穴が残ります。

全クライアントが「1 秒、2 秒、4 秒」と同じ数列で待つと、結局また同じ瞬間に再送が揃ってしまう。

そこで効くのが、待ち時間に乱数のゆらぎを足す ジッター(jitter) という工夫です。

各クライアントの再送が時間軸でばらけ、山だった負荷をなだらかな裾野へ均せます

擬似コードにすると、勘どころはこの数行に収まる。

# 擬似コード(考え方のみ)
wait = base * (2 ** attempt)          # 倍々に伸ばす
wait = wait + random.uniform(0, wait) # ジッターを足す
sleep(min(wait, cap))                 # 上限で頭打ち
# ... 略

とくに多数のクライアントを抱えるサービスほど、この一手間が安定性を大きく左右する。

派手さはなくても、安定運用への効き目は大きい。

よくある落とし穴

考え方が分かったところで、実装で足をすくわれがちな点も並べておきます。

  • 待ち時間に上限を設けない:失敗が続くと待ちが分単位に膨張し、復旧後の反応が致命的に遅れる
  • 何でも再送する:認証エラーや 400 系など、再送しても結果が変わらないエラーまで繰り返すのは無駄
  • ジッターを省く:足並みが揃ったままで、せっかくのバックオフが台無し

どれも、知ってさえいれば避けられるものばかり。

逆に言えば、上限・対象の見極め・ジッターの 3 点を押さえるだけで、設計はぐっと安定します

一歩進んだ運用のコツ

最後に、もう一歩進めるための視点を 2 つ。

リトライ総量に予算を持たせる

個々のリクエスト単位だけでなく、システム全体での再送回数に上限を設ける考え方があります。

いわゆる リトライバジェット で、暴走を全体最適の観点から止める仕組み。

全体で何回まで再送を許すかを決めておけば、一部の不調が全系へ連鎖する展開を防げる。

「冪等性」とセットで考える

再送して安全かどうかは、その操作が 冪等(何度行っても結果が変わらない性質) かどうかに大きく左右されます。

残高を増やすような操作を無邪気に再送すると、二重実行という別の事故を招く。

結局のところ、リトライ設計の核心は「待ち方の工夫」と「再送してよい操作かの見極め」の両輪にある

より厳密な設計指針はコチラが参考になります。

最後に

リトライは、ともすると「とりあえず繰り返す」で済ませてしまいがちな処理です。

けれど一歩引いて眺めると、相手をいたわりながら粘る、思いやりの設計だと気づきます。

次に小さな再送処理を書くときは、まず「待ち方」と「再送してよいか」を思い出したいですね。

以上です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA