自動化をcronで回す前の設計|二重実行・失敗通知・再実行をどうするか

cron自動化を安全に運用するため、前提条件、二重実行防止、ログ、冪等性、タイムゾーン、手動停止の設計をコード例付きで解説します。

cronへ1行追加すれば、処理を定期的に起動できます。しかし、前回の処理との重複、見逃した失敗、再実行による二重登録はcron式だけでは防げません。

この記事では、定期処理を登録する前に決めたい6つの設計を、最小構成のシェルスクリプトとともに整理します。cron式そのものより、「同じ処理が重なっても壊れないか」「失敗を後から追えるか」「人が止められるか」を先に固めるのが要点です。自動化の対象を選ぶ段階なら、毎日の作業を自動化する考え方から読むと全体像をつかめます。

登録前に決める6項目

設計項目最低限決めること確認する失敗例
実行条件ユーザー、作業場所、環境変数、正常終了の条件手動では動くがcronでは動かない
二重実行防止ロックの範囲、競合時に待つか終了するか前回処理と重なり、同じデータを更新する
ログと通知記録項目、保存期間、通知条件、通知先失敗しても翌日まで気づかない
再実行一意な実行キー、状態、再試行上限二重登録や二重送信が起きる
タイムゾーン起動判定、ログ、実行キーの日付基準日付境界や夏時間で対象日がずれる
停止と再開停止方法、権限、再開前の確認項目障害中も処理が繰り返される

実行時刻の書き方だけを確認したい場合は、cron式の書き方を参照してください。

定期実行の前提を固定する

最初に、処理の入力、出力、実行ユーザー、作業ディレクトリ、環境変数、正常終了の条件を1枚にまとめます。

POSIXのcrontab仕様では、定期コマンドに少なくともHOMELOGNAMEPATHSHELLの既定環境が渡されます。普段のターミナルで追加した設定は前提にせず、実行ファイルと保存先は絶対パスで指定します。秘密情報はソースやcrontabへ直書きしません。

登録前にcronと同じ条件で手動実行し、終了コード、期待した出力、対象がない日の動作を確認します。作業の分け方は記事制作を半自動化する手順にも応用できます。

#!/bin/sh
set -eu

APP_DIR="/srv/example"
LOG_DIR="/var/log/example"

cd "$APP_DIR"
mkdir -p "$LOG_DIR"
/usr/bin/node "$APP_DIR/scripts/daily-job.mjs"

パス、実行ユーザー、保存先の権限は※要確認です。

二重実行を止める

処理時間が実行間隔を超えれば次の起動と重なります。通信遅延もあるため、普段の速さを根拠に重複対策を省けません。

Linuxでflockを利用できる環境なら、1つのロックを取れたプロセスだけに処理を実行させられます。-nはロックをすぐ取得できない場合に待たず失敗する指定で、競合時の終了コードは既定で1です。たとえば次のcrontabでは、前回が動作中なら新しい本処理を開始しません。

*/5 * * * * /usr/bin/flock -n /run/lock/example-daily.lock /srv/example/bin/run-daily.sh

公式マニュアルによると、NFSやCIFSなど一部のファイルシステムではflockが失敗する場合があります。複数端末で動かす構成も、ローカルファイルだけでは全体を排他できません。共有データベースの一意制約など、環境に合う方式を※要確認です。

失敗を記録する

ログには、時刻、処理名、入力範囲、終了コード、処理件数を残します。APIキーや個人情報は出しません。

POSIX仕様では、未転送の標準出力と標準エラーは実装依存の方法で利用者へメール送信されます。これに頼らず、まず保存先を固定します。

15 2 * * * /usr/bin/flock -n /run/lock/example-daily.lock /srv/example/bin/run-daily.sh >> /var/log/example/daily.log 2>&1

終了コードが0以外、成功記録が一定時間ない、連続失敗のどれで通知するかも決めます。通知先、保存期間、実際の失敗頻度は※要確認です。自動投稿が失敗する原因と対策も参考になります。

再実行を安全にする

失敗後に同じコマンドを実行すると、途中まで保存されたデータへもう一度処理が走る可能性があります。そこで、同じ入力を複数回処理しても結果が壊れない「冪等性」を持たせます。

処理単位ごとに一意なキーを作り、「未処理なら実行、完了済みなら終了」と判定します。外部送信と完了記録の間で止まる場合に備え、pendingprocessingcompletedfailedの状態を分けます。

実行キー = 対象日 + ":" + 対象ID

開始時:
  completedなら終了する
  processingが残っていたら時刻と実行主体を確認する
  pendingまたはfailedならprocessingへ変更する

処理成功時:
  結果を検証してcompletedへ変更する

処理失敗時:
  エラー種別と試行回数を記録してfailedへ変更する

再試行は回数を制限し、認証失敗や入力不備は人の確認へ回します。外部サービスが冪等キーを受け付けるかは公式仕様で※要確認です。

データベースへ登録する処理では、アプリ側の事前確認だけでなく、一意制約を最後の防波堤にします。PostgreSQLのINSERT ... ON CONFLICTは、一意制約または一意インデックスとの競合時にDO NOTHINGDO UPDATEを選べます。ただし、これだけで外部APIへの二重送信までは防げません。データ保存と外部送信を別々の状態として記録し、対象サービスの仕様に合わせて設計します。

タイムゾーンを固定する

「毎日午前2時」では地域が曖昧です。基準をAsia/Tokyoなどの地域名かUTCで決め、crontab、アプリ、ログに明記します。

Linuxのcrontab(5)では、CRON_TZでcrontab用のタイムゾーンを指定できます。ただし、ログに記録される時刻はcronデーモンが動く端末のローカルタイムになると説明されています。つまり、起動判定とログ表示が同じタイムゾーンとは限りません。

CRON_TZ=Asia/Tokyo
15 2 * * * /usr/bin/flock -n /run/lock/example-daily.lock /srv/example/bin/run-daily.sh

CRON_TZ対応は対象端末のマニュアルで※要確認です。実行キーの日付にも同じ基準を使い、日をまたぐ処理は開始日と完了日のどちらへ属すか決めます。

手動停止口を残す

安全な自動化には停止口が必要です。実行前に停止ファイルを確認すれば、処理本体を変えず一時停止できます。

STOP_FILE="/srv/example/var/PAUSE_DAILY_JOB"

if [ -e "$STOP_FILE" ]; then
  echo "status=skipped reason=manual_pause"
  exit 0
fi

停止はskippedと記録します。再開前に停止理由、残存処理、再実行範囲を確認し、停止権限と連絡先も運用票へ残します。

停止中に予定時刻を何回通過したかも記録します。再開時に未処理分をすべて実行するのか、最新分だけにするのかは業務によって異なります。停止解除を、そのまま未処理分の一括実行と同じ意味にしない方が確認しやすくなります。

cron以外を選ぶ目安

端末の停止中に予定時刻を過ぎた処理を、起動後に補う必要があるなら、cron以外も候補です。systemdのタイマーユニットでは、OnCalendar=で指定したタイマーにPersistent=trueを設定すると、タイマーが停止していた間に少なくとも1回実行時刻を過ぎていた場合、再有効化時に処理を起動します。

ただし、複数回分を1回の起動でどう扱うかは処理側の設計が必要です。ジョブ履歴、権限、実行環境を一元管理したい場合は、利用中のクラウドやCIのスケジューラーも比較します。GitHub Actionsを使う場合の通知設計は、GitHub Actionsの失敗通知も参考になります。

よくある質問

cronで前の処理が終わっていない場合はどうなりますか

cronは次の予定時刻になれば新しいコマンドを起動するため、処理が重なる可能性があります。flockなどで排他し、競合時に終了するのか待つのかを決めます。

パソコンやサーバーの電源が切れていた分は後から実行されますか

通常のcronだけでは、停止中に過ぎた実行時刻を自動で補う前提にしない方が安全です。取りこぼしを補う必要がある場合は、systemdタイマーのPersistent=trueなどを検討し、対象環境で※要確認です。

cronの実行結果はどこで確認できますか

標準出力と標準エラーの扱いはcron実装や端末設定で異なります。この記事の例のようにログへ明示的に追記し、終了コードと成功件数も記録すると追跡しやすくなります。

再実行すれば必ず復旧できますか

必ずとは限りません。途中まで外部送信や保存が済んでいる可能性があるため、一意な実行キーと処理状態を確認してから再実行します。認証失敗や入力不備は、自動再試行より原因の修正を優先します。

cronのテストはどうすればよいですか

最初に処理本体をcronと同じ実行ユーザー、作業ディレクトリ、環境変数で手動実行します。その後、一時的なテスト用入力で終了コード、ログ、二重起動、停止、失敗通知を確認します。本番データを使う場合の影響範囲は※要確認です。

まとめ

cronへ登録する前の最終確認は、前提条件の固定、排他ロック、失敗ログと通知、安全な再実行、タイムゾーン、停止口の6点です。cronは起動係に絞り、データを守る責任は処理側にも持たせると、障害時に原因と復旧範囲を切り分けやすくなります。

一次情報の確認メモ

続き(結論と実データ)はnoteに置いています

ここでは手順のところまで書きました。実際に出た数字、うまくいかなかった条件、そのまま使える設定ファイルは、 note の記事にまとめてあります。

noteで続きを読む Xをフォローする