cron のスケジュールは実際どう動くか
cron 式は、ジョブがいつ走るかを決めるたった5つの数字です。そして驚くほど多くの本番障害が、その読み違いから始まります。本ガイドでは構文、読んだ印象と正反対に動く規則、タイムゾーンの罠、そして無人運用でスケジュールを信頼できる状態に保つ習慣を扱います。
5つのフィールド
標準の cron は順に5つのフィールドを取ります。分(0–59)、時(0–23)、日(1–31)、月(1–12)、曜日(0–6、0が日曜)です。各フィールドは単一の値、リスト(`1,15`)、範囲(`9-17`)、ステップ(`*/15`)、または「毎回」を表す `*` を受け付けます。つまり `0 9 * * 1-5` は月曜から金曜の朝9時です。
ステップは直前の範囲に適用されます。分フィールドの `*/15` が「ジョブを作った時点から15分ごと」ではなく「0・15・30・45分」を意味するのはそのためです。スケジュールは現在時刻がパターンに一致したときに発火します。cron は間隔を追跡せず、時計を見ているのです。
日と曜日は AND ではなく OR
誰もが驚く規則です。日フィールドと曜日フィールドの「両方」が制限されている場合、cron はどちらか一方が一致したときにジョブを実行します。両方が一致したときではありません。したがって `0 0 13 * 5` は「13日の金曜日」ではなく、毎月13日にも、毎週金曜日にも走ります。
この挙動は両方のフィールドが制限されている場合にだけ現れます。片方が `*` なら、もう片方がそのまま決めます。本当の積集合が欲しければジョブ側にガードを置くしかありません——スクリプトの冒頭で日付を確認し、違えば早期終了する。式では表現できないからです。
どの時計の上で走っているのか
cron 式はタイムゾーンを持ちません。スケジュールは実行側が使うゾーンで解釈されます。古典的な crond ならマシンのローカルゾーン、多くのマネージドスケジューラや CI では UTC です。同じ5フィールドが配置先によって別の実時刻を意味することになり、「深夜」のレポートが昼下がりに届く事故が起きます。
実行側のゾーンが夏時間を採用していると、年に2回、スケジュールは2通りのいずれかで狂います。時計を進める春の夜には、飛ばされた時間帯に予約されたジョブがまったく走りません。秋の夜には、繰り返される1時間のせいでジョブが2回走りえます。UTC で組めば両方を避けられますが、その代わり現地の壁時計の時刻が年間で1時間ずれます。どちらの失敗なら許容できるかを意識して選んでください。
拡張構文、ニックネーム、そして6番目のフィールド
多くのスケジューラが古典構文を拡張していますが、互換性はありません。Quartz 系の式は先頭に秒フィールドを足すため、6フィールドの式は5フィールドとまったく別物になり、コピーしてきたスケジュールが60倍の頻度で発火しかねません。実装によっては `L`(最終)、`W`(最も近い平日)、`#`(その月のn番目の曜日)を加え、日付フィールドで `*` の代わりに `?` を受け付けるものもあります。
大半の実装はニックネームも受け付けます。`@hourly`、`@daily`、`@weekly`、`@monthly`、`@yearly`、ときに `@reboot`。当てはまる場面では数値より明確ですが、正確な分を隠します——`@daily` は真夜中であり、そのマシン上の他のすべての `@daily` ジョブが走り出す時刻でもあります。
生き延びるスケジュールの設計
実行は「飛ばされる・重複する・重なる」と想定し、そのいずれでもデータが壊れないようジョブを設計しましょう。二重実行が無害になるよう冪等にし、遅い実行が次の実行と重ならないようロックを取り、何を処理したかを記録して、飛ばされた回が静かな欠損を残す代わりに追いつけるようにします。
スケジュールは散らしましょう。`0 0 * * *` に揃えたものは一斉に始まり、共有ホストではその集中は自ら招いた事故です。半端な分を選び、関連ジョブは時間をずらし、共有 API を叩くものには開始時に小さなランダム遅延を足します。最後に、ジョブが「走らなかった」ことに対してアラートを設定してください。静かに発火をやめたスケジュールは、やることが無いスケジュールとまったく同じに見えるからです。
- `*/15 * * * *` — 15分ごと、0・15・30・45分ちょうどに。
- `0 9 * * 1-5` — 平日 09:00、実行側のタイムゾーンで。
- `0 0 13 * 5` — 13日「または」金曜日。13日の金曜日ではありません。
- `0 3 1 * *` — 毎月1日の 03:00。