ラーメン屋で理解するキューの仕組みとAWSでの実装方法
エンジニアの福田です
弊社のサービスである「Comsbi」では、キューの仕組みを利用してシステムの開発を行うことがあります。
なんか使われてるなぁくらいで永い間過ごしてましたが、冷静にどうしてキューを使うようになったんだろう?そもそもキューを使うメリットって考えたことないなぁとか最近思ってしまいました。
てことで、そんな自分の疑問を解決するためにも、これからキューを使って開発を行おうと思ってる人にも刺さるようにキューの基礎を1から振り返っていけたらいいなと思います。
キューってどういう時に利用する?
現代のシステムでは、ユーザーからのリクエストやデータ処理が増えると、サーバーが対応しきれず、処理が遅れたりタイムアウトしたりすることがあります。
こうした課題を解決する手段のひとつが「キュー」を利用した非同期処理です。
重い処理や時間のかかるタスクを直接実行するのではなく、キューに登録して順番に処理することで、システム全体の安定性や応答速度を高めることができます。
キューをラーメン屋でイメージしてみる
キューをラーメン屋でイメージしてみましょう。
まずはそれぞれの役割について解説して、その後どういう流れでキューが処理されるのかを確認できればと思います!

キューの各要素解説
Producer
Producer は 「データを作って Queue に渡す側」 の役割です。
システムでは以下のようなものが Producer になります。
- ユーザーのリクエストを受け取る Web サーバー
- センサーから情報を送る IoT デバイス
- ログを生成するアプリケーション
Producer の仕事はただ一つ。
新しいデータを Queue に投入する(enqueue する)こと です。
ラーメン屋で例えるとお客さんみたいな感じかなと思ってます。
お客さんがラーメンを食べるという行動のために行列に並び始める感じですね。
Queue
Queue は 「データを一時的にためておく並び順のある領域」 です。
FIFO(First In, First Out:先に入ったものが先に出る)というルールで動きます。
特徴は次の通りです。
- データは「後ろに追加」される
- Consumer は Queue の「先頭から取り出す」
- 生産と消費のスピードが違っても調整役になれる
つまり Queue は、Producer と Consumer をスムーズにつなぐ クッション役 です。
ラーメン屋で例えると行列みたいな感じですね。
ラーメンを食べるまで行列で待つ、先に並んだ人からラーメンを食べられるって感じですね。
Consumer
Consumer は 「Queue からデータを受け取り、処理する側」 です。
システムでは以下のようなものが Consumer に当たります。
- バックグラウンドで画像処理するワーカー
- メール送信サーバー
- 分析用バッチ処理プログラム
Consumer の仕事は明確で、
Queue の先頭からデータを取り出し、処理していく(dequeue) ことです。
ラーメン屋で例えるとラーメン屋って感じですね。
行列に並んでる先頭のお客さんからラーメン屋の中に案内して、ラーメンを提供するみたいな感じ。ラーメンがプログラミングそのものって感じですね。
Queueの流れをラーメン屋で例えると...
① お客さんが来て並ぶ(Producer → Queue)
店に来たお客さんが行列の最後尾に並びます。
これは Queue への enqueue(追加) と同じです。
② ラーメン屋が空いたら先頭のお客さんを呼び入れる(Queue → Consumer)
店内に席が空くと、スタッフは 行列の一番先頭を呼びます。
これが Queue からの dequeue(取り出し) に該当します。
③ ラーメンを提供(Consumer が処理)
店側(Consumer)は呼んだお客さんにラーメンを提供します。
これは Consumer がデータを処理する段階 です。
④ Producer と Consumer が非同期に動いても Queue が調整する
お客さんが急に増えても、行列がそれを吸収してくれる
店の処理が追いつかなくても、行列が「一時保管庫」になる
まさに Queue が 生産と消費のスピードの差を調整する役割 を果たしています。
こんな話をしてると腹が減ってきますね。。。
キューを使うメリット
1. 非同期化による高速化
Webアプリケーションの中には、時間のかかる処理が少なくありません。
例えば、外部APIへのリクエスト、ファイル生成、データ集計などです。これらをリクエスト内で処理しようとすると、ユーザーは処理完了まで待たされ、場合によってはタイムアウトが発生してしまいます。
キューを導入すると、これらの処理をバックグラウンドへ切り離すことができます。アプリケーションは処理をキューに登録した時点で APIレスポンスを返せるため、ユーザーに対して常に高速で応答できます。
ユーザー体験の改善とシステム安定性の両立を実現できる点が、非同期化の大きな強みです。
2. 再実行やリトライが容易
ネットワークや外部サービスを相手にしている以上、処理の失敗は必ず発生します。
キューを利用しない直接実行型の処理だと、再実行ボタンを作成したり、手動で再処理を行ったりなど失敗時のリカバリを自前で考える必要があります。
一方、キューは失敗を前提とした仕組みを備えており、多くのメッセージキューには自動的に再試行を行うリトライ機能に加え、一定回数リトライしても処理が成功しなかったメッセージを通常の処理フローから切り離して専用の保管領域へ送る Dead Letter Queue(DLQ) が用意されています。DLQ に送られたメッセージには、失敗の理由やエラーコード、試行回数などのメタ情報が保持されるため、後から原因分析や手動修正、あるいは専用のワーカーによる再処理が可能になり、結果としてシステムの安定性と障害対応の容易さが大幅に向上します。つまり、アプリケーション側で一から複雑なリトライ機構やエラー隔離ロジックを実装しなくても、キューのアーキテクチャそのものが「失敗に強い」構造を持ち、問題が発生したメッセージを安全に扱えるようになっているのです。
3. 処理の分離とスケーラビリティ
本来、Webサーバーはリクエストを受け取ってレスポンスを返すことが役割です。
しかし、そこに重い処理を埋め込んでしまうと、以下の問題が発生します
- Webサーバーがビジー状態になり、他のリクエストを捌けなくなる
- スケールアウトの際に処理ごと複製する必要がある
- 処理速度が負荷に依存して不安定になる
キューを導入して重い処理をワーカーに移すと、Web処理とバッチ処理を疎結合に分離できます。
さらに、負荷が増えたらワーカー数を横に増やすだけで対応できるようになります。
クラウド構成においては、サーバーの自動スケーリングとも相性が良く、大規模サービスではほぼ必須の構成です。
4. 順序の保証
処理の順番が重要なケースでは、キューが大きな役割を果たします。
例えば:
- 更新 → 通知 → 履歴保存という順序で処理したい
- 同じユーザーに対する処理は順番通りに行う必要がある
このような処理を並列で行うと、順序が乱れてしまうことがあります。
しかし、FIFO(First In First Out)キューや、メッセージグループ機能(AWS SQS FIFOなど)を利用すると、投入された順番通りに安全に処理を実行することが可能です。
もちろん、すべてのキューが順序保証を持っているわけではないので、必要な場合は選定段階で考慮する必要があります。
逆に言えば、順序保証が必要な業務ロジックであっても、正しい選択と設計によって完全に実現できるというのは大きなメリットです。
キューを使う際の注意点
1. 失敗を前提に設計する(Failure-Oriented Design)
キューを利用する際は、ジョブが必ずしも一度で成功するとは限らない前提で設計します。
ネットワークエラー、外部APIの障害、一時的なリソース不足など、さまざまな要因でジョブが失敗する可能性があります。
そのため、適切なリトライ回数の設定や、失敗したジョブを隔離して保存するDLQの設計が重要です。
さらに、同じジョブが再実行されても正しい結果が得られるよう、処理の冪等性(同じ処理を何回実行しても結果が変わらない)を確保する必要があります。
2. 重複実行の可能性を考慮する(Handle Duplicate Execution)
クラウド型のキュー(SQS, Pub/Sub など)では、ジョブを「少なくとも一度(at-least-once)」配送する仕組みが一般的であり、同じメッセージが複数回処理される可能性があります。
これに対応するためには、排他制御(ロック)やデータベースレベルでの一意チェック(unique 制約、ジョブIDの記録)などで多重実行を防ぐ設計が求められます。
3. ジョブはステートレスに保つ(Stateless Job Design)
ジョブ内部で状態を持つと、リトライ時に状態が欠落したり、実行環境によって結果が変わるなどの問題が発生します。
そのため、必要な情報はすべてジョブの引数として渡す、または外部ストレージ(DB・オブジェクトストレージ・キャッシュなど)に保存して参照する形にし、ジョブ自体はステートレスに設計することがベストプラクティスです。
4. モニタリングが必須(Monitoring & Observability)
キューを導入すると非同期処理が増え、失敗が表面化しにくくなります。
そのため、キュー内にメッセージが積み上がっていないか、処理が正常に完了しているか、エラーが多発していないかなどを継続的に監視する仕組みが不可欠です。
メトリクス(処理数、遅延数)、アラート、DLQの確認、ログ分析などを組み合わせることで、安定運用を実現できます。
実際にSQSで実現してみる
非同期処理やジョブキューを構築する際、AWS SQS(Simple Queue Service)は非常に便利なサービスです。
ここからはそんなSQSを利用して、実際にどうやって実装していくのかを整理していきましょう!
0. そもそもどうやってSQSを呼び出す?
呼び出し方を確認
SQSを呼び出す方法をまずは見てみましょう
await client.send(new SendMessageCommand({
QueueUrl: queueUrl,
MessageBody: JSON.stringify({
userId: 123,
action: "create"
})
}));
キューのURLはSQS側で作成した部分に書かれています。
MessageBody に書いたデータがそのまま Lambda のイベントとして送られてきます。
1. Lambda を利用する
特徴
- サーバレスで簡単に SQS と連携できる
- メッセージが届くと自動的に Lambda が起動
- スケーリングは AWS が自動で管理
- 処理失敗時は DLQ に送信可能
- 実行時間は最大 15 分まで
基本フロー
- SQS キューを作成
- Lambda 関数を作成
- Lambda の「トリガー」として SQS を設定
- Lambda 内でメッセージを処理
- 成功時は自動で削除、失敗時は DLQ に送信
メリット
- 管理コストが少なく済む
- スケール・耐障害性は AWS が担当
- 小規模・軽量ジョブに最適
注意点
- 長時間処理には不向き(Lambdaには15 分制限がある)
- 高スループットや大量データ処理は Lambda の同時実行制限に注意
Lambda を SQS に紐づけて実行するイメージですね。
Lambda を SQS に紐づけるだけで、あとはメッセージが届くたびに自動で Lambda が起動します。
アプリ側は SQS にメッセージを送るだけでよく、追加の制御は不要です。
2. ECS を利用する
特徴
- コンテナとして Worker を常駐させる構成
- Lambda より柔軟に長時間・重いジョブを処理可能
- SQS との連携は「Worker 側でポーリング」する必要がある
ポーリングって何?
ポーリングとは、“新しいメッセージが来たかどうかを定期的に自分から取りに行く仕組み” のことです。
SQS は自動で通知してくれる仕組み(Push)は持っていないため、ECS の Worker は
メッセージ来た? → なければ待つ → あれば処理する
という動作をループで続けます。
このように Worker が自分で SQS を監視し続ける仕組みをポーリングと呼び、ECS + SQS の基本動作になります。
基本フロー
- SQS キューを作成
- ECS Fargate タスクを作成(コンテナに Worker スクリプトを配置)
- タスク内でポーリングし、メッセージ受信・処理・削除を行う
- 失敗した場合は削除せず再試行、必要に応じて DLQ に送る
Worker ポーリング例(概念)
while true; do
aws sqs receive-message --queue-url $SQS_URL --wait-time-seconds 20
# 処理
aws sqs delete-message --queue-url $SQS_URL --receipt-handle $HANDLE
done
メリット(ECS Fargate + SQS)
- 長時間・重い処理が可能
- 高スループット処理向き
- コンテナ内で柔軟に環境構築可能
注意点
- ポーリング・冪等性・可視性タイムアウトの設計が必要
- ECS クラスターやタスク管理が必要で運用コストが Lambda より高い
3. Lambda と ECS どっちを利用するのがいいの?
自分が思う、それぞれのメリットが以下です
| サービス | メリット |
|---|---|
| Lambda | 簡単に実装可能、軽量である、運用が簡単 |
| ECS | 柔軟性がある、重い処理も対応可能 |
その上で、条件によって分けるのが望ましいと思います。
Lambda
- 処理時間が短く、頻繁にスケーリングが必要な時
- コストの安さを重視したい
- 運用の簡単さを重視したい
- 小規模な案件
ECS
- 処理時間が長い、リソースが重い、コンテナ化済みアプリ
- 柔軟性と性能を重視
- 複雑な案件
応用編: APIにSQSを導入してみる
ここまでみていくと、APIの処理にキューを導入するのも良いプラクティスなのではないかと思ってしまいます。
実際に双方を比較してみましょう!
API単体で処理する方式
特徴
APIにリクエストが届いたら即座に処理を行い、結果をレスポンスとして返す方式です。例えば、簡単なデータ取得や軽量な計算処理などはこの方式で十分です。
メリット
-
設計がシンプル
APIのリクエストとレスポンスの流れだけで完結するため、コードや構成が分かりやすいです。 -
即時レスポンス
処理が軽量であればユーザー体験も良好です。 -
デバッグやテストが容易
同期処理なので、ログを追うだけで動作確認ができます。
デメリット
-
重い処理には不向き
外部API呼び出しや大規模データ処理があるとレスポンスが遅延し、タイムアウトやユーザー体験の悪化を招きます。 -
スケーリングが難しい
高トラフィック時にはAPIサーバーが処理待ちで詰まりやすく、スケール設計が複雑になります。
APIはリクエスト・レスポンスのみ、処理をSQSで非同期にする方式
特徴
APIは「リクエストを受け取るだけ」にして、実際の処理はSQS経由でWorkerが非同期に行います。ユーザーには即座に受理レスポンスを返すため、UIやUXの遅延を防ぎつつ、バックグラウンドで重い処理を安全に実行できます。
メリット
-
ユーザー待機時間が短い
リクエスト送信後すぐにレスポンスを返すため、API呼び出し側の体感速度が向上します。 -
高トラフィックでもスケーラブル
Workerを必要に応じてスケールさせることで、大量リクエストでも処理を捌けます。 -
重い処理や外部依存の影響をAPIに持ち込まない
たとえ外部サービスが遅延しても、APIレスポンスは即座に返るため、フロントエンドや他サービスへの影響を最小化できます。
デメリット
-
即座に処理結果を返せない場合がある
非同期処理なので、「処理完了までのステータス確認用API」を別途用意することが多いです。 -
Worker設計や監視が必要
冪等性、可視性タイムアウト、DLQ、リトライ回数など、失敗に備えた設計が必須です。
結局はケースバイケース
結論として、API単体で処理するか、SQSなどのキューを使ってWorkerで非同期処理するかは、ケースバイケースで判断するのが望ましいです。
軽量な処理の場合
例えば、DBの簡単な更新やメッセージを送るだけの処理であれば、API単体で同期的に実装する方がシンプルで効率的です。
この場合、ユーザーへのレスポンスも即座に返せるため、UXにも影響が少なく、システム全体の設計も単純になります。
重い処理や複雑な処理の場合
逆に、複数の外部APIを呼び出す処理や、計算やデータ加工など処理が重い場合、または複数のステップが必要な場合は、Workerで非同期に処理する方が適切です。
この方法であれば、APIのレスポンスは迅速に返しつつ、重い処理はバックグラウンドで安全に実行できます。
さらに、SQSを使えばリトライや失敗時の処理(DLQ)も組み込みやすくなり、安定性とスケーラビリティが向上します。
まとめ
今回はさらっとキューの基本からキューを実際に利用してるサービス「AWS SQS」についてまでざっと見ていきました。
API上で全てを行うってのは簡単ではありますが、重い処理の時は悩みどころが多いのも事実。そんな悩みをキューを利用することで解決できるだけでなく、ユーザー体験も向上させることができるってのも魅力的です。
そんなキューも利用しつつ、APIの開発を行えたら今後開発の幅がかなり広がるなと思いました。
Discussion