第19章:冪等性(考え方編)🧷🧠
この章のゴール 🎯✨
- 「冪等性(べきとうせい)」が何かを、**“メッセージが2回届く世界”**の前提で説明できるようになる 😺📩
- **Idempotency Key(冪等キー)**の考え方をつかむ 🪪🔑
- 「送り手」よりもまず “受け手が守る” のが基本だと腹落ちする 🤝🛡️
1) なんで冪等性が必要なの?🤔📬
Outbox の配送は、だいたい At-least-once(最低1回は届く) の発想だよね📩🔁 この世界では「届かない」より安全にする代わりに、“同じメッセージが2回以上届くことがある” のが自然な挙動になるよ〜😅
たとえば…👇
- 受け手が処理した直後に通信が切れて「受け取ったよ(ACK)」が返せない
- 送り手側(Relay)が「届いたか不安…」で再送する
- ネットワーク遅延やタイムアウトで再試行が走る
結果:同じ内容が2回届くことがある! 📩📩 だから 「2回届いても、1回分として扱える」 ように作る必要があるんだ〜🧠✨ (この考え方は、たとえば Amazon Web Services の標準キューでも「重複があり得るから冪等にしてね」と明確に書かれてるよ)(AWSドキュメント)
2) 冪等性ってなに?🧷📘(超やさしく)

冪等性=同じお願いを何回されても、最終結果が変わらないこと 🔁➡️🟰
✅ 冪等な例(何回やっても結果が同じ)🙂
- 「注文ステータスを “発送済み” にする」 → もう発送済みなら、そのまま発送済み(変わらない)🚚✅
- 「ユーザーのプロフィール画像URLをこの値にする」 → 同じURLを何回セットしても同じ🖼️
❌ 冪等じゃない例(回数で結果が変わる)😱
- 「ポイントを +10 する」 → 2回届いたら +20 になっちゃう💥
- 「在庫を -1 する」 → 2回届いたら -2 で在庫がズレる🌀
ポイントはこれ👇 “同じ入力が繰り返される” を前提に、状態を壊さない設計にする 🛡️✨
3) 「HTTPの冪等性」と似てる話 🌐🧠
Web API の世界でも、冪等性は超重要だよ〜📡✨ 一般に PUT は冪等(同じ内容を何回送っても同じ状態に落ち着く)で、POST は冪等じゃない(作成が2回走るかも)って扱いが多いんだ📮 Microsoft の API設計ガイドでも「PUTは冪等、POST/PATCHは保証されない」って整理されてるよ。(Microsoft Learn)
そして最近は、POST みたいな“本来は冪等じゃない操作”を安全にするために、Idempotency-Key というヘッダーの標準化(ドラフト)も進んでるよ📌 IETF のドラフトでも「POST/PATCHを再試行可能にする」目的がはっきり書かれてるよ。(datatracker.ietf.org)
4) Outbox での冪等性:どこで守る?🤝🛡️
結論から言うね👇 基本は “受け手(コンシューマ)” が守る ✅
なぜかというと…
- 送り手側で「絶対1回だけ送る」をやろうとすると難しい(通信は信用できない)😵💫
- キューやブローカー側の機能で重複が減っても「ゼロ」とは限らないことがある😅
- だから最後の砦として、**受け手が「2回来ても平気」**にするのが堅い🧱✨
5) Idempotency Key(冪等キー)って何?🪪🔑
冪等性を守るために、メッセージに “この処理はこれだよ” って分かる 一意のID を付けるのが定番だよ📌
Outbox だとだいたいこうなる👇
- Outbox テーブルの Id(OutboxId)をそのまま使う ✅
- それをメッセージのヘッダーや本文に入れて送る📩
この「同じキーの再送は同じ結果を返す」って考え方は、たとえば Stripe の API でも超有名で、同じ Idempotency Key の再試行に対して“同じ結果”を返す仕組みを明確に説明してるよ。(Stripe ドキュメント)
6) 受け手の基本戦略(考え方)📥✅
受け手側はこう考えると分かりやすいよ👇
✅ 受け手のやること(超ざっくり)
- メッセージから MessageId(= OutboxId など)を取り出す🪪
- 「この MessageId、もう処理した?」を確認する👀
- もし処理済みなら、何もせず成功扱いで終わる(これが冪等!)✅
- 未処理なら、業務処理をして、最後に「処理済み」を記録する📝
このときの超重要ポイントはこれ👇 「業務処理」と「処理済み記録」を “同じトランザクション” に入れる 🔒 (ここを分けると、タイミング次第でまた地獄になる…😇)
※実装は次章(第20章)でガッツリやるよ〜🔥
7) 冪等にしやすい操作・しにくい操作 🍀⚠️
冪等にするコツは、“増やす/減らす” より “状態をセットする” を優先することが多いよ✨
👍 冪等に寄せやすい例
- Status を “Paid” にする / “Shipped” にする 🧾✅
- 「このIDのレコードが無ければ作る。あれば同じ内容で更新する(Upsert)」🧩
👿 冪等にしにくい例(対策が必要)
- 残高に +100 / 在庫を -1 / ポイント加算 🎲💥 → “何回目か”で結果が変わるから、重複を検知して止める仕組みがほぼ必須になる
8) よくある落とし穴(考え方だけ先に潰す)🕳️😱
落とし穴A:確認してから処理、の間にすり抜ける👯
「処理済みじゃないね!」→処理→「処理済み登録」 この間に別スレッドでも同じことが起きると二重処理するかも💥 👉 対策は次章で:DBの一意制約やトランザクションで固める🧱🔒
落とし穴B:冪等キーの粒度がズレる🎯
- 「メッセージ1回」に対してキーが1つ、が基本
- 「ユーザーID」みたいな大雑把なキーにすると、別の正しい処理まで弾いちゃうことがある🙅♀️ 👉 まずは OutboxId みたいな メッセージ固有ID が安全✅
落とし穴C:処理済み記録を永遠に残してしまう🗄️
処理済みテーブル(Inbox的なやつ)が増え続けるとつらい🥺 👉 “保持期間” を決めて掃除するのが普通(掃除設計は第22章で触れるよ🧹)
9) ミニ確認クイズ(理解チェック)🧠✨
Q1:同じ「ポイント+10」メッセージが2回届いたら?📩📩
- 冪等じゃないままだと… 👉 +20 になる可能性 😱
- 冪等にするには… 👉 「同じ MessageId は1回だけ通す」 の仕組みがいる✅
Q2:冪等性は送り手だけ頑張ればOK?💪
- 👉 基本はNG(通信が信用できないから)🙅♀️
- 👉 受け手が最後の砦 🛡️✨
10) この章のまとめ 🎀📌
- Outbox の世界は “2回届くかも” が前提 📩🔁
- 冪等性は **「同じ処理が繰り返されても最終結果を壊さない」**こと🧷
- 定番は Idempotency Key(OutboxId など) を使って、受け手で重複を潰す🪪✅
- 実装は次章で:処理済みテーブル(Inbox的なもの) や 一意制約 を使ってガチガチにするよ🧱🔒