背景
SQS(メインキュー+DLQ)とLambdaによるメッセージキュー処理をAWS SAMで構築しました。VisibilityTimeoutの計算根拠と、DLQへの振り分け条件を実装ベースで整理します。
1. テンプレート全体(DLQ→メインキュー→Lambdaの順に定義する理由)
Resources:
BatchDLQ:
Type: AWS::SQS::Queue
Properties:
MessageRetentionPeriod: 86400
BatchQueue:
Type: AWS::SQS::Queue
Properties:
VisibilityTimeout: 180
RedrivePolicy:
deadLetterTargetArn: !GetAtt BatchDLQ.Arn
maxReceiveCount: 3
ProcessFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.lambda_handler
Policies:
- SQSPollerPolicy:
QueueName: !GetAtt BatchQueue.QueueName
Events:
SQSEvent:
Type: SQS
Properties:
Queue: !GetAtt BatchQueue.Arn
BatchSize: 5
BatchQueueがRedrivePolicyでBatchDLQのARNを参照するため、CloudFormationの依存解決の都合上DLQを先に定義する必要があります。
2. VisibilityTimeoutはLambdaタイムアウトの6倍が目安
VisibilityTimeout: 180 # Lambdaタイムアウト(30秒) × 6
この計算式はAWS推奨値です。可視性タイムアウトがLambdaの処理時間より短いと、処理中に同じメッセージが再度キューに出現し、二重処理が発生します。「6倍」という余裕を持たせることで、リトライ処理や一時的な遅延にも耐えられるようにしています。
3. SQSPollerPolicyが付与する実際の権限
Policies:
- SQSPollerPolicy:
QueueName: !GetAtt BatchQueue.QueueName
この2行が実際に付与しているのは以下3つの権限です。
sqs:ReceiveMessage # キューからメッセージを取り出す
sqs:DeleteMessage # 処理完了後にメッセージを削除する
sqs:GetQueueAttributes # キューの属性情報を取得する
コンソールで同じ権限を用意する場合、IAMロールにAWSLambdaSQSQueueExecutionRoleマネージドポリシーを手動アタッチすることになります。
4. DLQへの振り分けをコードでテストする
if body.strip().lower() == "error":
raise ValueError(f"意図的なエラー: messageId={message_id}, body={body}")
このLambdaはerrorという本文を受信すると意図的に例外を投げます。SQSは失敗を検知するとVisibilityTimeout後に再度メッセージをキューに戻し、maxReceiveCount(今回は3回)失敗するとDLQに移動します。
1回目の失敗 → 180秒後に再試行
2回目の失敗 → 180秒後に再試行
3回目の失敗 → DLQへ移動
DLQに移動するまで最大180秒×3回≒9分かかるため、動作確認をすぐにしたい場合はVisibilityTimeoutを一時的に10秒程度に下げて試すと早く結果が見られます。
5. BatchSize: 5によるバッチ処理
for record in event["Records"]:
message_id = record["messageId"]
body = record["body"]
BatchSize: 5を指定すると、送信タイミングによっては最大5件のメッセージがまとめて1回のLambda呼び出しに渡されます。event["Records"]をループする実装にしておけば、1件でも複数件でも同じコードで処理できます。
まとめ
VisibilityTimeoutの計算根拠とSQSPollerPolicyが肩代わりする3つの権限、この2点がSAMのSQS連携で押さえておくべきポイントでした。DLQへの実際の振り分け確認手順は元記事にまとめています。