

SQS→Lambda、10件中1件失敗で10件全部再処理されてた
この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。
SQSをイベントソースにしたLambdaで、エラーが起きたときの挙動を確認した。
何が起きたか
現状の構成はこんな感じ。
const queue = new sqs.Queue(this, 'ExampleQueue', { visibilityTimeout: Duration.seconds(30), retentionPeriod: Duration.days(7),});events: [ new sources.SqsEventSource(queue, { batchSize: 10, maxBatchingWindow: Duration.seconds(10), enabled: true, }),],DLQなし、reportBatchItemFailuresなし。この状態だと、10件バッチで受け取って9件成功・1件失敗しても、Lambdaが例外で落ちれば10件全部がキューに戻ってやり直しになる。DLQを設定していなければ、SQSの保持期間(最大14日)までこれが繰り返される。
reportBatchItemFailuresを足すとどうなるか
{ "batchItemFailures": [ { "itemIdentifier": "messageId" } ]}Lambdaがこれを返すようにすると、失敗したmessageIdだけが再試行対象になり、成功した9件はその場で削除される。
ここで気になったのが、「reportBatchItemFailures: trueだけ設定して、Lambda側の実装(batchItemFailuresを返す処理)をまだ書いていなかったらどうなるか」という点。これは特に問題なくて、設定を有効にしていてもLambdaがbatchItemFailuresを返さなければ、今まで通り「成功なら全部削除、失敗なら全部再試行」の挙動のまま。Lambda側を実装した時点で部分成功が効くようになる。設定と実装は別々に進めて大丈夫、ということ。
maxReceiveCountの数え方
DLQへのRedrivePolicyでmaxReceiveCount: 5と設定した場合、
ReceiveCount=1 → 失敗ReceiveCount=2 → 失敗...ReceiveCount=6 → DLQへ移動という流れになる。つまり「5回まで試して、6回目の受信でDLQ行き」。初回を1回目としてカウントするので、設定値=リトライ回数ではなく「初回+リトライ回数」がmaxReceiveCountになる。
バリデーションエラーは別扱いにした
DLQは「技術的な失敗(DynamoDB障害、S3障害、タイムアウトなど)」を想定した仕組みで、何回リトライしても直らない「決定的なエラー(スキーマ違反、必須項目不足、型違いなど)」を混ぜるとDLQの中身がノイズだらけになる。
なので、バリデーションエラーはInvalidQueueのような別キューに直接送る設計にした。このキューを人が見るだけ・監視だけの用途なら、InvalidQueue自体にDLQは不要。逆にLambdaがInvalidQueueを読んで再整形・再投入するような処理を挟むなら、そのLambda用に別途DLQを付ける必要がある。
まとめると
- DLQなし+
reportBatchItemFailuresなし → 1件失敗で全件が保持期間いっぱいまでループする reportBatchItemFailuresは設定と実装がセットで初めて効く。設定だけなら挙動は変わらないmaxReceiveCountは「初回含めてN回目でDLQ」というカウント- 技術的失敗はDLQ、決定的なバリデーションエラーは別キューに分ける方が運用しやすい