実装メモ:スケジュール処理

毎日、指定時刻(0:00)に処理を実行したい。どうすればいい?

この問題は、専用ソフトcronがあるくらいにはややこしかったりする。

条件未達

  1. 現在時刻を確認 (3/11 0:00)
  2. 目標時刻を確認 (3/10 18:00)
  3. 差を求める (6h)
  4. Sleep?

条件成立

  1. Sleep完了
  2. 現在時刻を確認 (3/11 0:00)
  3. 目標時刻を確認 (3/11 0:00)
  4. 処理実行
  5. 目標時刻の更新 (3/12 0:00)
  6. 差を求める (24h)
  7. Sleep?

問題点

0:00に実行してほしいが、時刻は変わるかもしれない

  • ntpdateで現在時刻が遅くなった
  • ntpdateで現在時刻が早まった
  • 旅行等でタイムゾーンが変わった
  • サマータイムでオフセットが変わった

cronから学ぶ

分解能は1分。ジョブは毎分チェックする。場所移動によるタイムゾーンの変更は無視し、起動時のタイムゾーンを使い続ける。ただしサマータイムの変更は検知する。

平常時の誤差は1分未満だが、それ以上の誤差が生じても対応できる。

処理が重いか、システム負荷が高く、5分未満の遅延が発生した場合、過去5分に実行するはずだったタスクを実行する。基準時刻を1分ずつ進めながら、古い順に実行する。

5分以上3時間未満の遅延が発生した場合、現在時刻のジョブに加え、過去に実行するはずだった定時ジョブを実行する。ただし、総処理時間は1分で、間に合わなかったジョブは実行されない。通常5分以上の遅延が発生することは稀で、サマータイム開始(1:59 → 3:00)の影響を想定している。定時ジョブは時刻に"*"を使っていないジョブのことである。ワイルドカードジョブは短い間隔で実行される想定なのか、1回くらい処理されなくても問題ないと判断されたのかもしれない。

誤差が3時間未満のマイナスだった場合、最後に確認した時刻に基準時刻が追いつくまで、定時ジョブの実行をスキップし、ワイルドカードジョブのみを実行する。サマータイム終了(1:59 → 1:00)の影響を想定している。定時ジョブが1日に2度実行されることを防いでいる。

誤差がそれ以上だった場合、本来実行されるはずだったジョブは全てスキップされる。

所感

サーバ上で動かすため、タイムゾーンを固定するのは妥当。設定できる最小分解能でポーリングしているので性能は悪くない。定時ジョブが特別扱いされており、

GMTを基準に考える。サマータイムが厄介で、2:00がすっ飛ばされたり、夏の間ジョブが1時間ずれるので、タイムゾーン(ST)に修正する。ntpdateで50分進む場合も考えられる。

余談。cronの設定は ワイルドカード("star")かどうかが重要で、実際の回数は重要ではない。例えば 0-9と書けば定時ジョブが10個定義され、*/12と書けば12分or12時間毎のワイルドカードジョブが定義される。サマータイム突入時、

cron読解メモ

clockTimeという、epochTimeを60で割った値を使う。ようはtime(NULL)

sleep復帰後のclockTimeは timeRunning に格納される。最後に処理を実行した基準時刻はvirtualTime に格納される。この差は通常1になるが、時々より大きな値になったり、負の値になることがある。cronのソースコードにはDSTの影響とあるが、DSTはepochtimeに影響しないので、その真意はわからない。サマータイムがepochtimeを変更するシステムを考慮しているのかもしれない。

この差が正の場合、cronチェックが遅れているため、基準時刻を進めながら本来実行すべきだったジョブを実行する。差が中程度の場合は定時ジョブのみ、差が大きすぎる場合は諦める。差が負の場合は基準時刻に追いつくまでワイルドカードジョブのみを実行する。

ようは、ユーザーが時刻を進めればその間のジョブを実行しようとし、時刻が巻き戻れば元の時刻になるまで定時ジョブを無視しているようだ。

なお、タイムゾーンのオフセットはGMToffで管理する。それぞれとlocaltimeのBSD/GNU拡張tm_gmtoffを使用する。この2つを保存しておけば、

サマータイムの導入による3時間未満の誤差、3時間以上の変化、あるいは0分未満の誤差が生じることがある。

OSの時刻が補正されて、基準が遅くなった

上記ロジックで支障ない。

OSの時刻が補正されて、基準が早くなった

本来処理が実行されるべき時刻に処理が実行されなくなる。

時差の変更

動作中に日本(GMT+9)から中国(GMT+8)へ旅行したらどうする?