Azure Function の Timer Trigger の色々
- Timer Trigger Function とは
- Timer Trigger Function はどうやって定期実行をやっているのか
- TimerTrigger Attribute で指定出来る事
- 何故スケールアウトされても単一のインスタンスで実行されるのか
Timer Trigger Function とは
Timer Trigger Function は大まかに以下の特徴を持ちます
- CRON 式、もしくは TimeSpan を使ってスケジュールを指定する
- Azure Function はスケジュールに従って起動する
- 実行履歴は Blob に記録されている
Blob に記録する実行状態
Blob に以下のような json を記録して実行状態を管理しています。
{ "Last": "2023-03-01T16:00:35.0115543+09:00", "Next": "2023-03-01T16:01:35+09:00", "LastUpdated": "2023-03-01T16:00:35.0115543+09:00" }
それぞれ
- Last: 前回メソッド実行日時
- Next: 前回 ScheduleStatus 更新した時点での、次のスケジュール実行予定日時
- LastUpdated: 前回 ScheduleStatus 更新した日時
という値になってます。この json
が TimerTrigger Function に Bind される TimerInfo.SchedleStatus にマッピングされます。
この値は Function の処理が終了した時点で計算がなされます。await _executor.TryExecuteAsync(input, token);
の部分が Azure Function として実装した部分になるはず。
Timer Trigger Function はどうやって定期実行をやっているのか
まぁそういうもんだからと言えばそうなのですが、定期実行の処理を組むための方法はいくつかあります。
- System.Timers.Timer
- System.Threading.Timer
- System.Threading.PeriodicTimer
- System.Windows.Forms.Timer
- System.Windows.Threading.DispatcherTimer
それぞれの Timer の良し悪しがどうかはありますが、結論から言うと Timer Trigger Function では System.Timers.Timer を使って定期実行を行っています。
TimerListener の実装を読めばわかるのですが、端的に言うと以下のことをやっていますね。
- インスタンス起動時に、 Attribute に指定した Schedule と Blob に記録した実行状態もとに次回実行時間を決める
- Blob に実行状態が記録されていなければ、現在時刻をベースに次回実行時間を決める
- つまり Function の初回デプロイ時には起動しない
- 計算した次回実行時が現在日時よりも小さい場合、Function は即実行される
- Blob に実行状態が記録されていなければ、現在時刻をベースに次回実行時間を決める
- Timer インスタンスの Elapsed にイベントを登録
- 開発者が実装した処理がイベントとして登録される
- Elapsed に追加したイベントの終了時に、次回 Function 起動までのインターバルを登録
- Timer インスタンスを作り直している
Elapsed に追加したイベントの終了時に、次回 Function 起動までのインターバルを登録
何でイベントの終了時に毎回インターバルを計算しているかというと、Function の実行時間を加味しているためと思われます。例えば以下の状況で考えてみます。
- Schedule は 10 分に一回
- Function の実行時間は平均して 5 分
- Function の初回実行時間は
10:00:00+00:00
Schedule は 10 分に一回なので、10:10:00+00:00
、10:20:00+00:00
と起動していくことが期待されます。しかし Function の実行時間を 5 分としているため、初回 Function の終了時間は 10:05:00+00:00
となります。そうすると次回の Function 実行時間までの Interval は 00:05:00
となるので、毎回計算が必要というわけですね。
autoReset=true
として Interval に Schedule の値をそのまま使えばいいのでは?とも思うかもですが、例えば Function の起動時間が Schedule の間隔を越してしまうとどうなるのでしょうか?
- Schedule は 10 分に一回
- Function の起動時間が 11 分
そうすると autoReset=true
なので Function が完了せずに次の Function が実行されてしまいます (Elapsed のイベントが実行されるので)。そうすると Blob の実行状態もおかしなことになってしまうので、毎度インスタンスを作り直しているのだと思います。
TimerTrigger Attribute で指定出来る事
ここにまとまってます。
Schedule に CRON 式か TimeSpan を指定し Function の起動間隔を定義する
Schedule
は Function の実行間隔を定義できるものです。その定義の仕方として CRON 式
と TimeSpan
が使えるよという事です。
CRON 式
は慣れないとぱっと書けないと思います。私は 秒 分 時 日 月 曜日
という順番で定義するとだけ覚えています。複雑な式を書く場合は Chat GPT や Copilot にお世話になれるので、最低限で良いかなと思っている今日この頃。
TimeSpan
は正確に言うと TimeSpan 値
を指定出来る、です。なので TimeSpan.FromMinutes(1)
とかではなく、00:01:00
のようにせねばいけません。TimerTrigger Attribute
は string
しか受け付けないようになっています。
1 年に一回の起動をさせたければ、365.00:00:00
のような感じになりますね。これはこれでわかりにくい。
ちなみにですが、設定名を % で囲んでおくと Schedule に設定できます。例えば、
[Function("TimerTriggerSample")] public void Run([TimerTrigger("%SampleTimerSchedule%")] TimerInfo myTimer) { _logger.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); if (myTimer.ScheduleStatus is not null) { _logger.LogInformation($"Next timer schedule at: {myTimer.ScheduleStatus.Next}"); } }
のように定義すると、%SampleTimerSchedule%
の設定値をそのまま使えます。環境変数から簡単に起動間隔を修正できます。
RunOnStartup=true にすると、Function Runtime の起動時に Function を実行できる
なので開発環境では true
にしておくのが便利かと思います。1h に一度起動するみたいな Function の動作確認がだるいですからね。
Function Runtime の起動時という事なので、Azure にデプロイすると例えば Function App 自体の再起動時やインスタンスのスケールアウト時に Function が実行できるということになります。本番環境で true
にするのは推奨されていないみたいですね。
実装を読んでみると、インスタンスが起動した時に問答無用で Function の処理を実行しています。その時点をベースに次回実行時の日時が決まるみたいですね。インスタンスが再起動したりすると、その時点で Function の起動タイミングがずれていくのが分かります。
例えば、RunStartup=true
、Schedule が 1 時間に一回だったとして、
2024-03-01T09:00:00+00
Function が起動- Next=
2024-03-01T10:00:00+00
として Function が終了 2024-03-01T09:30:00+00
にインスタンスが再起動- 再起動完了後に Function が起動
- Next=
2024-03-01T10:30:00+00
として Function が終了
とかになってしまうわけです。インスタンス再起動はプロダクト運用者が意図せずに行われるので予測がつきません。期待しているスケジュールともずれていく可能性があるというのも含めて、本番環境では false
にするのが推奨されているのでしょう。
UseMonitor で Blob にスケジュールを記録するか切り替える
規定では UseMonitor=true
となっていて Timer Trigger Atrbute
では false
にすることが出来ません。ドキュメントには実行間隔が 1 分以上の場合は true
と書かれており、Schedule の値で起動間隔を 1 分未満
にすると記録されなくなります。
以下が該当部分の Source Code ですね。
Timer Trigger Function は冒頭でも書きましたが、Function 終了後に Blob に 実行状態を永続化します。もちろんこの書き込み処理にも時間はかかりますし、ネットワークアクセスも行うので、頻繁に起動される Function の場合は永続化したくないという事なのでしょう。
もちろん、これが永続化されないからといって定期実行が行われないわけではありません。冒頭でも話しましたが、TimerTrigger Function
は内部的には Timer のインスタンスを使っています。なのでこのインスタンスの Interval (Timer Trigger Attribute の Schedule で設定した間隔) が経過する度に Function が起動されます。
デメリットとしてはインスタンス再起動時に前回実行時の状態を復元出来ない事でしょうか。1s 未満の起動を期待する Function で、それがデメリットになる場面があるかはわかりませんが (どちらにせよ再起動時にすぐ起動するわけだし)。
ただ、そもそも起動間隔を 1s 未満にするという事は、「Function の実行時間も 1s 未満である」という事であり、そういう処理を Timer Trigger で実装する場面がどれほどあるかはわからないですね。
何故スケールアウトされても単一のインスタンスで実行されるのか
TimeTriggerListener に Singleton Attribute が Mode=Listener で付与されているからです。