📄
概念 📚 software-design-concepts

非同期処理(Queue/Event)

メッセージキュー・イベント駆動アーキテクチャの設計原則とAt-least-once配信の扱い方

非同期処理とは、リクエストの送信と処理結果の受信を時間的に分離することで、送信者をブロックせずに処理を継続できる設計パターンである。メッセージキュー(RabbitMQ・Amazon SQS・Google Cloud Pub/Sub)はプロデューサーとコンシューマーを疎結合にし、負荷の平準化・耐障害性の向上・スループットの向上に寄与する。キューを挟むことでコンシューマーが一時的にダウンしてもメッセージを消失させず、復旧後に処理を再開できる。

イベント駆動アーキテクチャ(EDA)はシステム内の状態変化をイベントとして発行し、それをサブスクライバーが非同期に処理するモデルである。コマンド駆動(command-driven)が「〇〇せよ」という指示を特定の宛先に送るのに対し、イベント駆動は「〇〇が起きた」という事実を公開し、誰がどう処理するかを発行者が関知しない。この特性によってサービス間の結合が大幅に緩和されるが、その代わりにシステム全体のフローが分散して把握しにくくなるというトレードオフを持つ。

At-least-once 配信は「少なくとも1回は配信される」という保証であり、ネットワーク障害などによってメッセージが重複配信される可能性がある。コンシューマーはその処理を冪等(Idempotent)に設計することで重複実行の副作用を防ぐ必要がある。デッドレターキュー(DLQ)は処理失敗を繰り返したメッセージを隔離し、人手による調査・再処理を可能にするための重要な設計要素である。バックプレッシャー(Backpressure)はコンシューマーの処理能力を超えるメッセージが流入する場合に、上流の送出レートを制御する仕組みであり、コンシューマーのメモリ枯渇やクラッシュを防ぐ。

コードレビューで着目するポイント

  • コンシューマーの処理が冪等に設計されているか(同一メッセージを複数回処理しても副作用が生じないか)
  • デッドレターキュー(DLQ)が設定されており、失敗したメッセージが可観測性を持って管理されているか
  • メッセージのスキーマ(ペイロード定義)がバージョン管理されているか
  • コンシューマーのスループットを考慮したバックプレッシャー制御が実装されているか
  • イベントの順序保証が必要なユースケースで、パーティション・シャードによる順序制御が設計されているか
  • メッセージの可視性タイムアウト(Visibility Timeout)が処理時間より十分に長く設定されているか
  • イベント駆動フローのトレーサビリティ(correlation ID 等)が確保されているか

典型的なアンチパターン

冪等性のないコンシューマー: At-least-once 配信を前提としているのに、メッセージ処理が冪等でない設計。重複配信時に二重請求や二重登録が発生するリスクを持つ。メッセージIDやコンシューマーサイドの重複チェックで対策する。

DLQ の放置: デッドレターキューが設定されていても監視・アラートが存在せず、失敗したメッセージが蓄積し続けてサイレントに業務データが失われるパターン。DLQ は必ずアラートと再処理ワークフローとセットで設計する。

過度な同期変換: 非同期キューを使いながら、応答を得るために同期的なポーリングでブロックする実装。非同期の恩恵がなくなり、タイムアウトやデッドロックのリスクが生じる。

参考リソース