Lambda + SQS非同期処理基盤のインフラ設計判断ポイント
はじめに
こちらはe-dash advent calendar 2025の7日目の記事です。
以前、SQS + Lambdaを組み合わせた非同期処理基盤を構築しました。
この構成はシンプルに見える一方、実際の運用を考えると、意外と検討すべきポイントが多くあります。
今回の記事では、実際に構築した際にどのような点を考慮し、どのような設計判断をしたのかを外部向けにも共有できる形で整理しました。
同じくLambda + SQSでイベント処理基盤を作ろうとしている方の参考になれば幸いです。
標準キューを使うか、FIFOキューを使うか
| 項目 | 標準キュー | FIFO キュー |
|---|---|---|
| メッセージ順序 | ほぼ順序通りだが保証なし | メッセージグループ単位で 厳密な順序保証 |
| 重複配信 | 最大 1 回以上(少しの重複があり得る) | 重複排除が可能(deduplication ID 使用時) |
| スループット | 非常に高い(ほぼ無制限) | 制限あり・デフォルト:300 TPS・バッチ利用:3,000 TPS |
| レイテンシ | 低い | わずかに高い(順序制御のため) |
| メッセージグループ | なし | 必須(group ID により並列度を制御) |
| 利用料金 | 少し安い | 少し高い |
| ユースケース | 大量処理 / 並列処理向き | 順序保証・重複防止が必要なワークフロー |
FIFOキューは順序保証や重複排除などの便利な機能を持っていますが、その代償として、標準キューに比べて制約が多いという特徴があります。
そのため、要件に合致しない場合は基本的に標準キューを使う方がベターです。
FIFO キューを安易に使わない方がよい主な理由
- スループットが大きく制限される
- メッセージグループ IDによって並列度が制御されてしまう
- 料金がやや高い・構成が複雑になる
そのFIFO、本当に必要ですか?
同一リソースに対する連続更新をされる場合に、順序を保証すべくFIFOを使おうとするケースが多いと思います。
しかし、FIFOを選択するとスループット制限や直列処理による遅延など、多くの制約を受けることになります。
一方で、アプリケーション側で順序の乱れを吸収できる設計を採用すれば、必ずしもFIFOに頼る必要はありません。
例えば、更新リクエストにタイムスタンプやバージョン番号を持たせ、最新のイベントのみを反映し、古いイベントは破棄するという処理を行えば、標準キューでも順序逆転による不整合を防ぎ、正しい最終状態を維持することができます。
このように、アプリケーション側で順序制御できる場合は、よりスケーラブルで高スループットな標準キューを選ぶ方がメリットは大きいといえます。
標準キューを採用する場合に理解しておく前提
標準キューはスケーラブルで扱いやすい一方、
以下の特性があるため、アプリケーション側で吸収できる設計が必要です。
- メッセージ順序が保証されない
ほぼ順序通りですが、順序が逆転する可能性があります。
- 重複メッセージが発生し得る
SQSの仕様として Exactly onceではないため、アプリ側で冪等性を担保する必要があります。
Lambdaに連携する際にキューに設定するパラメータ
可視性タイムアウト
可視性タイムアウトは、処理中のメッセージを他のコンシューマーから一時的に見えなくするための時間です。
Lambdaがメッセージ処理に時間を要している間に、別のLambdaが同じメッセージを再取得してしまう事態を防ぐ役割があります。
この値がLambdaのタイムアウトよりも短いと、処理中にもかかわらずメッセージが再びキューに戻り、同一メッセージが大量に並列処理されるなどの想定外の挙動を引き起こします。
そのため、ある余裕のある数値を設定しましょう。
公式の推奨値は、Lambdaのタイムアウトの6倍以上の数値です。
キューの可視性タイムアウトを関数タイムアウトの少なくとも 6 倍に設定します。これにより、Lambda は、前のバッチの処理中に関数がスロットリングされた場合に再試行するのに十分な時間を確保できます。
AWS Lambda 関数をトリガーするように Amazon SQS キューを設定する より引用
最大受信回数
公式の推奨は、少なくとも5回です。
ソースキューの再処理ポリシーで、
maxReceiveCountを少なくとも 5 に設定することをお勧めします。これにより、Lambda は、失敗したメッセージをデッドレターキューに直接送信する前に再試行を数回行う機会を確保できます。
Amazon SQS イベントソースマッピングの作成と管理 より引用
DLQの設計と必要性
Lambdaがメッセージ処理に失敗した場合、再試行を繰り返しても回復しないケースがあります。
このような永続的に失敗するメッセージを隔離し、通常の処理フローを妨げないようにする仕組みがDLQ(Dead Letter Queue)です。
DLQは特別なサービスとして存在するわけではなく、元のSQSキューとは別に、失敗時に格納する専用のSQSを用意し、それをDLQとして設定するという形で実現します。
DLQに入ったメッセージの運用フロー
以下の運用フローを採用しました。
- CloudWatchアラームでDLQメッセージの監視
- AWS Chatbotと連携し、Slackへ通知
- オペレーターがメッセージを確認し、処理の失敗原因を調査
- 原因を修正
- 手動でDLQから元のSQSへメッセージを再送
この運用は一般的ですが、リアルタイム性が求められるワークロードではDLQ到達までの遅延が課題となるため注意が必要です。
検知をより早く
AWS の推奨設定どおり、
- 可視性タイムアウト:Lambdaのタイムアウトの 6 倍
- 最大受信回数:5 回
とした場合、
Lambda が毎回タイムアウトすると、
可視性タイムアウト × 最大受信回数 = Lambdaのタイムアウトの30倍の時間
を経て、ようやくメッセージが DLQ に送られます。
例:Lambda Timeout 30 秒 → 最長 15 分後に DLQ へ到達
これでは、障害に気付くのが遅れるため、Lambdaの失敗を即時に検知できる監視を併用することで、影響を最小限に抑えやすくなります。
SNSを前段に挟むべきか?
SQSの前にSNSを置くと、複数の下流処理へ同時にイベントを分配するファンアウト構成を簡単に実現できます。
また、処理単位でキューやLambdaを分離できるため、責務の切り分けがしやすく、将来的な拡張にも柔軟に対応できます。
一方、イベントを処理するのが1つのLambdaのみであり、今後の拡張予定がない場合には、SNSを挟むことによるメリットが薄く、構成を複雑にしないためにもSNSは導入しない判断が適切です。
Discussion