メインコンテンツまでスキップ

第22章:リトライ設計(バックオフ/ジッター/キャンセル)🔁⏳

22.1 リトライって何のため?(CampusCafeで起きる“あるある”)☕📱

CampusCafeは「注文→在庫→決済→通知」で外部や別コンポーネントが絡むので、通信が一瞬コケるのは日常茶飯事です😇📡 そこで “もう一回だけ試す” のがリトライ。でも、やり方を間違えると事故ります💥

  • ✅ 直したいこと:一時的な不調(たまたま遅い/瞬断/混雑)で失敗にしない💪✨
  • ❌ 事故るやつ:失敗した瞬間に 全員が同じタイミングで再突撃 → サーバがさらに死ぬ😵‍💫🔥

22.2 まず最重要:リトライしていい失敗/ダメな失敗 🚥⚠️

リトライは「一時的なら効く」「永久にダメなら無駄」です😊

✅ リトライしやすい(=一時的っぽい)代表

  • サーバ側のエラー(HTTP 500以上
  • タイムアウト系(HTTP 408
  • 混雑で制限(HTTP 429
  • 例外:HttpRequestExceptionTimeoutRejectedException(タイムアウト戦略で投げられる) ※.NETの標準リトライ方針でも、だいたいこの辺を対象にします📌 (Microsoft Learn)

❌ 基本リトライしない(=永久にダメ/仕様エラー)代表

  • 入力ミス(400系の多く:例 400/401/403/404 など)🙅‍♀️
  • “在庫なし”みたいな業務エラー(もう一回やっても増えない)📦❌
  • 二重実行が致命傷になる処理(後述の「POSTの地雷」)💣

22.3 “待ち方”が本体:バックオフ(Backoff)⏳📈

失敗した瞬間に「0秒で3回連打!」は一番ダメです😇 待ち時間を伸ばしながら再試行します。

代表的な形👇

  • 一定(Constant):毎回同じ待ち(例:1秒、1秒、1秒…)🕐
  • 線形(Linear):1秒、2秒、3秒…📏
  • 指数(Exponential):0.2秒、0.4秒、0.8秒、1.6秒…🚀 → 分散/外部API相手は、基本これが使いやすいです✨

22.4 ジッター(Jitter)= “ゆらぎ”で同時突撃を防ぐ 🎲🧯

バックオフだけでも「みんなが同じ待ち→同時に再開」になりがちです😵‍💫 そこで 待ち時間にランダム性(ジッター)を混ぜて、再試行の山(スパイク)を崩すのがコツです✨

AWSも「ジッターでスパイクを散らして、負荷と総呼び出し回数を大きく減らせる」って説明しています📉🫶 (Amazon Web Services, Inc.)

Pollyでも UseJitter = true で「待ち時間にランダム要素」を足せます🎲 特に指数バックオフ+ジッターは内部で Decorrelated Jitter Backoff 系の計算を使う、と明記されています📌 (PollyDocs)


22.5 キャンセル(Cancellation)= “やめたい”をちゃんと止める 🛑🙋‍♀️

ユーザーが「キャンセル」したのに、裏でリトライが走り続けたら怖いですよね😱 そこで CancellationToken を最後まで通すのが大事です✨

  • Web APIなら HttpContext.RequestAborted を下流まで渡すのが基本🎯
  • Pollyのリトライは既定で OperationCanceledException は対象外(=キャンセルは止まる)になっています✅ (PollyDocs) → なので「キャンセルを握りつぶさない」だけで、かなり安全になります😊

22.6 “待つ予算”を決めよう(ユーザー体験の設計)💸⏱️

リトライは無限にやるものじゃなくて、待てる時間の予算で決めます🧾✨

例:CampusCafeの「決済確認」画面(ユーザー待ち)

  • 目安:合計 5〜10秒くらいで決着させたい(長いと不安)😣

  • その中で:

    • 1回あたりの上限(Attempt Timeout)
    • 合計の上限(Total Timeout)
    • リトライ回数(MaxRetryAttempts)
    • 待ち方(Exponential + Jitter) をセットにします🎛️

cap_cs_study_022_exponential_backoff_jitter

.NETの標準ハンドラーは「合計タイムアウト」「リトライ」「試行タイムアウト」などを 順番に重ねて持っていて、既定値も公開されています(例:合計30秒、最大再試行3回、指数+ジッター、遅延2秒…など)📌 (Microsoft Learn)


22.7 最大の地雷:POSTをリトライすると“二重課金”になりうる 💣💳

たとえば決済APIが POST /payments で「課金を作る」タイプだと、 リトライ=同じ課金を2回作る危険があります😇

だから.NET標準でも「危険なHTTPメソッドはリトライ無効にできる」仕組みがあります👇 (Microsoft Learn)

  • DisableFor(HttpMethod.Post, …)
  • DisableForUnsafeHttpMethods()(POST/PUT/PATCH/DELETE/CONNECTなどをまとめてOFF)

じゃあ決済はどうするの? → 第18〜20章でやった 冪等キー(Idempotency-Key) を入れて「同じ要求は1回だけ」になるなら、POSTでもリトライ可能になります🔑🛡️ (※この章では“設計としてそれを前提にする”のがポイント!)


22.8 実装:HttpClientに“標準の回復性”を付ける(まずは簡単に)🧩🧰

① 安全側:まずは危険メソッドのリトライを切る(初心者向け)✂️

using Microsoft.Extensions.Http.Resilience;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient("PaymentClient")
.AddStandardResilienceHandler(options =>
{
// POST等の危険メソッドをまとめてリトライしない(安全側)
options.Retry.DisableForUnsafeHttpMethods();
});

var app = builder.Build();
app.Run();

DisableForUnsafeHttpMethods() の例は公式ドキュメントにも載っています📌 (Microsoft Learn)


22.9 実装:待ち方を“自分で決める”(バックオフ+ジッター+タイムアウト)🎛️✨

「決済確認は最大10秒で諦めたい」みたいに、UXから逆算して数値を入れていきます😊

公式ドキュメントの AddResilienceHandler 例(Retry + CircuitBreaker + Timeout)をベースに、CampusCafe用に調整してみます👇 (Microsoft Learn)

using System.Net;
using Microsoft.Extensions.Http.Resilience;
using Polly;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient("PaymentClient")
.AddResilienceHandler("PaymentPipeline", static pipeline =>
{
// ✅ リトライ:指数バックオフ + ジッター
pipeline.AddRetry(new HttpRetryStrategyOptions
{
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
MaxRetryAttempts = 3,
// Delay なども状況に応じて調整(画面の待てる時間に合わせる)
});

// ✅ 1回の試行の上限(例:1リクエストをだらだら待たない)
pipeline.AddTimeout(TimeSpan.FromSeconds(2));
});

var app = builder.Build();
app.Run();

ポイント💡

  • 指数+ジッターは “同時に再突撃” を避けるための定番です🎲🚀 (Amazon Web Services, Inc.)
  • キャンセルは CancellationToken を渡して握りつぶさなければOK(キャンセルはリトライ対象外が基本)🛑 (PollyDocs)

22.10 ミニ演習:CampusCafeのリトライ方針を“数字で”決めてみよう 🎛️🧪

お題:決済サービスが「たまに遅い/たまに429を返す」😵‍💫

**あなたの方針(例)**を、下の表を埋めて決めてください✍️✨

項目あなたの案
最大再試行回数3回
待ち方Exponential
ジッターON
1回の試行上限2秒
合計の待ち予算8秒
対象エラー500+/408/429

ヒント🎀

  • 429は「混んでるから待ってね」なので、待ちを長めにするのが相性良いです(しかもジッターで散らす)🐢🎲
  • 500+ / 408 / 429 を対象にするのは、標準方針でも採用されています📌 (Microsoft Learn)

22.11 ちょい上級:相手が「Retry-After」を返したら、その秒数だけ待ちたい 🕰️📩

Pollyのリトライは「結果から次の待ち時間を取り出す」パターンも紹介されています✨(DelayGeneratorで調整)(PollyDocs) → 決済/外部APIが Retry-After を返す設計なら、かなり賢くなれます😊


22.12 よくある事故まとめ(これだけ避ければ勝ち)🏆😇

  • ❌ 固定待ちで全員が同時再開(スパイク地獄) → ✅ ジッターON🎲 (Amazon Web Services, Inc.)
  • ❌ POSTを無邪気にリトライして二重処理 → ✅ 危険メソッドはOFF、もしくは冪等キーで守る🔑🛡️ (Microsoft Learn)
  • ❌ キャンセルしても裏で回り続ける → ✅ CancellationTokenを通す🛑 (PollyDocs)
  • ❌ ずっと待たせる(ユーザーが不安) → ✅ 合計の待ち予算を決めて打ち切り⏱️ (Microsoft Learn)

22.13 AI活用(Copilot / Codex向け)🤖✨

  • 「このAPI(決済/在庫/通知)で リトライ対象にしていい失敗と、してはいけない失敗を列挙して。理由もつけて」🧠
  • 「決済確認は最大8秒まで待てる前提で、指数バックオフ+ジッターの待ち時間の例(各試行のタイムライン)を作って」⏳🎲
  • AddResilienceHandler の設定を、POSTは冪等キー前提で安全にする版/安全側でPOSTを無効にする版の2つ書いて」🔑🛡️