Mímisbrunnr知恵の泉

← 分散システム 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:配送保証(at-most-once・at-least-once・exactly-once) | 関連:分散トランザクションとSaga分散コンピューティングの誤謬(8つの誤謬)

要点(BLUF)

問題設定 ── 再試行は諸刃の剣

ネットワークは喪失する(分散コンピューティングの誤謬(8つの誤謬))ので、再試行は必須。だが冪等でない再試行は状態を壊し(二重課金)、無秩序な再試行は過負荷を増幅する(落ちかけたサーバに殺到してとどめを刺す)。「安全に・優しく」再試行する設計が要ります。

仕組み ── 4つの信頼性パターン

1. 冪等性キー(重複排除)

クライアントが安定したリクエストIDを付け、サーバは処理済みIDを記憶して2回目以降は副作用なしで前回結果を返す。これで at-least-once 配送でも効果は exactly-once(配送保証(at-most-once・at-least-once・exactly-once) で残高140→50を実証)。

2. 指数バックオフ

失敗のたびに待機時間を倍々に延ばす:

waitk=base×2k\text{wait}_k = \text{base} \times 2^{k}

即時連打を避け、相手の回復時間を与える。ただし**上限(cap)**を設けて無限に延びないようにする。

3. ジッタ(最重要)

バックオフだけだと「全員が同じ k で同じ時刻に再試行」して再殺到する。フルジッタは待機を uniform(0, wait_k) にrandomizeして時間軸に散らす。

4. サーキットブレーカ

連続失敗が閾値を超えたら回路を開いて(open)一定時間リクエストを即座に失敗させ、相手を休ませる。試験的に半開(half-open)で様子見し、回復したら閉じる(closed)

flowchart LR
    C["closed(通常)"] -->|"連続失敗が閾値超え"| O["open(即失敗・相手を休ませる)"]
    O -->|"クールダウン経過"| H["half-open(試験送信)"]
    H -->|"成功"| C
    H -->|"失敗"| O

具体例 ── ジッタでピークを下げる(実機)

ラボ 07-02_backoff_jitter.py。共有障害から復旧した瞬間、待機窓 W=8 秒のラウンドにいる1000クライアントが再試行:

== ジッタなし(全員 t=W に集中) ==
  使用スロット数: 1
  ピーク同時再試行数: 1000 (負荷率 100.0%)

== フルジッタ([0,W) に分散) ==
  使用スロット数: 8
  ピーク同時再試行数: 140 (負荷率 14.0%)

ピーク低減率: 7.1 倍 (ジッタなし 1000 -> フルジッタ 140)

ジッタなしだと全員が同じ瞬間(窓の末尾)に再試行しピーク1000=全員集中。フルジッタは待機窓 [0,8) に均し、ピークは**140(14%)**へ——約 **7倍(≒W倍)**の低減。これがサーバ復旧直後の再殺到(サンダリングハード)を防ぎます。

正しさの観点 ── 安全性と活性、そして相手への配慮

なぜ分散だと難しいか(直観)

再試行は「自分の成功確率」だけ見ると常に正義だが、全員が同じ判断をすると集団で相手を潰す(合成の誤謬)。分散の信頼性は、個々の最適でなく群れとしての振る舞いを設計すること。ジッタもサーキットブレーカも「自分が引くことで全体が回る」という協調の発想です。

⚠️ よくある誤解・落とし穴

対応ラボ

distributed-systems-study/labs/07-02_backoff_jitter.py(フルジッタが再試行ピークを1000→140に約7倍低減することを確認済み)。重複排除の実証は 配送保証(at-most-once・at-least-once・exactly-once) のラボ。

関連

第7章 分散トランザクションと信頼性 目次