メッセージキュー

メッセージキューとは

メッセージキューは、あるプログラムが送った「処理してほしい内容(メッセージ)」を一旦溜めておき、別のプログラムが順番に取り出して処理する仕組みです。RabbitMQ、Amazon SQS、Apache Kafka などが代表的な実装です。

注文API → [メッセージキュー] → メール送信ワーカー
        → [メッセージキュー] → 在庫更新ワーカー

注文APIは「メールを送って」「在庫を更新して」と待たずにキューへ投げるだけで応答を返せます。

初学者向けポイント

  • 同期処理は「頼んで終わるまで待つ」。非同期処理は「頼んだらすぐ次に進み、結果は後で受け取る・受け取らない」。メッセージキューは後者を実現する代表手段
  • 送信側(Producer)と受信側(Consumer)が直接つながらないのが最大の特徴。片方が落ちていてもキューにメッセージが残り、復旧後に処理を再開できる
  • 重い処理(メール送信、画像変換、レポート生成など)を[[REST API]]の応答から切り離し、ユーザーを待たせないために使われる

キューの中で何が起きているか

登場人物は3つです。メッセージを送るProducer(送信側)、預かるブローカー(キューの本体)、取り出して処理するConsumer(受信側)です。

Consumer は処理を終えたらブローカーへ「処理できた」と伝えます。これを ack(確認応答)と呼びます。ack が返らないまま一定時間が過ぎると、ブローカーはそのメッセージをキューに戻して別のワーカーへ配り直します

Producer →[ブローカー]→ Consumer が取り出す
                        ├ 成功して ack → キューから消える
                        └ ack が無い(落ちた・遅すぎた)→ 再配信

この再配信のおかげで「ワーカーが途中で落ちてもメッセージが消えない」耐障害性が成り立ちます。裏返しとして、ちょうど1回だけ配る保証は普通は無いことになります。処理は終わったのに ack を返す直前に落ちれば、同じメッセージがもう一度配られるからです。これが「少なくとも1回」配信で、受信側を [[冪等性]] のある作りにしなければならない理由です。

配り方は2種類ある

同じ「キュー」でも、配られ方が2種類あります。

配られ方向く用途
ワークキュー1つのメッセージをいずれか1つのワーカーが処理する画像変換・メール送信など、仕事の分担
パブリッシュ/サブスクライブ1つのイベントを購読している全員が受け取る「注文が確定した」を在庫・通知・分析が各々受け取る

RabbitMQ や Amazon SQS は前者の仕事分担が主戦場です。Apache Kafka は後者に近く、読み取っても消さずに一定期間ログとして保持します。受信側は「どこまで読んだか」を自分で持つため、あとから巻き戻して読み直せます。「仕事を配りたいのか、出来事を知らせたいのか」で選ぶと迷いません。

メリットと注意点

観点内容
疎結合送信側と受信側がお互いを知らなくてよい。片方の変更が他方に影響しにくい
耐障害性受信側が一時停止してもメッセージは消えずに残る
スケーラビリティ受信側のワーカーを増やせば処理速度を並列に上げられる
注意点「メッセージが届いたはず」という前提が崩れると調査が難しい。順序保証や重複配信の扱いを設計時に決めておく必要がある

運用でつまずくところ

  • 順序は保証されないことが多い — 標準的なキューは複数ワーカーへ並列に配るため、投入順と処理順は一致しません。順序が要るなら FIFO 対応のキューを選ぶか、「同じ顧客IDは同じワーカーへ」とキーで振り分けます。順序保証はスループットと引き換えです
  • 失敗し続けるメッセージを隔離する — 壊れたデータなど、何度リトライしても必ず失敗するメッセージが居座ると後続が詰まります。一定回数失敗したら別のキューへ退避するデッドレターキュー(DLQ)を用意し、件数を [[監視とアラート]] で見ます
  • キューが伸びていないか見る — 投入速度が処理速度を上回ると待ち行列は際限なく伸び、「非同期だから速い」はずの処理が数時間遅れになります。滞留件数と最古メッセージの待ち時間は [[オブザーバビリティ]] の必須項目で、伸び続けるならワーカーを増やします
  • メッセージに巨大なデータを入れない — 画像や大きなCSVをそのまま載せず、[[Amazon S3]] などに置いてその場所を指す文字列だけを送ります。キュー1件のサイズには上限があります(SQS は256KB)

どこで使われるか

  • 注文確定後のメール送信、通知配信のような「今すぐ返さなくてよい処理」
  • マイクロサービス間の連携(サービスAのイベントをサービスBが購読する)
  • [[データパイプライン・ETL]] における、大量データの段階的な処理の橋渡し

次の一歩を選ぶ地図

メッセージキューは多くの技術の合流点にあります。目的別に読み進めてください。

いつキューへ逃がすかを判断する — [[並行処理と非同期]] が「待たせない」設計の全体像、[[REST API]] が同期で応答を返す側です。外部通知を受ける [[Webhook]] は「即座に200を返し、重い処理はキューへ積む」が定石で、最も分かりやすい出番です。

壊さずに受け取る — 再配信がある以上、受信側の作りが要です。[[冪等性]] で二重処理を防ぎ、[[エラーハンドリングとリトライ設計]] で再試行の回数と間隔を決めます。[[分散トランザクションとSaga]] は「DBは更新できたのにメッセージ送信に失敗した」というずれの防ぎ方を扱います。

他の処理方式と使い分ける — 夜間にまとめて処理するなら [[バッチ処理]]、大量データを段階的に変換するなら [[データパイプライン・ETL]] が本筋です。ごく軽いジョブなら [[Redis]] のリスト機能で足りることもあり、専用のブローカーを立てる前に検討できます。

どこに置いて動かすか — キューは監視と更新の対象になる [[ミドルウェア]] の一種です。ワーカーは [[Kubernetes]] でスケールさせるか、キューへの到着をきっかけに [[サーバーレス・Lambda]] を起動する形が定番です。サービスの分割そのものの設計は [[マイクロサービス]] にあります。

関連技術とのつながり

  • [[並行処理と非同期]] — 重い処理をキューへ逃がすのは非同期設計の定番パターン
  • [[Kubernetes]] — キューのブローカーやワーカーはコンテナとして運用されることが多い
  • [[データパイプライン・ETL]] — データを段階的に処理する構成でメッセージキューが橋渡し役になる
  • [[分散トランザクションとSaga]] — サービスをまたぐ更新をメッセージでつなぐときの整合の取り方
Q: Consumer から ack(確認応答)が返らないまま時間が過ぎたとき、ブローカーが行うことはどれ?
- [ ] そのメッセージを破棄してキューから消す
- [x] そのメッセージをキューに戻し、別のワーカーへ配り直す
- [ ] キュー全体を停止して手動対応を待つ
解説: ack が返らなければ処理されなかった可能性があるため再配信されます。この仕組みが耐障害性を生む一方、同じメッセージが2回処理されうる原因にもなります。

Q: メッセージキューの受信側に冪等性が求められる理由はどれ?
- [ ] メッセージの順序が必ず逆順になるから
- [ ] キューの容量に上限があるから
- [x] 「少なくとも1回」配信のため、同じメッセージが2回配られることがあるから
解説: ack を返す直前に落ちるなどで再配信が起きます。同じメッセージを2回処理しても結果が変わらない作りが必要です。

Q: デッドレターキュー(DLQ)を用意する目的はどれ?
- [x] 何度リトライしても失敗するメッセージを退避し、後続が詰まるのを防ぐ
- [ ] メッセージを暗号化して保存する
- [ ] 処理済みのメッセージを長期保管して分析に使う
解説: 壊れたデータなど必ず失敗するメッセージが先頭に居座ると後続が処理されません。一定回数失敗したら別のキューへ逃がし、その件数を監視します。