はじめに
「1分より短いサイクルで定期的にLambdaを実行する」と検索すると、以下の記事がヒットします。
この手法を試してみたのですが、うまくいかなかったため、課題点と改善策をまとめました。
上記記事の概要
定期的にLambdaを実行するには、EventBridgeを使う方法が一般的ですが、最短で1分間隔の実行となります。そのため、上記の記事では、SQSのメッセージタイマーの機能を利用し、SQSにメッセージを投入してから指定した時間だけメッセージが表示されないようにすることで、SQSトリガー+Lambda実行時にSQSメッセージを投入することで秒単位のサイクルでLambda実行する旨が記載されています。
課題
上記を実際に試すと、手元の環境では一定回数実施後に、Lambdaの実行が停止してしまいました。また、以下のメッセージがAWSから届きました。
日本語に訳すと、
AWS Lambda は、AWS アカウント内の 1 つ以上の Lambda 関数が他の AWS リソースとの再帰ループで呼び出されていることを検出しました。AWS アカウントに予期しない料金が請求されるのを防ぐため、Lambda は「影響を受けるリソース」タブにリストされている再帰呼び出しを停止しました。
とのことで、再帰ループを検知するとAWS側で自動停止してくれる機能があるようです。今回は意図的に再帰ループを利用しているので、困りました。チェック機能をオフにすることもできるようなのですが、それもそれで怖いですし。
改善策
ループにならなければいいので、トリガーは1分おきに実行するEventBridgeとSQSの2つを使用し、以下のようなコードを書きました。
import json
import boto3
from datetime import datetime, timezone
QUEUE_URL = "XXXXXXXXXXXXX"
sqs = boto3.client('sqs')
def lambda_handler(event, context):
# イベントソース(SQS, EventBridge)の判定
is_sqs = isinstance(event.get('Records'), list) and len(event['Records']) > 0 and event['Records'][0].get('eventSource') == 'aws:sqs'
is_eventbridge = event.get('source') == 'aws.events' and event['time']
# SQSトリガーのときは、なにもしない
# EventBridgeトリガーのときは、SQSにメッセージを投入する
if is_sqs:
print("from SQS: " + event['Records'][0]['body'])
elif is_eventbridge:
print("from EventBridge:" + event['time'])
sqs.send_message(QueueUrl = QUEUE_URL, MessageBody = event['time'] + "+15", DelaySeconds = 15)
sqs.send_message(QueueUrl = QUEUE_URL, MessageBody = event['time'] + "+30", DelaySeconds = 30)
sqs.send_message(QueueUrl = QUEUE_URL, MessageBody = event['time'] + "+45", DelaySeconds = 45)
else:
print("from other Trigger")
return {
'statusCode': 200,
'body': "success"
}
15秒おきに処理をしたい場合は、EventBridgeトリガーで呼ばれた際に、15秒後、30秒後、45秒後にメッセージが表示されるようにメッセージタイマーを設定したメッセージを投入しておけば、あとはSQSトリガーで拾ってくれます。
おわりに
意図しない無限ループを防ぐAWSの監視機能は優秀、と思うとともに、このような機能を安易に無効化するのではなく回避策を適用することで、AWSを利用するメリットを最大限享受したいなと思いました。
