SQS → Lambdaのリトライ処理について整理してみた

2018年の6月より、AWS LambdaのイベントソースとしてSQSが選択できるようになりました。

今回はLambdaのイベントソースでSQSを選択し、Lambda側で処理が失敗した場合、どのようにリトライ処理が行われるのかについて整理してみました。

# リソースの作成

今回は、SNS → SQS → Lambda の構成で検証していきたいので、それぞれのリソースを作成いたします。

## SQS

"inu-queue"というキュー名でキューを作成いたします。キューの種類は標準で各設定値はデフォルトです。

そして、"inu-dlq"というキュー名でDLQ(デッドレターキュー)用のキューも作成します。こちらもキューの種類は標準で各設定値はデフォルトになります。

2つのキューを作成したら"inu-queue"のDLQの設定を行います。

DLQの設定方法は、AWSコンソール画面だと以下の手順で設定することになります。 

inu-queueのキューにチェックをつける → [キューの操作]をクリック → [キューの設定]をクリック → デッドレターキューの設定で[再処理ポリシーの使用] をチェック → デッドレターキューの記入箇所に"inu-dlq"と入力 → 最大受信回数を3に設定します。

![sqs20200621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451269/e088341c-ecb1-f203-ecb0-550c73f1a4e0.png)

SQSでは再処理ポリシーを設定した場合、あるメッセージに対して後段の処理(今回はLambda)が失敗した際にはそのメッセージはキュー内から削除されません。
したがって、可視性タイムアウトの時間が経過したら再びそのメッセージを処理することが可能になります。

設定した最大受信回数よりも多く後段の処理が失敗した場合、そのメッセージは設定したDLQに移動します。このあたりについては後ほど具体例をもとに説明いたします。





## Lambda

AWSコンソールの画面でAWS Lambdaの画面に遷移して[関数]  → [関数を作成]で

"dog"という名前のLambda関数を作成します。

今回はロールは[基本的な Lambda アクセス権限で新しいロールを作成] で作成しインラインポリシーで以下の権限を追加しました。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sqs:DeleteMessage",
                "sqs:GetQueueAttributes",
                "sqs:ReceiveMessage"
            ],
            "Resource": "arn:aws:sqs:[region]:[accountID]:inu-queue"
        }
    ]
}
```

そして[トリガーの追加]から先ほど作成したSQSのキュー"inu-queue"を選択しトリガーを有効にします。
![lambda20200621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451269/8835820a-e027-2086-1b86-b1f1002371cd.png)

![トリガー20200621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451269/210eacdb-1fb8-7ab9-bc9e-d63d1a37d7be.png)


これでリソースの作成は以上になります。



ちなみにSQSのイベントトリガーを有効にする際にバッジサイズを1よりも多くすると、同時に複数のメッセージを取得できますが、冪等性の担保する必要があります。



# 処理の時系列の整理

具体的な数値で考えると理解しやすくなると思うのでここでは以下とします。

- 配信遅延時間：10秒
- 可視性タイムアウト：20秒
- 最大受信回数：2回

## **処理が成功する場合**

SQSに新規メッセージAが入力される 
→ 10秒(配信遅延時間)が経過した後、メッセージAを処理できる状態になる 
→ LambdaがメッセージAを処理開始(1回目) 
→ SQS内のメッセージAは20秒の間、どのLambdaからも処理されなくなる。
→ LambdaがメッセージAの処理を成功 
→ SQS内のメッセージAが削除される

## **処理が失敗する場合**

キューに新規メッセージAが入力される 
→ 10秒(配信遅延時間)が経過した後、メッセージAを処理できる状態になる 
→ LambdaがメッセージAを処理開始(1回目) 
→ キュー内のメッセージAは20秒(可視性タイムアウト)の間、どのLambdaからも処理されなくなる。
→ LambdaがメッセージAの処理を失敗 
→ キュー内のメッセージAが削除されない 
→ LambdaがメッセージAの処理を開始してから20秒経過(可視性タイムアウト) 
→ キュー内のメッセージAが処理可能になる
→ LambdaがメッセージAを処理開始(2回目) 
→ キュー内のメッセージAは20秒(可視性タイムアウト)の間、どのLambdaからも処理されなくなる。
→ LambdaがメッセージAの処理を失敗 
→ キュー内のメッセージがDLQ(デッドレターキュー)へ移動する


という流れになります。
**設定した最大受信数分だけLambdaが実行し、それでも失敗した場合メッセージがDLQへ移動する**点がポイントになります。



# 検証

本当に最大受信回数分だけLambdaが実行されるのか検証してみました。

Lambda "dog"のコードを以下とし、必ずエラーとなるように設定します。

```python
import json

def lambda_handler(event, context):
    msg = event['Records'][0]['body']
    print(msg)
    try:
        a = 1 / 0 
    except:
        raise Exception('ERROR!!')
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```



そしてDLQの最大受信数を3に設定し、AWSコンソールのSQSの画面から

"inu-queue"を選択 → [キューの操作] → [メッセージの送信]でメッセージを送信します。

![sqstest20200621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451269/f1601534-7e30-a030-c2d8-3bf06f8cb922.png)


数分待ってからLambdaのログを確認すると...

![log20200621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451269/e89b170e-455d-fc98-1e5f-6532c7f664df.png)


test-messageのメッセージが3回処理されエラーが発生していることが確認できました。



次にDLQにメッセージが移動しているのか確認します。

そしてDLQの最大受信数を3に設定し、AWSコンソールのSQSの画面から

"inu-dlq"を選択 → [キューの操作] → [メッセージの表示/削除]でメッセージを確認します。

![dlq20200621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451269/892ea0b5-c787-28af-d028-aa48f9126b19.png)


test-messageのメッセージが追加されていることが確認できました。同様に"inu-queue"内のメッセージを確認するとtest-messageが削除されていました。

これでメッセージがDLQへ移動していることが確認できました。

DLQに蓄えられたメッセージは再度元のキューに戻したり、デバッグ用にログとしてアーカイブすることができます。

# 最後に

今回は、LambdaのイベントソースにSQSを選択した際のリトライ処理について整理させていただきました。

SQS と Lambdaの組み合わせはサーバーレスな組み合わせでも強力なものですのでこれからも知見を深めていきたい次第です。