SQSトリガーで起動するLambdaは非同期呼び出しではないので、DLQの設定はSQSでやろう。LambdaのDLQやDestinationはダメでした。

SQSトリガーで動作するLambdaは、非同期呼び出しではありません。DLQを使いたい場合は、SQSのDLQを使いましょう。
2021.06.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

SNS→SQS→Lambdaの組み合わせを使っている方は多いと思います。 このとき、LambdaがErrorになってもSQSにデータが残り続けるため、任意時間後に自動で再実行してくれます。便利ですね。

しかし、何らかの理由によってDLQを設定するとき、LambdaのDLQやDestinationを設定しても、動作しませんでした。 理由はSQSトリガーで動作するLambdaは非同期呼び出しではないからです。DLQを使いたい場合は、SQSのDLQを使いましょう。

概要図

実際に試してみました。

おすすめの方

  • SQSトリガーで起動するLambdaをAWS SAMで作りたい方
  • SQSにDLQを設定したい方
  • SNS -> SQSの構成を作りたい方

まずは、LambdaのDLQやDestinationが動作しないことを確認する

sam init

sam init \
    --runtime python3.8 \
    --name Lambda-Sqs-Dlq-Test-Sample \
    --app-template hello-world \
    --package-type Zip

SAMテンプレート

下記を作成しています。

  • SNS
  • SQS
  • SNS→SQSのポリシー
  • Lambda

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda-Sqs-Dlq-Test-Sample

Resources:
  # LambdaのDLQ用のSNSトピック
  HelloWorldFunctionDlqTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Protocol: email
          Endpoint: sample@example.com

  SampleTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Protocol: sqs
          Endpoint: !GetAtt SampleQueue.Arn

  SampleQueue:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 120

  QueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref SampleQueue
      PolicyDocument:
        Statement:
          - Action:
              - sqs:SendMessage
            Effect: Allow
            Resource: "*"
            Principal:
              Service: sns.amazonaws.com

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Timeout: 5
      DeadLetterQueue:
        Type: SNS
        TargetArn: !Ref HelloWorldFunctionDlqTopic
      Policies:
        - arn:aws:iam::aws:policy/AmazonSNSFullAccess
      EventInvokeConfig:
        MaximumEventAgeInSeconds: 60
        MaximumRetryAttempts: 1
        DestinationConfig:
          OnFailure:
            Type: SNS
            Destination: !Ref HelloWorldFunctionDlqTopic
      Events:
        SQS:
          Type: SQS
          Properties:
            BatchSize: 5
            Queue: !GetAtt SampleQueue.Arn

  HelloWorldFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}

Lambdaコード

強制的にErrorを発生させています。

app.py

def lambda_handler(event, context):
    raise NotImplementedError()

デプロイ

sam build

sam package \
    --output-template-file packaged.yaml \
    --s3-bucket cm-fujii.genki-deploy

sam deploy \
    --template-file packaged.yaml \
    --stack-name Lambda-Sqs-Dlq-Tests-Sample-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

SNSにメッセージを発行して、DLQの動きを確認する

SNSにメッセージを発行します。

SNSトピックに送信する

しばらく待っても、DLQ用のSNSトピックを経由したメールは届きません。 なお、CloudWatch LogsでLambdaのログを確認すると、VisibilityTimeout:120ごとにLambdaが実行されています。

Lambdaが2分毎に実行されている

SQSは、「処理中のメッセージ」にキューが溜まっています。

SQSに処理中のキューがある

SQSのDLQを設定する

SAMテンプレート

下記を追加します。

  • DLQ用のSQS
  • DLQ用のSQSを試しに購読するLambda(ログを出すだけ)
  • DLQ用のSQSトリガーで起動し、eventをログ出力するLambda

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda-Sqs-Dlq-Test-Sample

Resources:
  # LambdaのDLQ用のSNSトピック
  HelloWorldFunctionDlqTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Protocol: email
          Endpoint: sample@example.com

  SampleTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Protocol: sqs
          Endpoint: !GetAtt SampleQueue.Arn

  SampleQueue:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 120
      RedrivePolicy:
        maxReceiveCount: 2
        deadLetterTargetArn: !GetAtt SampleDeadLatterQueue.Arn

  # DLQ用のSQS
  SampleDeadLatterQueue:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 60

  QueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref SampleQueue
      PolicyDocument:
        Statement:
          - Action:
              - sqs:SendMessage
            Effect: Allow
            Resource: "*"
            Principal:
              Service: sns.amazonaws.com

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Timeout: 5
      DeadLetterQueue:
        Type: SNS
        TargetArn: !Ref HelloWorldFunctionDlqTopic
      Policies:
        - arn:aws:iam::aws:policy/AmazonSNSFullAccess
      EventInvokeConfig:
        MaximumEventAgeInSeconds: 60
        MaximumRetryAttempts: 1
        DestinationConfig:
          OnFailure:
            Type: SNS
            Destination: !Ref HelloWorldFunctionDlqTopic
      Events:
        SQS:
          Type: SQS
          Properties:
            BatchSize: 5
            Queue: !GetAtt SampleQueue.Arn

  HelloWorldFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}

  DumpSnsFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app2.lambda_handler
      Runtime: python3.8
      Timeout: 5
      Events:
        SQS:
          Type: SNS
          Properties:
            Topic: !Ref HelloWorldFunctionDlqTopic

  DumpSnsFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${DumpSnsFunction}

DLQ用のSQSを試しに購読するLambda

eventをログ出力します。

app2.py

def lambda_handler(event, context):
    print(event)

デプロイ

sam build

sam package \
    --output-template-file packaged.yaml \
    --s3-bucket cm-fujii.genki-deploy

sam deploy \
    --template-file packaged.yaml \
    --stack-name Lambda-Sqs-Dlq-Tests-Sample-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

DLQ用のSQSを試しに購読するLambdaのログ

「SNS -> SQS -> Lambda」でLambdaがErrorになり続けていましたが、SQSに設定したDLQ(SQS)にキューが移動します。このDLQ(SQS)をトリガーに動くLambdaのログを見ると、「This is a pen.」を受け取れています。

DLQ用のSQSを受け取ったログ

「SNS -> SQS -> Lambda」にあったSQSのキューも処理されています。

SQSに処理中のキューがない

さいごに

SQSトリガーで動作するLambdaは、非同期呼び出しではありません。DLQを使いたい場合は、SQSのDLQを使いましょう。

これに気づくまで半日以上かかりました……。どなたかの参考になれば幸いです。

参考