Lambdaで利用できるDead Letter Queue(DLQ)とはどんな機能であるか調べてみた

2019.12.23

こんにちは、CX事業本部の若槻です。

本エントリは、AWS LambdaとServerless #2 Advent Calendar 2019の23日目です。

ジョインした案件のLambdaの実装でDead Letter Queue(DLQ)が使われており、このDLQがどのような機能であるかよく理解できていなかったため、動作検証含め調べてみました。

Dead Letter Queueとは

When you invoke a function asynchronously, Lambda sends the event to a queue. A separate process reads events from the queue and runs your function. When the event is added to the queue, Lambda returns a success response without additional information. To invoke a function asynchronously, set the invocation type parameter to Event.

(中略)

The output file (response.json) doesn't contain any information, but is still created when you run this command. If Lambda isn't able to add the event to the queue, the error message appears in the command output.

Lambda manages the function's asynchronous invocation queue and attempts to retry failed events automatically. If the function returns an error, Lambda attempts to run it two more times, with a one-minute wait between the first two attempts, and two minutes between the second and third attempts. Function errors include errors returned by the function's code and errors returned by the function's runtime, such as timeouts.

When all attempts to process an asynchronous invocation fail, Lambda can send the event to an Amazon SQS queue or an Amazon SNS topic. Configure your function with a dead-letter queue to save these events for further processing.

(中略)

To send events to a queue or topic, your function needs additional permissions. Add a policy with the required permissions to your function's execution role. ・Amazon SQS – sqs:SendMessage ・Amazon SNS – sns:Publish

Asynchronous invocation – Lambda retries function errors twice. If the function doesn't have enough capacity to handle all incoming requests, events might wait in the queue for hours or days to be sent to the function. You can configure a dead-letter queue on the function to capture events that weren't successfully processed.

上記のAWSドキュメントの記述を要約すると、

  • 関数を非同期で呼び出すと、Lambdaはイベントをキューに送信する。別のプロセスがキューからイベントを読み取り、関数を実行する。
  • イベントがキューに追加されるとLambdaは呼び出し元に成功応答を返す。
  • Lambdaはキューを管理し、関数の実行が失敗した場合は、1回目の試行の後に1分、2回目の試行の後に2分待機して自動的に関数の実行を再試行する。
  • Lambda関数にDLQを設定することにより、非同期呼び出しが失敗したイベントをキャプチャして、SQS queueまたはSNS topicに送信することができる。
  • DLQを設定するLambda関数の実行ロールには、宛先によってsqs:SendMessageまたはsns:Publishの権限が必要である。

とのこと。つまり、LambdaにDLQを設定することにより、呼び出し元に成功応答しか返さない非同期呼び出しの場合でも、関数実行の失敗をSQS queueやSNS topicに送信してエラー監視をできるようになる!ようです。

動作検証してみた

ここで作成するLambda関数のランタイムは一律でPython 3.7とします。

DLQを設定しない場合

成功するLambdaの場合

以下の実行が成功するLambda関数をsuccess-funcという名前で作成します。

lambda_function.py

import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

成功するLambda関数をAWS CLIで呼び出します。

  • 同期呼び出し(--invocation-type RequestResponse
$ aws lambda invoke \
  --function-name success-func \
  --invocation-type RequestResponse \
  response.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
  • 非同期呼び出し(--invocation-type Event
$ aws lambda invoke \
  --function-name success-func \
  --invocation-type Event \
  response.json
{
    "StatusCode": 202

失敗するLambdaの場合

以下の実行が失敗するLambda関数をfail-funcという名前で作成します。

lambda_function.py

import json

def lambda_handler(event, context):

    # raise error 
    raise ValueError("エラー!")

    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

失敗するLambda関数をAWS CLIで呼び出します。

  • 同期呼び出し(--invocation-type RequestResponse
$ aws lambda invoke \
  --function-name fail-func \
  --invocation-type RequestResponse \
  response.json
{
    "StatusCode": 200,
    "FunctionError": "Unhandled",
    "ExecutedVersion": "$LATEST"
}

ログストリームに関数を1回だけ実行した際のエラーが出力されています。

スクリーンショット 2019-12-22 23.18.43.png

  • 非同期呼び出し(--invocation-type Event
$ aws lambda invoke \
  --function-name fail-func \
  --invocation-type Event \
  response.json
{
    "StatusCode": 202
}

ログストリームに関数を初回、1分後の2回目、2分後の3回目を実行した際のエラーが出力されています。

スクリーンショット 2019-12-22 23.19.42.png

非同期呼び出しの場合は、関数の実行が成功/失敗のいずれの場合も、呼び出されるLambda側のログにはエラーが記録されますが、呼び出し側からすると"StatusCode": 202しか返らずエラー発生が検知できない結果となりました。

DLQを設定する場合

実際にLambda関数fail-funcにDLQを設定して、非同期呼び出しした関数の実行失敗時の動作を見てみます。

  • SNSトピックの作成

Lambda関数fail-funcの実行エラー時のDLQの送信先となるSNSトピックfailed-lambdaを作成します。

$ aws sns create-topic --name failed-lambda
{
    "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:failed-lambda"
}

作成したトピックをsubscribeする設定をします。SMSの場合は認証は不要です。

$ aws sns subscribe --topic-arn arn:aws:sns:ap-northeast-1:123456789012:failed-lambda \
  --protocol SMS \
  --notification-endpoint +818012345678
{
    "SubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:failed-lambda:2f45bdc4-5e14-49a4-abde-2ed84752b2bc"
}
  • Lambda実行ロールに権限追加

Lambda関数fail-funcがSNSトピックfailed-lambdaにPublish可能とするIAMポリシーを作成します。

AllowDeadLetterQueueForLambdaPolicy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      "Resource": "arn:aws:sns:ap-northeast-1:123456789012:failed-lambda"
    }
  ]
}
$ aws iam create-policy --policy-name AllowDeadLetterQueueForLambdaPolicy \
  --policy-document file://AllowDeadLetterQueueForLambdaPolicy.json
{
    "Policy": {
        "PolicyName": "AllowDeadLetterQueueForLambdaPolicy",
        "PolicyId": "AAAAAAAAAAAAAAAAAAAAA",
        "Arn": "arn:aws:iam::123456789012:policy/AllowDeadLetterQueueForLambdaPolicy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2019-12-22T15:50:23Z",
        "UpdateDate": "2019-12-22T15:50:23Z"
    }
}

作成したポリシーを、失敗するLambda関数fail-funcの実行ロール(例:lambda_basic_execution)に追加で適用します。

$ aws iam attach-role-policy \
  --role-name lambda_basic_execution \
  --policy-arn arn:aws:iam::123456789012:policy/AllowDeadLetterQueueForLambdaPolicy
  • Lambda関数のDeadLetterConfigの設定

Lambda関数fail-funcの設定を更新して、先ほど作成したSNSトピックfailed-lambdaをDLQの送信先として指定します。

$ aws lambda update-function-configuration \
  --function-name fail-func \
  --dead-letter-config TargetArn=arn:aws:sns:ap-northeast-1:123456789012:failed-lambda
{
    [...]
    "FunctionName": "fail-func",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:fail-func",
    "Runtime": "python3.7",
    "Role": "arn:aws:iam::123456789012:role/Lambda_Basic_Role",
    "Handler": "lambda_function.lambda_handler",
    "DeadLetterConfig": {
        "TargetArn": "arn:aws:sns:ap-northeast-1:123456789012:failed-lambda"
    },
    [...]
}
  • 非同期呼び出し(--invocation-type Event

失敗するLambda関数をAWS CLIで呼び出します。

$ aws lambda invoke \
  --function-name fail-func \
  --invocation-type Event \
  response.json
{
    "StatusCode": 202
}

ログストリームに関数が3回実行された際のエラーが出力されています。

image.png

subscribeに登録した電話番号には[NOTICE]という送信元から下記のようなSMSが届きました。

デフォルトではSMSはエラー通知内容の情報量がまったくありませんでした。ちなみにEメールをsubscriptionに設定した場合は下記のような内容のエラー通知が届きます。少なくともどの関数でエラーが発生したのかまでは分かります。

{"version":"0","id":"54a6ad92-6f5d-8ab2-2834-30fe98ad41c8","detail-type":"Scheduled Event","source":"aws.events","account":"123456789012","time":"2019-12-22T05:08:02Z","region":"ap-northeast-1","resources":["arn:aws:events:ap-northeast-1:123456789012:rule/fail-func"],"detail":{}}

これでLambda関数の非同期呼び出し時のDLQによる通知の動作確認を行うことができました。

Lambda関数を非同期的に呼び出すサービス

他のサービスで AWS Lambda を使用するによると、以下のようなサービスがLambdaの非同期呼び出しを行うとのことです。これらのサービスからLambdaを呼び出す際にDLQの利用を検討することになるかと思います。

  • Amazon Simple Storage Service
  • Amazon Simple Notification Service
  • Amazon Simple Email Service
  • AWS CloudFormation
  • Amazon CloudWatch Logs
  • Amazon CloudWatch Events
  • AWS CodeCommit
  • AWS Config
  • AWS IoT Events
  • AWS CodePipeline

おわりに

本記事ではDLQの仕様確認と、Lambda関数の非同期呼び出し時のDLQによる通知の動作確認を行いました。DLQの必要性と設定方法を理解することができました。今回はSNSトピックのsubscriptionにSMSやEメールを設定したため通知内容は定形のもののみでしたが、実際にはLambda関数をsubscriptionに設定して通知内容をカスタマイズすることになるかと思います。参考になれば幸いです。

参考