Mímisbrunnr知恵の泉

← 分散システム 一覧

🎓 レベル:発展 | 重要度:A(必須)

📎 前提:RPCとRMI | 関連:冪等性と再試行・バックオフ分散コンピューティングの誤謬(8つの誤謬)

要点(BLUF)

問題設定 ── 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-onceackまでなしありうるタイムアウト+再送
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になりました。

正しさの観点 ── 安全性と活性の分担

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

「処理されたか」が原理的に分からない(ack喪失と未処理が区別できない)から、安全側に倒すと再送→重複、別の安全側に倒すと諦め→喪失。両立は「効果を冪等にする」=「2回目以降を無害にする」しかない。だから分散の信頼性設計は冪等性が背骨になります(冪等性と再試行・バックオフ)。

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

対応ラボ

distributed-systems-study/labs/03-03_delivery_semantics.py(非冪等で残高140に破綻、冪等化で残高50に収束を確認済み)。

関連

第3章 通信とRPC 目次