XAPPSXAPPS
← Blog
·6分で読めます

GitHub Actions の cron が遅延する・動かない理由 — 日本時間の換算と実測データ

#GitHub Actions#自動化#cron#個人開発
GitHub Actions の cron が遅延する・動かない理由 — 日本時間の換算と実測データ

GitHub Actions のスケジュール実行を使うとき、最初にぶつかるのが「指定した時刻に動かない」問題です。毎朝9時に設定したはずのワークフローが、気づくと昼過ぎに走っている。私が1年近く動かしているデータ収集ワークフローの実測を見ると、1時間の遅れは日常、ひどい日は9時間近くずれました

この記事では、UTCと日本時間の換算という基本から、実際にどれくらい遅れるのかの生データ、そして「遅れても壊れない」組み方までをまとめます。

まず結論

  • cron の時刻指定は UTC。日本時間にするには9時間引く
  • スケジュール実行はベストエフォート。混雑時は遅延し、まれにスキップされる
  • だから「何時に動くか」に依存する設計をしてはいけない
  • 「まったく動かない」場合は、遅延ではなく別の原因(後述の4つ)を疑う

UTC と日本時間の換算

cron に書く時刻は必ず UTC です。日本時間(JST)は UTC+9 なので、動かしたい日本時間から9時間を引いた値を書きます。

GitHub ActionsのcronはUTC表記。日本時間9時に動かすなら0時(0 0 * * *)、日本時間18時なら9時(0 9 * * *)と書く。日本時間から9時間を引くのが基本で、9時間引いてマイナスになる場合は前日に繰り上がる

on:
  schedule:
    # 日本時間 9:00 = UTC 0:00
    - cron: "0 0 * * *"
    # 日本時間 18:00 = UTC 9:00
    - cron: "0 9 * * *"
    # 日本時間 6:00 = UTC 21:00(前日扱いになる点に注意)
    - cron: "0 21 * * *"

引き算の結果がマイナスになるとき(例: 日本時間の朝6時 → 6 − 9 = −3)は、24を足して21時と書きます。このとき「日付が前日にずれる」ので、曜日指定を併用しているときは特に注意が必要です。日本時間の月曜6時は、UTCでは日曜の21時です。

実測:9時のつもりが、10時〜18時に動いていた

ここからが本題です。以下は私が運用しているワークフロー(cron: "0 0 * * *" = 日本時間9時のつもり)が、実際に発火した時刻の連続10日分です。

日付 実際の発火(JST) 予定からの遅れ
8/22 10:02 +1時間02分
8/23 10:07 +1時間07分
8/24 10:05 +1時間05分
8/25 10:04 +1時間04分
8/26 10:06 +1時間06分
8/27 16:11 +7時間11分
8/28 17:57 +8時間57分
8/29 14:32 +5時間32分
8/30 12:30 +3時間30分

スケジュール実行の遅延の実測。1時間程度の遅れが常態で、混雑した日は7〜9時間ずれることもある。指定時刻ちょうどに動く日は一度もなかった

指定時刻ちょうどに動いた日は1日もありません。 平常時でも1時間前後、混み合う時期には半日近くずれます。無料枠だから遅いというわけではなく、スケジュール実行はそういう仕組みだと理解するのが正解です。

なぜ遅れるのか

GitHub のスケジュール実行は、キューに積まれて空いたランナーから順に処理されるベストエフォートの仕組みです。混雑時には遅延し、極端に混んでいる場合は実行がスキップされることもあります。

とくに毎時00分ちょうどは世界中のワークフローが集中する時間帯です。0 0 * * *0 * * * * のような「きっかり」の指定は最も混みます。少しでも遅延を減らしたいなら、7 0 * * * のように中途半端な分を指定するほうが有利です。

on:
  schedule:
    # 00分ちょうどを避ける
    - cron: "17 0 * * *"

これで遅延がゼロになるわけではありませんが、混雑のピークを外す効果はあります。

「遅延」ではなく「まったく動かない」とき

数時間待っても実行された形跡がない場合は、遅延ではなく別の原因です。次の4つを順に確認してください。

1. デフォルトブランチに置いていない スケジュール実行は、デフォルトブランチ(main など)にあるワークフローファイルだけが対象です。作業ブランチに置いたままだと永久に動きません。意外とよくある原因です。

2. リポジトリの活動がなく自動停止された パブリックリポジトリでは、60日間コミットなどの活動がないとスケジュール実行が自動的に無効化されます。放置していたリポジトリで急に止まったなら、これを疑ってください。Actions の画面から手動で再有効化できます。

3. フォークしたリポジトリ フォーク直後のリポジトリでは、スケジュール実行は既定で有効になりません。Actions タブから明示的に有効化する必要があります。

4. YAML の書式ミス on: の階層がずれている、cron の値をクォートしていない、といった書式のミスでもワークフロー自体が認識されません。cron 式は文字列としてクォートするのが安全です(- cron: "0 0 * * *")。

遅延を前提にした組み方

遅れるものだと分かってしまえば、対処は難しくありません。私は次の3つを守っています。

実行時刻に依存する処理を書かない

「9時に走る前提で、8時までのデータを集計する」のような時刻依存のロジックは壊れます。実行時点で取れるものを取る、あるいは日付だけを基準にする設計にします。何時に走っても同じ結果になれば、遅延は問題になりません。

二重実行されても壊れないようにする

遅延したジョブと次の回が近づくことがあります。同じデータを2回書き込んでも問題が起きないよう、保存先に一意制約を張っておくと安心です。

-- 同じ日に二重実行されても重複しない
unique (asin, category, captured_at)

この考え方は「GitHub Actions で毎日データを自動収集する」でも触れた、履歴を貯める設計と相性がいいものです。

手動実行の口を必ず開けておく

workflow_dispatch を書いておくと、Actions の画面からボタンひとつで即実行できます。「今すぐ動かして確認したい」ときにも、遅延しすぎて待てないときにも使えるので、スケジュール実行を書くときは常にセットで入れています。

on:
  schedule:
    - cron: "17 0 * * *"
  workflow_dispatch:

まとめ

GitHub Actions の cron は、UTCで書く時刻ちょうどには動かない遅れても壊れないように作る。この3点さえ押さえれば、スケジュール実行は個人開発でとても頼りになる仕組みです。サーバーを持たずに毎日データが貯まっていく体験は、一度作ると手放せません。

実際にデータ収集ワークフローを組む手順は「GitHub Actions で毎日データを自動収集する」に、YAML の全文つきでまとめています。