AWS Lambdaの非同期処理を試してみた。

## はじめに

今回は以下の記事で紹介したDirect Triggersにて利用している非同期呼び出しについて調べてみました。

https://qiita.com/s_moriyama/items/6372b10b8e8e4640de7b

## 非同期呼び出しって？

名前の通り、非同期でLambdaを呼び出す機能で、呼び出し元サービスはLambdaの処理を待つことなく、次の処理などを実施することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/cc3eb99d-9126-339a-a16f-661402aab276.png)

また非同期処理の場合、呼び出し元サービスが処理結果を知る方法に少し工夫が必要になってくるのですが、再試行や、エラー通知の仕組みも備わっていました。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invocation-async.html

## やってみよ！

早速非同期処理や、再試行・エラー通知周りを触ってみます。

### Lambdaの準備

常に成功また、失敗するような関数を用意しました。

まずは例外処理をコメントアウトし、常に成功する状態にしておきます。

```python
def lambda_handler(event, context):
    # raise Exception(f"エラー")

    print("処理実行")

    return {
        'statusCode': 200,
        'body': '成功しました！'
    }
```

### 非同期呼び出し方法

CLIで呼び出す時は、`-invocation-type Event`を付与することで非同期処理になります。

```console
aws lambda invoke \
  --function-name async_test   \
  --invocation-type Event \
  --cli-binary-format raw-in-base64-out \
  --payload '{ "key": "value" }' response.json
```

呼び出すと、以下ような応答がありました。

```json
{
    "StatusCode": 202
}
```

このコードはおそらく、HTTPステータスコードの`202 Accepted`を指しているものと思われます。

> リクエストを受け取ったが、処理が完了していないことを表します。実際には、処理はまだ始まっていない可能性もあります。

https://developer.mozilla.org/ja/docs/Web/HTTP/Status/202

CloudWatchLogsを見ると、実行ログが作成されているので、非同期処理を確認できます。

```
2025-01-19T05:04:15.826+09:00 処理実行
```

### 再試行

Lambdaの処理が失敗した場合、自動でリランをしてくれる機能です。

Lambdaの設定タブにある`非同期呼び出し`のところに再試行回数が設定できます。

![スクリーンショット 2025-01-19 5.07.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/be8c389e-6901-3ff5-ce0d-69e88a5cf9de.png)

設定は0〜2回までです。
![スクリーンショット 2025-01-19 5.08.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/e673a38e-ab77-45b0-37c9-7d2648d390c0.png)

必ず処理が失敗するようにコードを変更し、非同期処理をしてみます。

```python
def lambda_handler(event, context):
    raise Exception(f"エラー")

    print("処理実行")

    return {
        'statusCode': 200,
        'body': '成功しました！'
    }
```

結果は以下のようになりました。

|回数|発生時間|時間差|
|---|---|---|
|1|2025-01-19T05:10:19|-|
|2(再試行1回目)|2025-01-19T05:11:13|54秒|
|3(再試行2回目)|2025-01-19T05:13:04|111秒|

公式サイトにも以下のような記載があり、概ねあっていそうです。

> 関数からエラーが返された場合、Lambda はデフォルトでその関数をさらに 2 回再試行します。その際、最初の 2 回の試行の間に 1 分間、2 回目と 3 回目の間に 2 分間の待機時間があります。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invocation-async-error-handling.html

### デッドレターキュー

デッドレターキューサービスを使用すると、非同期処理できなかったまたは、再試行を試みたが成功しなかったイベントを補足することできます。

SNSとSQSが利用できるみたいですが、今回はSNSでメールを飛ばしてみます。

![スクリーンショット 2025-01-19 5.28.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/610d933e-3fd3-caf4-cf4d-3126a1a3b441.png)

2回の再試行が終わった後にメールが届きました。

![スクリーンショット 2025-01-19 5.36.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/286087e4-dc4b-f220-6607-6478dcb1b23c.png)

シンプルな内容で、Lambda関数起動時に指定するペイロードのみです。
複数のLambda関数で利用する場合は、SNSトピックを分けた方が良いかもです。

### イベント最大有効時間

未処理の非同期処理をキューで保持する時間を設定する項目です。

最小1分でしたので、1分で動作確認します。
![スクリーンショット 2025-01-19 5.43.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/9bbeca48-cd7b-a209-6ea1-e8424b3e8c56.png)

Lambda関数は２分待機するようにし、同時に２回非同期処理を実施してみます。
```
import time

def lambda_handler(event, context):    
    # 2分間待機 (120秒)
    time.sleep(120)

    print("処理実行")
    
    return {
        'statusCode': 200,
        'body': '成功しました！'
    }
```

が、エラーになりませんでした。非同期処理も並列処理で実施してくれてそうです。

次に同時実行回数を１にして実施してみます。

![スクリーンショット 2025-01-19 5.55.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/90fe5a28-439a-18b8-df7f-30d86d9e7289.png)

この状態で２回非同期処理を実施すると、１つは処理開始、１つはエラーメールが届きました。（メール内容は先ほどと同じ）

同時実行の上限数を超えた非同期処理はキューイングされた状態で待機し、そのキュー内の保持期間を超えた場合は、破棄されることがわかりました。

## まとめ

Lambdaを使った非同期処理はSNS->SQS->lambdaみたいなファンアウト構成をいつも使っていましたが、直接Lambdaを非同期処理で起動するのも良いかもですね！