メッセージングとイベント駆動 — 「置いておく」が生む疎結合

サービス同士を直接呼び合う代わりに、メッセージをキューに置いておく。受け手が落ちていても、混んでいても、キューが時間差を吸収してくれる。その代償として現れる配送保証・重複・順序という3つの問題を、動かしながら攻略します。

1. 呼ぶか、置くか — 同期RPCと非同期メッセージング

同期RPCでは、呼び出し側は応答が返るまでその場で待ちます。相手が停止していれば失敗し、相手が遅ければ自分も遅くなる — 故障と混雑が呼び出し元へ伝播する構造です。一方、メッセージキューを挟むと、送り手は「置いたら完了」。受け手が落ちていてもメッセージはキューに溜まり、復旧後に処理が再開されます。

下のデモで受け手を停止させてみてください。同期RPCは失敗の山になりますが、キュー側はメッセージを溜めて待ち、復旧後に淡々と消化します。生産と消費の速度差でキューの長さがどう変わるかも観察してください。

同期RPC vs キュー経由 — 受け手が落ちたとき何が起きるか
上段=同期RPC(受け手停止中は即失敗)、下段=キュー経由(停止中もキューが受け止め、復旧後に再開)。生産速度>消費速度にするとキューが伸び続けることも確認できます。
POINT — キューは「時間の疎結合」を買う装置 キューを挟むと、送り手と受け手が同時に生きている必要がなくなり(時間的疎結合)、瞬間的なアクセス集中もキューがならしてくれる(負荷平準化)。ただし消費が生産に恒常的に追いつかないキューは無限に伸びる。溜まり続けるキューは故障のサインであり、流入を絞るバックプレッシャが必要になる。
L = λ × W リトルの法則: 平均キュー長 L = 到着率 λ × 平均滞在時間 W。キュー長を測れば「メッセージが平均何秒待たされているか」が逆算できる

2. 配送保証 — 「ちょうど1回」は買えるのか

ネットワークはメッセージもACK(受領確認)も落とします。このとき設計者が選べる基本戦略は2つだけです。

下のデモで通信障害を注入して、2つのモードの挙動の違いを見てください。at-least-once では「届いたのにACKだけ消えた」とき、送信側は届いたことを知り得ないので再送するしかなく、受信側に同じメッセージが2度届きます。

at-most-once vs at-least-once — 障害注入でロストと重複を見る
「⚡消失」を押すと、at-most-once ではメッセージ本体が消えてロスト(送信側は気づかない)、at-least-once ではACKが消えて再送タイマーが発火し、受信ログに同じ番号が2回並びます。
注意 — 「exactly-once配送」は通信路だけでは実現できない 「メッセージングシステムが exactly-once を魔法のように保証してくれる」というのは俗説。信頼できないネットワーク上の配送は at-most-once か at-least-once のどちらかしか選べない(合意不可能性と同根の限界)。世に言う「exactly-once」の実体は、at-least-once 配送 + 受信側の冪等処理(または重複排除・トランザクション)を組み合わせて「実効的に1回(effectively once)」の処理結果を作る技法である。

3. 重複とともに生きる — 冪等コンシューマ

ロストが許されない業務では at-least-once 一択。つまり重複は前提です。そこで受信側を、同じメッセージを2回処理しても結果が変わらない — 冪等(idempotent) — に作ります。定番は、各メッセージに一意なIDを振り、受信側が処理済みIDテーブルと照合して重複を捨てる方式です。

冪等コンシューマ — 処理済みIDテーブルが重複入金を防ぐ
同じ「+100円 入金イベント」の流れ(再送による重複入り)を2種類のコンシューマが受信。素朴な実装は重複のたびに残高が水増しされ、冪等な実装はIDテーブルで重複を検知して捨てるので常に正しい残高を保ちます。

4. 順序 — 全体で守るか、キーごとに守るか

スループットを上げるにはキューをパーティションに分割して並列に消費します。しかし並列化すると全体の到着順序は失われます。実務での定石は「全体順序は諦め、同じキーの中だけ順序を守る」こと。メッセージのキー(注文ID・ユーザIDなど)のハッシュでパーティションを決めれば、同じキーのメッセージは必ず同じパーティションに直列に並び、キー内の順序(例: 注文A の「作成→支払→発送」)が保たれます。

partition(key) = hash(key) mod P P = パーティション数。同じ key は常に同じパーティションへ → キー内は順序保証、異なる key 同士は並行処理
パーティションと順序 — 同じ色(同じ注文)は同じレーンに並ぶ
色=キー(注文ID)、数字=そのキー内の連番。どのレーンでも同色の数字は必ず昇順=キー内順序は保証。パーティション数を1にすると全体順序が守られる代わりに並列度が1になり、増やすほど並列に流れます。

5. まとめ — イベント駆動設計の勘所

一歩先へ — ログとしてのブローカとイベントソーシング 近年の主流ブローカは、キューを「消費したら消える箱」ではなく追記専用ログとして実装する。コンシューマは自分のオフセット(読んだ位置)を進めるだけなので、複数の消費者グループが同じ履歴を独立に再生でき、障害時は巻き戻して再処理できる。この発想を突き詰めたのが、状態そのものではなくイベント列を正とするイベントソーシングであり、前章(10章)の Saga も「補償イベントの連鎖」としてこの土台の上に載っている。