この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
非同期呼び出しのLambdaは、処理失敗時に他のAWSサービスを実行する方法として、DLQ(デッドレターキュー)と(失敗時の)Destinations(非同期呼び出しの宛先指定)があります。DLQは2016年12月にリリースされた機能、かたやDestinationsは2019年11月にリリースされた比較的新しい機能です。
この2つの機能の違い、またはどう使い分けるべきか調べてみました。実際に両機能を使ってみて、得られる情報の違いを比較します。
実装&テスト
Serverless Frameworkで実装します。前回のエントリで失敗時のDestinationsについては実装済なので、そこにDLQも追加し、関数実行してみます。
ハイライト部分がDLQとして追加した箇所です。
serverless.yml
service:
name: destinations-sample
custom:
webpack:
webpackConfig: ./webpack.config.js
includeModules: true
fail-queue-name: fail-destination-queue-by-sls
plugins:
- serverless-webpack
- serverless-pseudo-parameters
provider:
name: aws
runtime: nodejs12.x
region: ap-northeast-1
environment:
AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1
iamRoleStatements:
- Effect: Allow
Action: sqs:SendMessage
Resource: !GetAtt DLQ.Arn
functions:
hello:
handler: handler.hello
destinations:
onFailure: arn:aws:sqs:#{AWS::Region}:#{AWS::AccountId}:${self:custom.fail-queue-name}
onError: !GetAtt DLQ.Arn
resources:
Resources:
FailQueue:
Type: "AWS::SQS::Queue"
Properties:
QueueName: ${self:custom.fail-queue-name}
MessageRetentionPeriod: 1209600
DLQ:
Type: "AWS::SQS::Queue"
Properties:
QueueName: dlq-by-sls
MessageRetentionPeriod: 1209600
Lambda関数は前回と同じです。イベントペイロードに{ "success": false }
を渡すとエラーを発生させるコードです。
handler.ts
import "source-map-support/register";
export const hello = async (event) => {
console.log(event);
if (event.success === false) {
throw new Error("destination test");
}
return true;
};
関数実行します。{ "success": false }
を渡したので、再試行含め計3回実行した後、DLQに指定したdlq-by-slsキューとDestinationsに指定したfail-destination-queue-by-slsキューにそれぞれメッセージが送信されているはずです。
npx sls invoke -f hello -d '{ "success": false }' -t Event
メッセージ確認
DLQ
$ aws sqs receive-message \
> --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/dlq-by-sls \
> --attribute-names All \
> --message-attribute-names All \
> --max-number-of-messages 10
{
"Messages": [
{
"MessageId": "14fe15eb-c097-4c59-82ae-b3812cfce169",
"ReceiptHandle": "AQEBguEFsZi3HdPLKx1Wb0Ya946j6z3EhQsaDlwugvV6qZTn3MA6Xe+dFsRoiu/77C6iGhMDjfKDnTwKIPWXN5++lQm1XNv9/4qK9Tg/A7G62KgJF0HebToO0aP/kh1S8HF2FSv9r8c24OJsWkHlZnodf3YFQAY7lcq13+4F1y7iTgZtM08DhkWoHlNzsskfKChmjVgu8EkXO85TfOJd/1SjWPub/TSWlr819M2eGP5SWM4Do2abTNdC1sAUUKVnt3K58HqJFz5vv21a7OKs88SIbRBJoe10M8YckKumoKiTEN9v19UnLPW1ALr33RMvwwh4RuvHz8VVlxmdeW/tWXLIKWhJThMV5819BqhQAa0oJGoMe7QlyLQKAaKayKYIgT81ACE/yrKQrhls73HbD6Z5Dg==",
"MD5OfBody": "c0a4229f65148628b26e451304ddac68",
"Body": "{\"success\":false}",
"Attributes": {
"SenderId": "AROAQWCYJEBRJM6ORNHK5:awslambda_800_20200628065429546",
"ApproximateFirstReceiveTimestamp": "1593327635445",
"ApproximateReceiveCount": "1",
"SentTimestamp": "1593327269630"
},
"MD5OfMessageAttributes": "7df843cefe0509f157ec71d0877ad60d",
"MessageAttributes": {
"ErrorCode": {
"StringValue": "200",
"DataType": "Number"
},
"ErrorMessage": {
"StringValue": "destination test",
"DataType": "String"
},
"RequestID": {
"StringValue": "b1084703-4304-4a53-ad7e-c1fa189ef921",
"DataType": "String"
}
}
}
]
}
Destinations
$ aws sqs receive-message \
> --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/fail-destination-queue-by-sls \
> --attribute-names All \
> --message-attribute-names All \
> --max-number-of-messages 10 \
{
"Messages": [
{
"MessageId": "24f74995-8de9-43bc-a832-46814d58e6d6",
"ReceiptHandle": "AQEBFxDcx7eHBBzT+60qDt7jdAAyMTtM56agiXbi1i5ABvXJw3qG8/2We5Io5ev8586Lc0T1hkZ/PA1s1loz6+rNCGXjZ5EVD4YlaYnNSxKqskNI3p6XM86lXPn8RyeXL5YVDNo6NCiMaUq0BmifxUjnz1Py6oyR3cbd/USYuKwztBS6tBO7GJd/qZjSA4bhE8bP/zkPK7xhxxcG8mZ5Jodiq3xamGlCTlLLUGSqLarakJ0P6S/1L06aii5N7nZj4unmAilangnJF/9/tNdNcUDxIO9Jg5Jd6eW0EZn63h1fIlTdFOykDrJb3Q0W812NY0M2tMDfr+nvBKxHYc5Fm/10QzO53EYPFD4x0UlrOoN2WS9O7j6tBT3EfVB+uJruneOkMGwKlbFvoXEWLy3vgfqXq3tYDIoRC/kfmhbWPItnbDg=",
"MD5OfBody": "70243e28b6f79f34491efd1abeba7752",
"Body": "{\"version\":\"1.0\",\"timestamp\":\"2020-06-28T06:54:29.645Z\",\"requestContext\":{\"requestId\":\"b1084703-4304-4a53-ad7e-c1fa189ef921\",\"functionArn\":\"arn:aws:lambda:ap-northeast-1:123456789012:function:destinations-sample-dev-hello:$LATEST\",\"condition\":\"RetriesExhausted\",\"approximateInvokeCount\":3},\"requestPayload\":{\"success\":false},\"responseContext\":{\"statusCode\":200,\"executedVersion\":\"$LATEST\",\"functionError\":\"Unhandled\"},\"responsePayload\":{\"errorType\":\"Error\",\"errorMessage\":\"destination test\",\"trace\":[\"Error: destination test\",\" at Runtime.n [as handler] (/var/task/webpack:/handler.ts:6:15)\",\" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)\"]}}",
"Attributes": {
"SenderId": "AROAQWCYJEBRJM6ORNHK5:awslambda_800_20200628065429546",
"ApproximateFirstReceiveTimestamp": "1593327594714",
"ApproximateReceiveCount": "2",
"SentTimestamp": "1593327269660"
}
}
]
}
Body部をパースしてみます。
$ aws sqs receive-message \
> --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/fail-destination-queue-by-sls \
> --attribute-names All \
> --message-attribute-names All \
> --max-number-of-messages 10 \
> | jq -r .Messages[0].Body \
> | jq
{
"version": "1.0",
"timestamp": "2020-06-28T06:54:29.645Z",
"requestContext": {
"requestId": "b1084703-4304-4a53-ad7e-c1fa189ef921",
"functionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:destinations-sample-dev-hello:$LATEST",
"condition": "RetriesExhausted",
"approximateInvokeCount": 3
},
"requestPayload": {
"success": false
},
"responseContext": {
"statusCode": 200,
"executedVersion": "$LATEST",
"functionError": "Unhandled"
},
"responsePayload": {
"errorType": "Error",
"errorMessage": "destination test",
"trace": [
"Error: destination test",
" at Runtime.n [as handler] (/var/task/webpack:/handler.ts:6:15)",
" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
]
}
}
比較
Destinationsの方が情報量が多い
DLQになくてDestinationsにある情報は以下です。
- errorタイプ
- Trace情報
- requestContext
- Lambda関数のARN
- condition - なぜ失敗判定されたか、ということかと思われます。今回の場合
RetriesExhausted
なので、規定のリトライ回数再試行したけど処理成功しなかったから、ということです。 - approximateInvokeCount - 処理試行回数
- responseContext
- ステータスコード
- Lambda関数のバージョン
- functionError
逆にDLQにある情報はすべてDestinationsに含まれています。
DLQはMessageAttributesにも情報がある
DLQはMessageAttributes(メッセージ属性)にも情報があります。というかBodyにはイベントペイロードしかないという潔さです。 Body内部ではなくMessageAttributesに情報があることで、若干目的の情報にたどり着くのが簡単になるでしょう。とくに以下のようにコンソールで確認する際は見やすいです。
リトライ処理はDLQの方が少しだけ簡単?
前述のとおりBodyにはイベントペイロードしか記載されていないので、それを使ってのLambda関数再実行はDLQの方が(やや)簡単に実装できるかと思います。以下例です。
$ npx sls invoke -f hello -d \
> "$(aws sqs receive-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/dlq-by-sls --max-number-of-messages 1\
> | jq -r .Messages[0].Body)" \
> -t Event
とはいえDestinationsもjqをもう一度かませばよいだけなので、これは本当に微々たる差ですね。。
$ npx sls invoke -f hello -d \
> "$(aws sqs receive-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/dlq-by-sls --max-number-of-messages 1\
> | jq -r .Messages[0].Body)\
> | jq -r .requestPayload)" \
> -t Event
と思ったのですが、Destinationsの方が簡単かもと思う部分も見つけました。
DLQのメッセージにはエラーが起きたLambda関数についての情報がありませんでした。ですので、どのLambda関数から送られてきたメッセージか、という部分はユーザー側で管理する必要がありそうです。大抵の場合SQSキュー名とLambda関数名を関連付けてわかりやすくされていると思いますが、そうしていない場合は辛そうです。
その他の違い
Destinationsの方が配信先のサービスが多い
DLQはSNSトピックと、SQSキューに対して配信可能です。一方のDestinationsはSNSトピックとSQSキューに加えLambda関数、EventBridgeにも配信が可能です。
バージョン
Lambda関数のバージョニングを使用する場合、DLQ、Destinations両方ともバージョンごとに設定が可能です。つまりバージョンが異なれば違う宛先に配信することができます。 しかし、DLQはバージョン毎に設定がロックされます。つまり過去バージョンのDLQの設定(宛先)を変更することはできません。一方Destinationsは変更可能です。
まとめ
DLQと(失敗時の)Destinationsについて比べてみました。情報量の多さ、連携できるAWSサービスの多さから、多くの場合でDestinationsを使うほうが良いかと思います。