1分より短いサイクルで定期的にLambdaを実行する

この記事は公開されてから半年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Lambdaを1分より短いサイクルで定期的に動かしたいと思ったこと、ありませんか?

Lambdaを定期的に実行する方法としてまず最初に思い浮かぶのはEventBridgeです。ただ、EventBridgeの場合、最短でも1分サイクルでの実行が限界になります。長時間かかる処理の状態を1分より短いサイクルでポーリングしたい…というときにはcronベースの指定ではどうしても限界がありますね。そこでどうするか?というのが今回のお題です。

どうするのか

今回はSQSを使って実装してみました。メッセージの表示遅延秒数をタイマーとして使用し、そこにSQS+Lambdaのメッセージトリガーを組み合わせる方法です。そしてLambda自身が新たなメッセージを投入することで定期実行を実現しています。
アーキテクチャ図はタイトル画像の通りです。LambdaとSQSの間でメッセージが行ったり来たり、という構図です。

キーとなる設定ポイントはDelaySecondsです。これはメッセージタイマーと呼ばれるもので、メッセージ投入後、投入タイミングから起算して指定した時間だけメッセージが表示されないようにするために指定するパラメータです。メッセージ単位で実現する場合はメッセージタイマーが使えますが、キュー全体で表示遅延を設定したい場合は遅延キューを設定することで同様の設定が実現できます。また、遅延キューとメッセージタイマーの両方を指定した場合はメッセージタイマーの値が優先されます。オーバーライドされる挙動としては直感的でわかりやすいですね。

作ってみる

今回は下記のテンプレートのような構成で組んでみました。主要なリソースはLambda関数(AWS::Lambda::Function)とSQSキュー(AWS::SQS::Queue)のふたつです。また、SQSのメッセージトリガーを利用するためにイベントソースマッピング(AWS::Lambda::EventSourceMapping)を構成しています。
CloudWatchロググループ(AWS::Logs::LogGroup)は、直接は必要ないリソースですがあわせて作成しています。これは、ロググループを事前作成せずにLambdaを実行した場合における課題を解決するのが目的です。

  • 保持期間が無制限となるロググループが作成されてしまう
    →あらかじめ保存期限を指定したロググループを作ることで、ログの肥大化を抑止できるようになります。
  • ロググループがみなしごリソースになってしまう
    →最初からテンプレートに組み込んでおくことで、「立つ鳥跡を濁さず」を実現できます。

パラメータには、メッセージタイマーで遅延させる秒数を指定しておきます。今回は30(秒)を設定してみました。
なお、このテンプレートではLambda関数と同時にその実行用ロールを作っていますので、「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」のチェックを忘れずに入れておきます。

CAPABILITY_IAMの確認にチェックを入れる

CloudFormation CAPABILITY_IAM確認画面

試してみる

作った環境にはまだメッセージがひとつもなく、Lambdaも動かない状態ですので、どこかで「着火」する必要があります。作成されたキューの画面で、「メッセージの送信」ボタンからメッセージを送ってみます。内容はなんでもOKです。ここではTTEESSTTの文字列を送ってみました。

SQSに「TTEESSTT」というメッセージを送信

SQSのメッセージ送信画面

なお、今回作った仕組みではキューにメッセージがない場合のエラーハンドリングは行っていませんので、Lambda関数のテストで着火するのは不可能です。テストした場合、定義関数の1行目でKeyErrorが発生して関数がエラーになっちゃいます。

当初の目的は「Lambdaを定期的に実行したい」でしたね。CloudFormationのテンプレートで指定したDelayパラメータがsqs#send_message()DelaySecondsパラメータに渡されるようになっていますので、その間隔で実行されるはずです。CloudWatchの画面に移動し、ログの出力間隔を見てみましょう。

CloudWatch Logs ロググループで「TTEESSTT」の出力を確認

ログを文字列「TTEESSTT」でフィルタした結果

指定した通りの頻度で実行されているのが確認できますね。

もうひとつメッセージを投入してみましょう。次はHHOOGGEEの文字列を送ってみます。

CloudWatch Logs ロググループで「HHOOGGEE」の出力を確認

ログを文字列「HHOOGGEE」でフィルタした結果

先ほどのTTEESSTTとは独立したサイクルで実行されていますね。このように、それぞれのメッセージごとに処理が定期実行されるため、シーケンシャルで処理する場合に起こりがちな「前の処理が長引くと実行が遅延していく」といった事象とはおさらばできそうです。

応用

今回作成した構成はPoCに近いバラックレベルの環境ですが、いろいろ応用の余地がありそうなので考えてみました。

  • SQSで保持するメッセージの内容にカウンターとなる値を保持させる
    定期的な実行を有限な回数で終わらせることができるようになります。
  • 実行間隔を動的に変更する
    何かの処理の進捗をチェックするケースにおいて、ステータスに進行率を持っている場合、80%までは120秒ごと、80%を超えたら60秒ごとにチェック頻度を上げる、といったことができるようになります。

まとめ

今回はLambdaを定期的に実行させるためにSQSを使ったアーキテクチャを紹介しました。SQSの定番処理といえばメッセージのシリアライズが挙げられますが、意外な使い方もできるんですね。ではまた!

あ、テストしたあとはちゃんと作成したスタックを削除しましょうね!

投稿者プロフィール

hiroo
根っこはインフラ屋な古いおじさん。