[アップデート] Lambda 関数が再帰ループを検出して停止するようになりました
コーヒーが好きな emi です。
2023/7/13 に、Lambda 関数の再帰ループを検出して停止するアップデートが発表されました。
「recursive loops」は「再帰ループ」のことで、Lambda 関数が無限に呼び出され続けてループしてしまう状態のことです。これを検知して停止してくれるというアップデートになります。
概要
Lambda 関数で処理したデータをどこかに連携する際、その連携操作がトリガーになってまた同じ Lambda 関数が起動してしまう、そして繰り返されて無限に課金されてしまう……このような状態を Lambda の再帰ループと呼びます。設定ミスによってまれに発生し、翌朝 AWS 料金通知を見てびっくりするような金額が課金されていた、という話をよく聞きます。
例えば Lambda 関数が イベントソースマッピング で Source キューからメッセージを取得し、処理したデータを Destination キューに格納するはずが、設定ミスによって Source キューにデータを戻す実装になってしまった場合再帰ループとなり、延々と課金され続けてしまう、といったケースが考えられます。
今回のアップデートでは、Lambda 関数がイベントを SQS または SNS に送信するとき、関数が呼び出された回数を追跡します。
同じトリガーイベントによって引き起こされる一連の Lambda 呼び出しをリクエストチェーンと呼び、同じリクエストチェーン内で関数が 16 回を超えて呼び出される場合、Lambda はそのリクエストチェーン内の次の関数呼び出しを停止し、イベントをデッドレターキューまたは設定済みの障害時の宛先に送信します。
内部的には X-Ray トレースヘッダーを使用しているそうです。
この機能の使用に追加料金はかかりません。
また、以下も通知されます。
- トラブルシューティングの手順が記載された AWS Health Dashboard 通知
- Lambda が再帰呼び出しを停止してからこの通知が表示されるまでに最大 3 時間かかる場合があります
- AWS アカウントの プライマリアカウント連絡先、代替オペレーション連絡先 に再帰ループ電子メールアラート
- AWS アカウントの関数ごとに 24 時間で最大 1 通のメールを送信します
- Lambda が再帰呼び出しを停止してからこの電子メールアラートを受信するまでに最大 3 時間かかる場合があります
Lambda 関数の再帰ループ検出は、すべてのアカウントに対してデフォルトで有効になっています。
もし意図的に Lambda 関数で再帰ループを発生させたい場合は、Lambda 関数の再帰ループ検出機能をオフにしたい旨を AWS サポートにお問い合わせください。
注意:DynamoDB や S3 にはまだ対応していません
現在、Lambda 関数、SQS キュー、SNS トピックの間の再帰ループを検出できます。
再帰ループしてしまうよくある例として、S3 イベントをトリガーに Lambda を起動して処理したデータをまた同じ S3 に PUT してしまい、またトリガーとなってループしてしまう…というのがあります。こういったケースにはまだ対応していないのでご注意ください。
サポートされている AWS サービス
繰り返しますが、現在、Lambda 関数、SQS キュー、SNS トピックの間の再帰ループを検出できます。
DynamoDB や S3 などの別の AWS サービスがループの一部を形成している場合、Lambda は現在それを検出して停止することができません。
以下の図は現在 Lambda が検出できるループの例です。
※サポートされている AWS のサービスと SDK より図を引用
サポートされている AWS SDK
Lambda が再帰ループを検出するには、Lambda 関数で次のいずれかの SDK バージョン以降を使用する必要があります。
ランタイム | 最低限必要な AWS SDK バージョン |
---|---|
Node.js | 2.1147.0 (SDK version 2) 3.105.0 (SDK version 3) |
Python | 1.24.46 (boto3) 1.27.46 (botocore) |
Java 8 および Java 11 | 1.12.200 (SDK version 1) 2.17.135 (SDK version 2) |
Java 17 | 2.20.81 |
.NET | 3.7.293.0 |
Ruby | 3.134.0 |
サポートリージョン
- アジアパシフィック (香港、ジャカルタ、大阪、ムンバイ、ソウル、シンガポール、シドニー、東京)
- アフリカ (ケープタウン)
- カナダ (中部)
- ヨーロッパ(フランクフルト、アイルランド、ロンドン、ミラノ、パリ、ストックホルム)
- 南米 (サンパウロ)
- 米国東部 (オハイオ、北部バージニア)
- 米国西部 (オレゴン、北部カリフォルニア)
検証
以下 Lambda 関数の再帰ループ検出についての AWS ブログを参考に、再帰ループする Lambda 関数と SQS キューを作成します。
GitHub のサンプルコードは以下を利用します。
AWS SAM CLI を使って環境を構築します。
私は開発環境として Cloud9 を利用しました。
Cloud9 環境の作成
- Name:sam-environment
- Description:sam-environment
Cloud9 環境ができたら、「開く」をクリックします。
画面下部のターミナルでコマンドを入力して操作していきます。
SAM CLI のバージョンアップ
今回 Java 17 を利用するため、SAM CLI のバージョンは SAM CLI v1.82.0
以上が必要です。
作成した Coud9 で利用できる SAM CLI のバージョンを見てみると、1.72.0
でした。
kitani.emi:~/environment $ sam --version SAM CLI, version 1.72.0 kitani.emi:~/environment $
以下のコマンドを順番に実行し、バージョンアップしました。
- 最新のインストーラーをダウンロード
wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
-
ダウンロードした zip ファイルを Unzip
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
-
アップデートを実行
sudo ./sam-installation/install --update
SAM CLI のバージョンを見てみると、1.91.0
になりました。
kitani.emi:~/environment $ sam --version SAM CLI, version 1.91.0 kitani.emi:~/environment $
アプリケーションの構築
git リポジトリのクローンを作成します。
git clone https://github.com/aws-samples/aws-lambda-recursion-detection-sample
プロジェクトディレクトリに移動します。
cd aws-lambda-recursion-detection-sample
SAM CLI を使用してアプリケーションを構築します。
sam build --use-container
SAM CLI を使用してリソースを AWS アカウントにデプロイします。
sam deploy --guided
- 実行結果
kitani.emi:~/environment/aws-lambda-recursion-detection-sample (main) $ sam deploy --guided Configuring SAM deploy ====================== Looking for config file [samconfig.toml] : Not found Setting default arguments for 'sam deploy' ========================================= Stack Name [sam-app]: AWS Region [ap-northeast-1]: #Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [y/N]: y #SAM needs permission to be able to create roles to connect to the resources in your template Allow SAM CLI IAM role creation [Y/n]: Y #Preserves the state of previously provisioned resources when an operation fails Disable rollback [y/N]: N Save arguments to configuration file [Y/n]: n Looking for resources needed for deployment: Creating the required resources... Successfully created! Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-1w8wlu79sqe51 A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False Uploading to sam-app/7570e3b35726fb62755206ec815b0fc5 9485162 / 9485162 (100.00%) Deploying with following values =============================== Stack name : sam-app Region : ap-northeast-1 Confirm changeset : True Disable rollback : False Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-1w8wlu79sqe51 Capabilities : ["CAPABILITY_IAM"] Parameter overrides : {} Signing Profiles : {} Initiating deployment ===================== Uploading to sam-app/7cfea9f7e810e1fc7390d9bfe61ca699.template 2259 / 2259 (100.00%) Waiting for changeset to be created.. CloudFormation stack changeset ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Operation LogicalResourceId ResourceType Replacement ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Add LambdaFunctionMySQSEvent AWS::Lambda::EventSourceMapping N/A + Add LambdaFunctionRole AWS::IAM::Role N/A + Add LambdaFunction AWS::Lambda::Function N/A + Add SqsDLQ AWS::SQS::Queue N/A + Add SqsSourceQueue AWS::SQS::Queue N/A + Add SqsTargetQueue AWS::SQS::Queue N/A ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/samcli-deploy1689767157/3f2c76ac-625a-41bd-9767-ee41c51fba58 Previewing CloudFormation changeset before deployment ====================================================== Deploy this changeset? [y/N]: y 2023-07-19 11:49:29 - Waiting for stack create/update to complete CloudFormation events from stack operations (refresh every 5.0 seconds) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ResourceStatus ResourceType LogicalResourceId ResourceStatusReason ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- CREATE_IN_PROGRESS AWS::SQS::Queue SqsDLQ - CREATE_IN_PROGRESS AWS::SQS::Queue SqsTargetQueue - CREATE_IN_PROGRESS AWS::SQS::Queue SqsDLQ Resource creation Initiated CREATE_IN_PROGRESS AWS::SQS::Queue SqsTargetQueue Resource creation Initiated CREATE_COMPLETE AWS::SQS::Queue SqsDLQ - CREATE_COMPLETE AWS::SQS::Queue SqsTargetQueue - CREATE_IN_PROGRESS AWS::SQS::Queue SqsSourceQueue - CREATE_IN_PROGRESS AWS::SQS::Queue SqsSourceQueue Resource creation Initiated CREATE_COMPLETE AWS::SQS::Queue SqsSourceQueue - CREATE_IN_PROGRESS AWS::IAM::Role LambdaFunctionRole - CREATE_IN_PROGRESS AWS::IAM::Role LambdaFunctionRole Resource creation Initiated CREATE_COMPLETE AWS::IAM::Role LambdaFunctionRole - CREATE_IN_PROGRESS AWS::Lambda::Function LambdaFunction - CREATE_IN_PROGRESS AWS::Lambda::Function LambdaFunction Resource creation Initiated CREATE_COMPLETE AWS::Lambda::Function LambdaFunction - CREATE_IN_PROGRESS AWS::Lambda::EventSourceMapping LambdaFunctionMySQSEvent - CREATE_IN_PROGRESS AWS::Lambda::EventSourceMapping LambdaFunctionMySQSEvent Resource creation Initiated CREATE_COMPLETE AWS::Lambda::EventSourceMapping LambdaFunctionMySQSEvent - CREATE_COMPLETE AWS::CloudFormation::Stack sam-app - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- CloudFormation outputs from deployed stack ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Outputs ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Key SourceSQSqueueURL Description Source SQS queue URL Value https://sqs.ap-northeast-1.amazonaws.com/123456789012/sam-app-SqsSourceQueue-dTq6zLUKqj21 Key TargetSQSqueueName Description Target SQS queue Value sam-app-SqsTargetQueue-PRZBseIbw2Al Key LambdaFunction Description Lambda Function ARN Value arn:aws:lambda:ap-northeast-1:123456789012:function:sam-app-LambdaFunction-h8mvRekt9xxs Key SourceSQSqueueName Description Source SQS queue Value sam-app-SqsSourceQueue-dTq6zLUKqj21 Key TargetSQSqueueURL Description Target SQS queue URL Value https://sqs.ap-northeast-1.amazonaws.com/123456789012/sam-app-SqsTargetQueue-PRZBseIbw2Al ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Successfully created/updated stack - sam-app in ap-northeast-1 kitani.emi:~/environment/aws-lambda-recursion-detection-sample (main) $
再帰ループさせる
デプロイできたら、Source SQS キューの URL をメモしておきます。
# Source SQS キューの URL をメモしておく https://sqs.ap-northeast-1.amazonaws.com/123456789012/sam-app-SqsSourceQueue-dTq6zLUKqj21
それでは SQS キューにメッセージを送り、Lambda 関数を再帰ループさせます。
aws sqs send-message --queue-url (Source SQS キューの URL) --message-body '{"orderId":"111","productName":"Bolt","orderStatus":"Submitted"}'
- 実行結果
kitani.emi:~/environment/aws-lambda-recursion-detection-sample (main) $ aws sqs send-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/sam-app-SqsSourceQueue-dTq6zLUKqj21 --message-body '{"orderId":"111","productName":"Bolt","orderStatus":"Submitted"}' { "MD5OfMessageBody": "669fad69f0c9db1819b6f35c879d9265", "MessageId": "878310fa-95e4-47b1-9bf8-ea3636fe8a96" } kitani.emi:~/environment/aws-lambda-recursion-detection-sample (main) $
結果を見る
Lambda 関数コンソールから該当の Lambda 関数を選択し、モニタリングタブでメトリクスを確認します。
Invocations
(呼び出し)の Count が 16 回で止まっています。
この時私が間違えて 2 回 SQS キューにメッセージを送るコマンドを実行してしまったので(一回目はコマンドを間違えた)線グラフになっていますが、一発でうまくいくと点になるはずです。
Recursive invocations dropped
のメトリクスを確認します。これは再帰的な関数呼び出しが棄却(ドロップ)されたことを表すメトリクスです。
これも私が間違えたためにグラフ上は 2 回になっています。
しかし、ちゃんと再帰ループは破棄されたようです。
通知
翌朝 Health Dashboard を確認すると、通知が来ていました。
メールでも通知が来ていました。
Hello, AWS Lambda has detected that one or more Lambda functions in your AWS Account: 123456789012 are being invoked in a recursive loop with other AWS resources. To prevent unexpected charges from being billed to your AWS account, Lambda has stopped the recursive invocations for the following functions: arn:aws:lambda:ap-northeast-1:123456789012:function:sam-app-LambdaFunction-h8mvRekt9xxs at time Wed Jul 19 12:15:00 UTC 2023 What do I need to do? Please review your functions and their trigger configurations to identify any unintentional recursive patterns. To prevent further recursive invocations of your function, follow the instructions in the AWS Lambda Developer Guide [1]. If your design intentionally uses recursive patterns, please contact AWS Support [2] to turn off recursive loop prevention for Lambda. [1] https://docs.aws.amazon.com/lambda/latest/dg/invocation-recursion.html [2] https://aws.amazon.com/support Sincerely, Amazon Web Services
環境の削除
SAM で作成された以下 3 つの CloudFormation Stack を順番に削除していきます。
- sam-app
- aws-sam-cli-managed-default
- aws-cloud9-sam-environment-xxxxxxxxxx
sam-app
の削除
sam delete
- 実行結果
kitani.emi:~/environment/aws-lambda-recursion-detection-sample (main) $ sam delete Enter stack name you want to delete: sam-app Are you sure you want to delete the stack sam-app in the region None ? [y/N]: y Are you sure you want to delete the folder sam-app in S3 which contains the artifacts? [y/N]: y - Deleting S3 object with key sam-app/7570e3b35726fb62755206ec815b0fc5 - Deleting S3 object with key sam-app/7cfea9f7e810e1fc7390d9bfe61ca699.template - Deleting Cloudformation stack sam-app Deleted successfully kitani.emi:~/environment/aws-lambda-recursion-detection-sample (main) $
aws-sam-cli-managed-default
の削除
SAM はバージョニングが有効な S3 バケットが作成されオブジェクトが存在しているため、先にバケットを空にしておいてください。
sam delete
- 実行結果
kitani.emi:~/environment/aws-lambda-recursion-detection-sample (main) $ sam delete Enter stack name you want to delete: aws-sam-cli-managed-default Are you sure you want to delete the stack aws-sam-cli-managed-default in the region None ? [y/N]: y - Deleting Cloudformation stack aws-sam-cli-managed-default Warning: Cannot resolve s3 bucket information from command options , local config file or cloudformation template. Please use --s3-bucket next time and delete s3 files manually if required. Deleted successfully kitani.emi:~/environment/aws-lambda-recursion-detection-sample (main) $
aws-cloud9-sam-environment-xxxxxxxxxx
の削除
Cloud9 の IDE 画面を閉じ、環境を削除しておきます。
CloudFormation Stack の画面で aws-cloud9-sam-environment-xxxxxxxxxx
を削除します。
終わりに
再帰ループは短時間で課金が跳ね上がるため、非常に怖いものです。今回のアップデートは Lambda 使いの皆様お待ちかねだったのではないでしょうか。
更に S3 や DynamoDB などにも対応していくと良いですね。