NTP serverが死んだら何が起こるんだろう?という疑問をしばらく考えていたのですが、一旦まとまったのでメモし
ます。
cronで定期実行しているシステムで、NTP(Network Time Protocol)などによるシステム時刻のジャンプが発生すると、ジョブが多重起動するリスクがあります。
この記事はこの素敵な記事を思いっきり参考にしています。詳細はこちらへ。
💡 この記事の前提:cronジョブ重複起動の根本原因は「次の実行時刻の即時到来」
cronの動作はシンプルで、現在時刻がcrontabに記述された時刻と一致するかをチェックし、一致すればジョブを起動する。
時刻が未来へジャンプしたとき、実行中のジョブが存在すると、以下のシンプルな条件で多重起動が起きる。
Job A が実行中 ∧ (現在時刻≥Job A の次の予定時刻)
簡単にいうと、Jobが実行中に、次にそのJobが起動される直前に時刻が飛ぶと、重複起動されます。
この原理は、ワイルドカードジョブ(シーン1)にも定刻ジョブ(シーン2)にも共通します。
😱 シーン1: ワイルドカードジョブにおける「次の実行時刻の即時到来」
ワイルドカード(*
)を含むジョブは、時刻が飛んだ際、cronのリカバリ対象にはならない。そのため、時刻ジャンプがそのまま次の実行時刻の到来として機能し、重複起動を招く。
シナリオ: 毎時0分ジョブの実行中に時刻が飛ぶ
-
設定: 毎時0分に実行されるJobを
0 * * * *
で設定している。 -
事象:
-
10:00:00
に Job A が起動(実行時間45分)。 - Job Aの実行中(
10:05:00
)に、システム時刻が10:59:00
へとジャンプする。
-
発生する挙動
- Job A は継続: 実行中の Job A は動作を継続する。
-
次の時刻が到来: 1分後の
11:00:00
が Job A の次の実行予定時刻となる。 -
同時実行: cronは
11:00:00
になると、Job Bを通常通り起動する。
Job Aが完了する前に Job Bが起動し、重複する。
図解:ワイルドカードジョブの重複
😱 シーン2: 定刻ジョブにおける「次の実行時刻の即時到来」(3時間以上のズレ)
特定の時刻に設定された定刻ジョブも、時刻のズレが3時間以上の場合は、cronはそれを時刻の修正と見なし、リカバリ(スキップされたジョブの補填)は一切行わない。しかし、次の実行予定時刻がすぐに訪れることで、やはり重複が発生する。
シナリオ: 実行中のJobがある状態で翌日へ大幅ジャンプ
-
設定: 日次ジョブ(Job)を毎日
2:00
に実行するように設定している。 -
事象:
-
1/1 2:00
の Job A が起動し、まだ実行中の状態にある(例:1/1 2:05
)。 - システム時刻が
1/2 1:59
へとジャンプする。(時刻の進行が23時間54分であり、3時間以上のズレにあたる)
-
発生する挙動
- Job A は継続: 実行中の Job A は動作を継続する。
- リカバリはスキップ: 時刻のズレが3時間以上のため、間の期間にスキップされたはずのジョブは全て無視される。
-
次の時刻が到来: 時刻が
1/2 1:59
となり、1分後の1/2 2:00
が Job A の次の実行予定時刻として到来する。 -
同時実行: cronは
1/2 2:00
になると、Job B(1/2 2:00
実行分)を通常通り起動する。
実行中の Job A と、次の実行予定時刻で起動した Job B が重なり、リカバリの有無に関係なく重複が発生する。
図解:定刻ジョブの重複
内容は シーン1 と同じなので、こっちはちょっと違う見せ方で描いてみる
✅ 重複起動の防止策
時刻ジャンプの幅やジョブの種類に関わらず、最も確実な対策はジョブ自体に排他制御を実装すること。
1. ロックファイルによる排他制御(flock
)
flock
コマンドを利用したロックファイルでの排他制御は、シンプルかつ最も堅牢。
# ロックファイルパスを指定 (ジョブごとに一意であること)
LOCK_FILE="/var/run/my_scheduled_job.lock"
# -n (non-blocking) でロックを試行。ロックできたら後続のコマンドを実行
if ! flock -n $LOCK_FILE -c "/path/to/your/actual_script.sh"; then
echo "$(date): Job is already running. Exiting."
exit 1
fi
flock
はOSのファイルロック機能を利用するため、信頼性が高く、ワイルドカードジョブ、定刻ジョブのいずれの重複起動にも対応可能です。
2. ジョブの冪等性(Idempotency)の確保
「複数回実行されても、システムの最終的な状態が変わらない」という冪等性を持たせる設計も非常に重要。
-
データベース操作:
INSERT
ではなく UPSERT(更新または挿入)を適用し、データの重複挿入を防ぐ。 - 処理対象の限定: 処理対象データを、実行時刻のような曖昧な情報ではなく、確定的なキー(処理対象日付など)に基づいてフィルタリングし、重複実行時の影響を最小限に抑える。
結論
NTPが死んだ後、死んだままならこの問題は起きません。問題なのはNTPが死んだ後に 復活する時 です。復活させる前には、やばい cron がいないか確認してから復活させましょう。