🎓 レベル:発展 | 重要度:A(必須)
📎 前提:RPCとRMI | 関連:冪等性と再試行・バックオフ・分散コンピューティングの誤謬(8つの誤謬)
要点(BLUF)
- 喪失も重複も起きるネットワークで「何回処理されるか」を約束するのが配送保証。3種:at-most-once(0〜1回)/at-least-once(1回以上)/exactly-once(ちょうど1回)。
- at-most-onceは再送しない=重複ゼロだが喪失しうる。at-least-onceは ack まで再送=喪失しないが重複しうる。
- 配送レベルのexactly-onceは一般に達成困難。実務では「at-least-once配送 + 受信側を冪等化(重複排除)」で実効的にちょうど1回を作る。これをシミュで実証する。
問題設定 ── ackが消えたらどうする
クライアントがリクエストを送り、サーバは処理して ack を返す。だが 分散システムとは・なぜ難しいか の通り、ack が喪失すると、クライアントには「処理されたのに失敗に見える」。ここで再送すると——サーバは二重実行するかもしれません。
sequenceDiagram
participant C as クライアント
participant S as サーバ
C->>S: 振込(req)
Note over S: 実行(残高 -= 1000)
S-->>C: ack (喪失!)
Note over C: 失敗とみなし再送
C->>S: 振込(req) 再送
Note over S: また実行(残高 -= 1000) 二重!
モデル ── 3つの配送意味論
| 意味論 | 再送 | 喪失 | 重複 | 必要なもの |
|---|---|---|---|---|
| at-most-once | しない | ありうる | なし | 何もしない(送りっぱなし) |
| at-least-once | ackまで | なし | ありうる | タイムアウト+再送 |
| exactly-once(実効) | ackまで | なし | 排除 | 再送 + 冪等性/重複排除 |
「ちょうど1回配送」は二将軍問題的に不可能に近い。だが「ちょうど1回処理(の効果)」なら、リクエストIDで重複を排除すれば作れます。
アルゴリズム ── 冪等化(重複排除)
サーバは処理済みリクエストIDを覚え、同じIDが再来したら副作用を起こさず前回の結果だけ返す。
handle(req_id, op):
if req_id in applied: # 既に処理済み
return cached_ack # 副作用なしでackを返す
result = apply(op) # 1回だけ副作用
applied.add(req_id)
return ack
具体例 ── 非冪等は壊れ、冪等は守る(実機)
ラボ 03-03_delivery_semantics.py。ackが確率50%で喪失する環境で、加算(非可換な副作用)を5回意図して実行:
== at-least-once + 非冪等(重複排除なし) ==
意図した加算: 5回 x 10 = 50
実際の副作用回数: 14 / 残高: 140
-> ack喪失による再送が二重実行となり、残高が膨らむ(壊れる)
== at-least-once + 冪等化(リクエストID重複排除) ==
意図した加算: 5回 x 10 = 50
実際の副作用回数: 5 / 残高: 50
-> 再送が来てもID重複は副作用ゼロ。残高はちょうど50 = exactly-once相当
同じ「at-least-once+再送」でも、重複排除が無いと副作用が14回に膨れて残高140(壊れる)。リクエストID重複排除を入れると副作用はちょうど5回、残高50。配送はat-least-onceのまま、効果はexactly-onceになりました。
正しさの観点 ── 安全性と活性の分担
- 安全性(二重実行しない):冪等性/重複排除が担う。同じIDの再送は効果ゼロ。
- 活性(いつか1回は実行される):タイムアウト+再送が担う。ackが来るまで諦めない。
- 2つを別々の機構で担保するのがコツ。再送だけでは安全性が、冪等だけでは活性が欠ける。
なぜ分散だと難しいか(直観)
「処理されたか」が原理的に分からない(ack喪失と未処理が区別できない)から、安全側に倒すと再送→重複、別の安全側に倒すと諦め→喪失。両立は「効果を冪等にする」=「2回目以降を無害にする」しかない。だから分散の信頼性設計は冪等性が背骨になります(冪等性と再試行・バックオフ)。
⚠️ よくある誤解・落とし穴
- 「exactly-once配送のミドルウェアを使えば安心」→ 多くは「at-least-once+ブローカ内重複排除」。自分の副作用(外部DB・課金)の冪等化は別途必要。
- 「リトライ=信頼性」→ 冪等でないリトライは信頼性を下げる(残高140の実例)。
- 「リクエストIDは何でもよい」→ クライアント生成の安定したIDでないと、再送ごとにIDが変わり排除できない。
- 「加算は冪等」→ 加算は冪等でない(2回足すと倍)。冪等なのは「この値にセット」型。だからID重複排除が要る。
対応ラボ
distributed-systems-study/labs/03-03_delivery_semantics.py(非冪等で残高140に破綻、冪等化で残高50に収束を確認済み)。
関連
- 信頼性パターンとして深掘りは 冪等性と再試行・バックオフ
- 再送を生む透過性の罠は RPCとRMI
- 「ネットワークは信頼できない」前提は 分散コンピューティングの誤謬(8つの誤謬)