0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[AWS SQS] 手動復旧してないのに登録していないデータが戻ってた?! SQS 自動リトライの仕組み🐰🚀

0
Last updated at Posted at 2026-01-23

何が起きたか?!🐰

Amazon Connect Contact LensのログをOpenSearchに転送するETLパイプラインで、Lambda関数にバグが入った。修正してデプロイしたら、エラー期間中のデータが勝手にOpenSearchに登録されていた。手動復旧は一切していない。

答えはSQS + Lambdaの自動リトライの仕組みにあった。

構成

S3(Contact Lensログ)
  ↓ イベント通知
SNS → SQS → Lambda(ETL処理) → OpenSearch

なぜ自動で復旧したのか

SQSとLambdaの組み合わせには、こういう挙動がある。

Lambdaが成功したら → SQS(正確にはEventSourceMapping)がメッセージを自動で消す

Lambdaが失敗したら → メッセージは消えずにキューに残る

残ったメッセージは → VisibilityTimeout(後述)が過ぎると再びLambdaに渡される

つまりこういう流れだった。

  1. Lambda関数にバグがあり、処理が失敗し続ける
  2. メッセージは消えないので、SQSにどんどん溜まっていく
  3. バグを修正してデプロイ
  4. 溜まっていたメッセージが順番にLambdaに渡され、今度は成功する
  5. 結果、エラー期間中のデータもOpenSearchに登録された

メッセージの保持期間(MessageRetentionPeriod)はデフォルト4日間、最大14日間まで設定できる。この期間内に直せばデータは失われない。

VisibilityTimeoutとは

ここが少しややこしい。SQSのメッセージは「取り出したら無くなる」わけじゃない。Lambdaがメッセージを受け取っても、キューから消えるのはあくまで処理が成功した後。

じゃあ処理中に同じメッセージを別のコンシューマーが拾ったら二重処理になる。それを防ぐのがVisibilityTimeoutで、要は「今ウチが処理中だから他には見せないで」というロック期間。メッセージが消えたんじゃなくて、一時的に触れなくしているだけ。

処理が失敗した場合、このロックが切れるとメッセージが再び見える状態に戻り、Lambdaが再度呼び出される。これがリトライの仕組みの正体。

AWSの推奨は Lambda関数のタイムアウト × 6 に設定すること。スロットリング時のリトライ猶予を確保するためらしい。

ContactLensLogsQueue:
  Type: AWS::SQS::Queue
  Properties:
    QueueName: !Sub ${ProjectName}-${Environment}-contact-lens-logs-queue
    VisibilityTimeout: 60  # Lambdaタイムアウト10秒 × 6
    MessageRetentionPeriod: 345600  # 4日間

注意点として、Lambda関数のタイムアウトはVisibilityTimeout以下じゃないとEventSourceMappingの作成・更新時にエラーになる。

バッチ処理の落とし穴

BatchSize: 10にしていると、10件まとめて処理される。デフォルトでは1件でも失敗するとバッチ全体がキューに戻る。つまり成功した9件も再処理される。

これが嫌ならReportBatchItemFailuresを設定する。

LambdaFunctionEventSourceMapping:
  Type: AWS::Lambda::EventSourceMapping
  Properties:
    BatchSize: 10
    EventSourceArn: !GetAtt ContactLensLogsQueue.Arn
    FunctionName: !GetAtt LambdaFunctionForContactLens.Arn
    FunctionResponseTypes:
      - ReportBatchItemFailures

Lambda側では失敗したメッセージIDだけを返すようにする。

def lambda_handler(event, context):
    failures = []
    for record in event['Records']:
        try:
            process_message(record)
        except Exception as e:
            failures.append({"itemIdentifier": record['messageId']})
    return {"batchItemFailures": failures}

DLQ(Dead Letter Queue)も入れておこう

何度リトライしても失敗するメッセージがあると、キューが詰まる。DLQを設定しておけば、指定回数失敗したメッセージを別のキューに逃がせる。

ContactLensLogsQueue:
  Type: AWS::SQS::Queue
  Properties:
    VisibilityTimeout: 60
    MessageRetentionPeriod: 345600
    RedrivePolicy:
      deadLetterTargetArn: !GetAtt ContactLensLogsDLQ.Arn
      maxReceiveCount: 5  # 5回失敗でDLQ行き

ContactLensLogsDLQ:
  Type: AWS::SQS::Queue
  Properties:
    QueueName: !Sub ${ProjectName}-${Environment}-contact-lens-logs-dlq
    MessageRetentionPeriod: 1209600  # 14日間

AWSの推奨はmaxReceiveCount: 5以上。

リトライされるなら冪等性は必須

同じメッセージが複数回処理される可能性があるので、何回処理しても同じ結果になるように作る必要がある。今回のケースではOpenSearchの_idにユニークなキーを指定して、同じドキュメントが重複登録されないようにしていた。

まとめ🐰🚀

SQS + Lambdaの構成では、Lambdaが失敗してもメッセージはキューに残り続ける。バグを直してデプロイすれば、溜まったメッセージは自動で再処理される。保持期間内に直せば手動復旧は不要。

ただし、この仕組みに頼るなら以下は押さえておきたい。

  • VisibilityTimeoutはLambdaタイムアウトの6倍に
  • ReportBatchItemFailuresで無駄な再処理を防ぐ
  • DLQで永久に失敗するメッセージを逃がす
  • 冪等な実装で重複処理に備える

参考ドキュメント

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?