メッセージングとイベント駆動 — 「置いておく」が生む疎結合
サービス同士を直接呼び合う代わりに、メッセージをキューに置いておく。受け手が落ちていても、混んでいても、キューが時間差を吸収してくれる。その代償として現れる配送保証・重複・順序という3つの問題を、動かしながら攻略します。
1. 呼ぶか、置くか — 同期RPCと非同期メッセージング
同期RPCでは、呼び出し側は応答が返るまでその場で待ちます。相手が停止していれば失敗し、相手が遅ければ自分も遅くなる — 故障と混雑が呼び出し元へ伝播する構造です。一方、メッセージキューを挟むと、送り手は「置いたら完了」。受け手が落ちていてもメッセージはキューに溜まり、復旧後に処理が再開されます。
下のデモで受け手を停止させてみてください。同期RPCは失敗の山になりますが、キュー側はメッセージを溜めて待ち、復旧後に淡々と消化します。生産と消費の速度差でキューの長さがどう変わるかも観察してください。
2. 配送保証 — 「ちょうど1回」は買えるのか
ネットワークはメッセージもACK(受領確認)も落とします。このとき設計者が選べる基本戦略は2つだけです。
- at-most-once(高々1回): 送りっぱなしで再送しない。重複は絶対に起きないが、ロストし得る。
- at-least-once(少なくとも1回): ACKが来るまで再送し続ける。ロストはしないが、ACKが消えると重複が起きる。
下のデモで通信障害を注入して、2つのモードの挙動の違いを見てください。at-least-once では「届いたのにACKだけ消えた」とき、送信側は届いたことを知り得ないので再送するしかなく、受信側に同じメッセージが2度届きます。
3. 重複とともに生きる — 冪等コンシューマ
ロストが許されない業務では at-least-once 一択。つまり重複は前提です。そこで受信側を、同じメッセージを2回処理しても結果が変わらない — 冪等(idempotent) — に作ります。定番は、各メッセージに一意なIDを振り、受信側が処理済みIDテーブルと照合して重複を捨てる方式です。
4. 順序 — 全体で守るか、キーごとに守るか
スループットを上げるにはキューをパーティションに分割して並列に消費します。しかし並列化すると全体の到着順序は失われます。実務での定石は「全体順序は諦め、同じキーの中だけ順序を守る」こと。メッセージのキー(注文ID・ユーザIDなど)のハッシュでパーティションを決めれば、同じキーのメッセージは必ず同じパーティションに直列に並び、キー内の順序(例: 注文A の「作成→支払→発送」)が保たれます。
5. まとめ — イベント駆動設計の勘所
- キューは時間的疎結合と負荷平準化を買う装置。ただし溜まり続けるキューはバックプレッシャで守る。
- 配送保証は at-most-once / at-least-once の二択。「exactly-once」は配送ではなく処理側の冪等性との合わせ技で作る。
- 重複排除は一意ID+処理済みテーブルが定番。冪等な操作(上書き・集合追加)に設計を寄せるのも有効。
- 順序は「同じキー→同じパーティション」でキー単位に守る。全体順序と並列性はトレードオフ。