メッセージキューを導入するかどうかの判断は、「非同期にすれば速くなる」という直感だけで進めると後悔しやすい選択です。切り離す価値のある処理と切り離すと複雑さだけが増す処理を見極め、順序保証・再送・重複制御をどこまで設計に組み込むかを決めること——これが構築判断の核心です。本記事では、同期処理が行き詰まる構造から出発し、マネージドか自前かの選定判断、非同期化の上限まで、設計判断の軸を整理します。
同期処理のまま抱え込んで行き詰まる構造
リクエスト待機時間がスループットのボトルネックになる仕組み
外部APIへの呼び出しやDBへの書き込みをすべて同期で直列に並べると、1リクエストあたりの応答時間がそのままスループットの上限を規定します。例えば1リクエストの処理に平均500msかかる場合、ワーカー数20のアプリケーションサーバーが捌けるのは毎秒40リクエストが天井です。外部の決済APIの応答が200msから3秒に伸びた瞬間、スループットは15分の1近くに落ちます。ワーカーがすべて外部API待ちでブロックされ、リクエストキューが詰まり、タイムアウトが連鎖するという状況が発生します。
スケールアウトでカバーしようとしてサーバーを増やしても、ワーカー数が増えるだけで根本の構造は変わりません。コストだけが膨らんでいく結果になります。この段階で「同期のまま水平展開しても限界がある」と気づくことが、キュー導入を検討するきっかけになります。
連鎖的な同期呼び出しがシステム全体を止める
複数サービスをA→B→C→Dと同期で連結すると、1か所の遅延が全体に波及します。Cが応答に3秒かかればAのレスポンスも3秒以上かかり、AのワーカーはDの結果が返るまで解放されません。サービス数が増えるほど単一障害点も増加するため、変更が怖くなって機能追加が滞る状況に陥ります。
現場では、この連鎖に気づかないまま機能追加を続け、気づいたときには変更が怖くなっているケースをよく聞きます。本来リアルタイムで処理できる内容が「誰かが落ちているかもしれない」という恐れからバッチ処理に逃がされ、段階的にリアルタイム性が失われていきます。同期連鎖はシステムを「壊れていないが触れない」状態に追い込む典型的なアンチパターンです。
スケールアウトで解決しようとして失敗するパターン
「遅いならサーバーを増やす」という判断で水平スケールアウトしても、同期構造が変わらない限り効果には上限があります。特にDBへの書き込みが集中するケースでは、アプリサーバーを10台に増やしてもDBの書き込みキャパシティを超えれば詰まります。マスタースレーブ構成でリードレプリカを増やしても、書き込みはマスター1台に集中するため、書き込み集中型の処理では恩恵がほとんど得られません。
スケールアウトは「同質な並列化」には効きますが、「直列依存の連鎖」には効きません。この違いを見極めずにインフラコストを積み上げるのが、最もよく見られる失敗パターンです。同期処理のままでは何台並べても行き詰まる構造そのものは変わらない——この認識が、設計を見直す出発点になります。
キューで切り離すべき処理・切り離すと複雑になる処理
キューに向いている処理の特徴
キューで切り離すのが設計として合理的なのは、「呼び出し元がすぐに結果を必要としない処理」と「バーストに対して処理速度を一定に保ちたい処理」です。典型例はメール送信・PDF生成・画像リサイズ・外部システムへの通知・バックグラウンド集計です。これらは応答に数秒から数十秒かかることがあり、ユーザーが待つ意味のない処理です。
「後で処理されてもよいが、必ず処理されなければならない」という特性を持つ処理もキューが適しています。受注データの倉庫システムへの連携・決済後のポイント付与・ログのストレージ転送などが該当します。これらを同期に組み込むと、連携先の一時障害がトランザクション全体を失敗させるリスクを生みます。キューを挟むことで、本体トランザクションと後続処理の生存性を切り離せます。
キューを入れると逆に複雑になる処理
一方、切り離すと設計が複雑になる処理もあります。代表的なのは「処理結果をリアルタイムで呼び出し元に返す必要がある」ケースです。ユーザーの入力バリデーション・在庫確認・認証・APIレスポンスそのものがこれに当たります。これらをキューに乗せると、呼び出し元はレスポンスをポーリングまたはwebhookで受け取る仕組みを別途作る必要があります。
コールバックURLの管理・タイムアウト制御・エラーの返し方など、同期なら自然に解決していた問題が設計課題として浮かび上がります。複数ステップが強い依存関係を持ち順序に意味がある処理は、キューによる並列化がかえって整合性を崩す危険もあります。「Aが終わったらBを、Bが終わったらC」という強依存ワークフローに複数の非同期キューを重ねると、失敗時のリカバリが指数的に複雑になります。
切り離し判断の境界線
切り離すかどうかの境界線は「失敗したとき、呼び出し元に即座にエラーを伝える必要があるか」という問いに集約されます。即座にエラーを伝える必要があるなら同期のまま持つべきです。処理の失敗を後で検知し、リトライや補償処理で対処できるなら、キューへの切り離しが適します。この判断を曖昧にしたまま実装すると、「キューに積んだのに失敗したことをユーザーに伝えられない」という状況が発生します。切り離しの境界を決めることは、障害時の責任範囲を設計することと同義です。
比較表: 同期/非同期キュー/バッチの向き不向き
比較軸の整理
処理方式を選ぶ際に考慮すべき軸は、応答要件・失敗耐性・スループット特性・実装複雑度の4つです。同期処理・非同期キュー・夜間バッチの3方式をこの軸で比べることで、「どの処理をどの方式で持つか」の判断が整理されます。いずれかの方式が優れているのではなく、処理の性質によって向き不向きが分かれます。
処理方式の比較
| 比較軸 | 同期処理 | 非同期キュー | バッチ処理 |
|---|---|---|---|
| 応答速度 | 即時(処理完了まで待機) | 非即時(ポーリング/通知が必要) | 非即時(定時実行) |
| リアルタイム性 | 高い | 準リアルタイム(秒〜分) | 低い(時間〜日次) |
| 失敗時の影響 | 呼び出し元に即座に伝播 | 本体と分離(リトライ設計が必要) | バッチ全体またはジョブ単位 |
| バースト耐性 | 弱い(詰まりやすい) | 強い(バーストを吸収しやすい) | 強い(大量データに向く) |
| 実装複雑度 | 低い | 中〜高(冪等性・再送設計が必要) | 低〜中(ジョブ管理が必要) |
| 向いている処理 | 認証・バリデーション・在庫確認 | メール送信・通知・外部連携 | 集計・レポート・大量データ処理 |
| 向いていない処理 | バースト集中・外部依存が重い処理 | 即時レスポンスが必要な処理 | リアルタイム性が求められる処理 |
方式を混在させるときの落とし穴
3つの方式を混在させることが実際の構成では一般的です。ユーザー操作は同期で受けつつ、後続の外部連携はキュー、集計レポートはバッチ、という組み合わせは合理的です。ただし「どの処理がどの方式で動いているか」の地図を誰も管理していない状態になると、障害発生時にどこを見ればよいか分からなくなります。構築を任されたある担当者が「キューで処理しているはずの注文連携がなぜ失敗しているか」を調査するのに半日かかったという話は珍しくありません。方式の混在を許容しつつも、処理分類の台帳を持つことが後工程の追跡性を左右します。
順序保証・再送・重複をどこまで設計に織り込むか
順序保証の代償を理解する
多くのメッセージキューはデフォルトで「最低1回の配信(at-least-once delivery)」を保証しますが、厳密な順序保証は別のコストを伴います。FIFO型のキューはシャードあたりの処理順序を保証しますが、スループットはスタンダードキューと比べて大きく落ちます。Kafkaはパーティション内の順序を保証しますが、パーティションを跨いだ順序は保証されません。
順序保証が必要な処理を洗い出す際に問うべきは、「全体で厳密な順序が必要なのか、それともエンティティ単位(例:ユーザーIDごと)で順序が保てればよいのか」という点です。エンティティ単位で十分であれば、パーティションキーやシャーディングで設計的に解決できます。全体順序に拘ると不要なスループット低下とコスト増を招くため、要件の粒度を最初に確認することが重要です。
再送設計と冪等性の作り込み
ネットワーク障害やコンシューマーのクラッシュに備えて、再送は設計の前提とする必要があります。コンシューマーが処理に失敗した場合、キューはメッセージを再投入します。このとき同じメッセージを2回処理しても副作用がない「冪等な」処理になっているかが重要です。ポイント付与処理で同じメッセージを2回処理すると、2倍のポイントが付与されるリスクがあります。
冪等性を確保するには、処理済みIDをDBやキャッシュに記録し、受け取ったメッセージのIDが記録済みであれば処理をスキップする実装が一般的です。この設計を最初から組み込まず後追いで追加しようとすると、既存の処理ロジック全体に影響が及ぶため、設計初期に方針を固める必要があります。
重複処理の許容範囲を設計で決める
重複を完全にゼロにする「exactly-once」設計は、コストと複雑度が大幅に上がります。ほとんどのケースでは「冪等な処理+at-least-once配信」の組み合わせで実用上問題ありません。設計段階で「このシステムが許容できる重複処理の範囲はどこまでか」を明示しておくと、実装の複雑度と障害時の対処方針が一貫します。例えば「通知メールは同一メッセージで最大2回まで送信されても許容できる」という合意があれば、冪等性の実装コストを下げる判断ができます。許容範囲を決めずに実装を始めると、後から「これは重複してはいけなかった」という仕様追加が発生し、リファクタリングコストが膨らみます。
マネージドキュー/自前キューの選定判断
マネージドキューが向いているケース
クラウドが提供するマネージドキューは、ブローカーの冗長化・スケーリング・モニタリング基盤が最初から提供されるため、立ち上げ工数は自前構築と比べて3分の1以下になることが多いです。スループットの要件が数千から数万メッセージ/秒の範囲内であり、マルチリージョンや複雑なルーティングが不要なケースでは、マネージドを選ぶのが合理的です。チームのメンバーがMQ運用に習熟していない場合も、運用負荷の観点からマネージドが安全な選択です。
デッドレターキュー・可視性タイムアウト・メッセージ保持期間といった設定を適切に行えば、マネージドキューで本番運用のほとんどのユースケースをカバーできます。機能上の制約が明確になった段階で自前構築への移行を判断する方が、初期の構築複雑度を下げられます。
自前構築を選ぶ理由と代償
オープンソースのMQをVM上にデプロイして自前管理するケースで合理性があるのは、データを外部ネットワークに出せないコンプライアンス要件がある場合、または1秒間に数十万件を超えるスループット要件がある場合です。それ以外で自前構築を選ぶと、ブローカーのパッチ適用・ディスク管理・ノード障害時の復旧手順・モニタリング設定などを継続して維持するコストを負います。
5台以上のブローカークラスターを継続運用する工数は、専任エンジニア換算で少なくとも月0.5人月程度の継続コストがかかります。「マネージドより安く済む」という見込みで始めても、障害対応・バージョンアップ・設定変更の積み重ねで、数か月後には逆転しているケースが少なくありません。選定の際は初期構築コストだけでなく、2年間の継続運用コストを含めて比較することが重要な判断軸になります。
段階的な移行判断と抽象層の設計
初期フェーズではマネージドキューで始め、スループット上限や機能要件の不足が明確になった段階で自前構築への移行を判断する方が、構築初期の複雑度を下げられます。移行を見越してコンシューマー側のインターフェースを薄い抽象層で包んでおくと、ブローカーの差し替えコストを下げられます。ただしこの抽象層も過剰に設計すると独自フレームワーク化してメンテナンス負荷が増すため、「差し替えに必要な最小限の分離」に留めることが重要な判断基準です。手を動かして実装する部分については、公式ドキュメントやOSSのベストプラクティスを参照してください。
非同期化しすぎて追跡不能になる境界
非同期の連鎖が観測性を壊す
処理をキューで切り離すたびに、1つのリクエストの「その後」が見えにくくなります。同期処理ならスタックトレース1本で追えた処理の流れが、非同期の連鎖を経ると「どのコンシューマーがどの順序で処理したか」を追うために複数のログを突き合わせる必要が生じます。キューを3段以上連鎖させたシステムでは、障害時に原因を特定するまでの平均時間が同期構成の3〜5倍に膨らむという事例が報告されています。
観測性の問題は、非同期化した直後よりも半年から1年後に表面化することが多いです。最初は問題なく動いているように見えても、構成が複雑になるにつれてデバッグに費やす時間が増え、ある時点で「どこで何が起きているか誰も分からない」という状況に陥ります。
トレーシングIDと観測設計の組み込み判断
非同期化を進めるなら、リクエスト起点で生成したトレーシングID(相関ID)をメッセージのヘッダーに乗せ、すべてのコンシューマーがそのIDをログに出力する設計を最初から組み込む必要があります。後から追加しようとすると、コンシューマーの実装を全件修正することになります。分散トレーシングを採用すれば、非同期を跨いだ処理のつながりを可視化できますが、エージェントの導入・設定・ストレージのコストが別途発生します。
観測基盤の準備ができていない状態で非同期化を広げることは、「見えない処理」を増やすことと同義です。非同期化を進める前に「ここで何かが失敗したとき、誰がどのログを見て対処するか」を設計として決めておくことが、後の運用コストを大きく左右します。
非同期化の上限をどこに引くか
「非同期にできるなら全部非同期にする」という方向性は、追跡不能な処理の山を作りやすいです。1リクエストの処理の中でキューを跨ぐのは最大2段までとする経験則が、システム複雑度と観測性のバランスとして機能しやすいとされています。3段以上になる場合は、処理のフローを状態として管理できるワークフローエンジンの導入を検討した方が、処理の可視化・制御がしやすくなります。
非同期化の判断基準を「切り離せるか」ではなく「切り離した後も追跡できるか」に置くことで、観測性の崩壊を防げます。追跡できない処理を増やすことは、障害対応コストを増やすことと同義です。この視点を設計判断の前提に置くことで、非同期化の範囲を適切に制限できます。
メッセージキュー導入判断チェックフレーム
導入前に確認すべき問い
導入を判断する前に、まず既存の同期処理がなぜ問題を起こしているかを確認します。「遅い」「詰まる」「落ちる」の原因が本当に同期の直列構造にあるのか、それともクエリの非効率・インフラの過小設計・外部API側の問題なのかを切り分けることが先決です。キューを入れることで解決できる問題と、キューを入れても解決できない問題を分けて考えることで、導入の有無だけでなく「どの処理をキューに乗せるか」の判断が明確になります。問題の原因を特定しないまま非同期化すると、複雑度だけが増して根本の詰まりは解消されないままになります。
導入判断チェックリスト
- 対象の処理について、呼び出し元がすぐに結果を必要としないことを確認したか
- 処理の失敗を後から検知し、リトライや補償処理で対処できる設計になっているか
- コンシューマーの冪等性(同じメッセージを複数回受け取っても副作用がない)を確保できるか
- デッドレターキューの監視とアラートを設計に含めているか
- トレーシングID(相関ID)をメッセージヘッダーに乗せる設計になっているか
- 順序保証が必要な場合、その範囲(全体 vs エンティティ単位)を明確にしたか
- 重複処理の許容範囲をチームで合意し、設計に反映しているか
- マネージドキューと自前構築のどちらを選ぶか、スループット要件とコンプライアンス要件で判断したか
- キューを跨ぐ段数が3段以上になる場合、ワークフローエンジンの採用を検討したか
- コンシューマーの監視・オンコール対応のフローを設計に含めているか
- メッセージスキーマのバージョン管理方針を決めているか
判断を確定させる場面
チェックリストを通過したとしても、最終的な判断は「導入後の運用をチームが継続できるか」という問いに帰着します。マネージドキューを選んでもコンシューマーの監視・デッドレターキューの処理・メッセージスキーマの管理は自前で行う必要があります。特に、コンシューマーが処理を失敗してデッドレターキューにメッセージが積み上がっているのに誰も気づかない、という状況は現場でよく起きます。
アラートとオンコール対応のフローを設計に含めることで、初めてキューは「導入しただけ」から「機能している非同期処理」になります。メッセージキューの導入判断は、キューを入れるかどうかよりも、切り離した処理を誰がどうやって運用し続けるかを設計できているかどうかに本質があります。
▶ 手を動かして本番まで到達する
その設計判断を、RHEL10の本番環境で手を動かして固めませんか
ここまでの設計判断は、読めば腹に落ちます。ですが「自分の手で本番まで通しきる自信」は、それとは別の話になります。
最大6名の少人数で、構築実務者の到達点まで引き上げます。座学で終わらせず、本番相当の環境で判断を検証する2日間です。
Linuxセミナー上級編(RHEL10)|最大6名の少人数ハンズオン