第129回 実務で回すワークフローのスケジューリングとオーケストレーション — Pythonで作るcronからPrefectへの現実的移行手順

定期処理を任されて「とりあえずcronで回している」が運用中に不具合を起こし、慌てて対応した経験はありませんか?重複実行でDBが壊れた、想定より処理時間が延びてスケジュールがずれた、失敗通知が来ずに気づかなかった──こうした現場のつまずきに寄り添い、まずは安定した軽量運用から始め、必要に応じてオーケストレーターへ移行するための現実的な手順とチェックリストをまとめます。

なぜスケジューリング/オーケストレーションが必要か(現場で起きる典型的な失敗)

現場でよく見る失敗を事例で整理します。まずは原因を把握することで、どこまで投資すべきか判断できます。

問題 典型例 短期対処 長期対策
重複実行 前回ジョブが終わらないうちに次のcronが走り、データ不整合 ロックファイル/flockで二重起動防止 オーケストレーターで実行制御と依存管理
スキップ(見落とし) システム再起動でタイマーが動かなくなる、ログが回っていない 監視と死活チェック、ログ収集の確立 メトリクスとAlertでSLA運用
リソース枯渇 同時実行が多くなりDB接続枯渇やCPU爆発 並列数の制限(シリアライズ) スケジューラでキューイングとスケール設計
依存関係の崩壊 上流ジョブの失敗を下流が検知せず進行 簡易チェックと通知 タスク依存を明示できるオーケストレーター導入

選定基準(実務目線)

どの方式が合うかは、次の観点で判断します。

  • ジョブ頻度:分単位か日次か
  • データ依存:ジョブ間に依存関係があるか
  • 運用体制:1人で見るのかチームで運用するか
  • コスト:運用負荷やクラウド費用を含めた総コスト
要件 軽量(cron/systemd) オーケストレーター(Prefect/Airflow)
向いているケース 単純で少数の定期ジョブ、運用リソースが少ない 依存関係が複雑、再試行や観測性が重要な場合
導入コスト 中〜高(運用体制と監視が必要)
運用負荷 低(だが細部を自分で作る必要あり) 中(初期設定は大きいが機能は豊富)

軽量運用の実践(cron / systemd タイマー / コンテナ内cron)

cronでPythonスクリプトをvenvで実行する例

目的 例(crontab)
毎朝3時にvenv経由で実行(ログ保存) 0 3 * * * /bin/bash -lc ‘source /srv/myapp/venv/bin/activate && python /srv/myapp/scripts/daily_job.py’ >> /var/log/myapp/daily_job.log 2>&1

二重起動防止(ロックファイルの単純実装)

説明 スニペット(bash)
ジョブ開始時に排他ロックを取り、処理終了で解放します。簡易実装はロックファイルとPID確認。 LOCK=/tmp/daily_job.lock
if [ -f “$LOCK” ]; then
echo “Already running”
exit 0
fi
trap ‘rm -f “$LOCK”‘ EXIT
echo $$ > “$LOCK”
# Python実行
source /srv/myapp/venv/bin/activate
python /srv/myapp/scripts/daily_job.py

注意:flockコマンド(/usr/bin/flock)を使うとより堅牢です。必要ならinotifyやsystemdの機能で補強します。

ログ管理とローテート

目的 例(logrotate 設定)
ログを肥大化させない /var/log/myapp/*.log {
daily
rotate 7
compress
missingok
notifempty
create 0640 myuser mygroup
}

失敗時のリトライ設計(cron段階)

  • 短期的:cronを短い間隔で再実行するwrapperを作る(ただし並列注意)
  • 推奨:ジョブ内部で指数バックオフを実装し、致命的なエラーでのみ終了コードを返す
  • 通知:失敗時にSlack/メール/Webhookで通知する(必須)

Pythonベースのオーケストレーター比較(実務目線)

項目 Prefect Airflow Dagster
導入コスト 低〜中(Prefect Cloud を使う場合は簡単) 中〜高(Infraが必要) 中(設計の自由度は高い)
運用負荷 低め(エージェントモデルでスケール) 高め(Scheduler/Worker/DBの管理) 中(観測性は良いが慣れが必要)
UI 分かりやすい(Prefect UI) 強力だが設定が複雑 開発者向けで直感的
依存関係の記述 コードベースで柔軟(タスク・フロー) DAGで明示的に記述 タイプセーフな定義が可能
スケール感 小〜中規模チームに適合 中〜大規模向け 中規模での開発効率が高い
最短導入パス(実務) Prefect Core + Prefect Cloud(またはLocal Agent)で1日〜数日で開始可能 Docker Compose でまずは試し、本番はKubernetes等で構築 ローカルでの開発→CIで検証→共有レポジトリの流れが推奨

cron運用からオーケストレーターへ段階的に移す手順(ステップとテスト)

重要なポイントは「小さく始めて、確実に検証しながら移す」ことです。以下は現場で再現可能な順序です。

  1. タスク化:既存スクリプトを関数単位に分け、外部依存を引数化する(冪等化の下準備)
  2. 冪等化:同じ入力で何度実行しても状態が壊れないことを担保するテストを作る
  3. チェックポイント化:途中結果を保存する(ファイル/DB)ことで途中再開を容易にする
  4. コンテナ化:Dockerで同一環境を作る。ローカルでコンテナ実行が通ることを確認する
  5. CIによる検証:ユニットテスト/統合テスト/コンテナビルドをCIで自動化する
  6. スケジューラへ移行:まずは非本番(ステージング)でPrefect等に1本移す。問題なければ徐々に増やす

各ステップでのテスト手順とロールバック例:

  • タスク化後:ユニットテストが通らなければロールバックして細分化を見直す
  • コンテナ化後:ローカルのコンテナで整合性が取れない場合は環境差分を洗い出す
  • スケジューラで問題が出た場合:該当ジョブのみcronへ一時復帰(ロールバック)して原因調査

運用設計チェックリスト(ワークシート形式)

項目 チェック内容 実務でのメモ
監視 実行数/成功率/実行時間のメトリクスを収集 Prometheus + Grafana や Prefectのメトリクスを利用
ログ 集中ログ(ELK / Loki 等)へ送る。ログレベルとフォーマットを定義 構造化ログ(JSON)が望ましい
アラート 失敗率閾値・実行遅延・再試行上限到達をアラート化 Slack/メールに通知し、SRE担当者のオンコール手順を用意
SLA ジョブごとの許容遅延と復旧時間を定義 ビジネス影響に基づく優先度付け
リトライ/バックオフ 最大試行回数、バックオフ戦略(線形/指数)の決定 外部API呼び出し等は指数バックオフ推奨
並列制御 同時実行数の上限とキュー戦略 DB接続数やAPIレート制限を踏まえて決定
コスト管理 呼び出し回数・クラウドリソースを予算管理 予期しない実行増に備えアラート設定

実践例:Prefectでの最小構成Flow(ローカル実行→クラウドに移す際の注意)

目的 サンプル(簡略化)
タスク定義とFlow from prefect import flow, task

@task
def extract():
return ‘data’

@task
def transform(d):
return d.upper()

@task
def load(d):
print(‘save’, d)

@flow
def my_flow():
d = extract()
t = transform(d)
load(t)

if __name__ == ‘__main__’:
my_flow()

スケジュールと保存 # Prefectではスケジュールを登録し、結果はCloud/Artifactに保存可能
# ローカルで動作確認後、Prefect CloudやAgent経由で実行を移行

ローカル→クラウド移行時の注意点:環境変数やシークレット管理、ストレージのパス、ネットワーク制限に注意。まずステージングで1本動かしてから本番へ。

cronからPrefectへ1本移行する具体的手順(小さな成功体験)

  1. 既存cronジョブを1つ選定(低リスクでビジネス影響が小さいもの)
  2. スクリプトをタスク化し、冪等化テストを作る(ユニットテスト)
  3. コンテナ化してローカルで実行確認
  4. PrefectでFlowを作成しローカルで実行(ログと結果を確認)
  5. ステージングにジョブを登録、1週間ほど監視して問題がなければ本番へ移行
  6. 移行後も旧cronは一定期間残し、問題がなければ削除(ロールバック余地を残す)

テストとCI/デプロイ戦略(実務的)

  • ユニットテスト:タスク単位での入力→出力を固定して検証
  • 統合テスト:Docker Compose やローカル Prefect エージェントでの結合確認
  • ステージングスケジュール:本番とは別の時間帯/ネームスペースで実行
  • CI自動デプロイ:マージ時にフロー定義をLint・テスト・イメージビルドし、ステージングへデプロイ
  • ロールアウト/ロールバック:新バージョンを段階的に有効化し、問題があれば旧バージョンへ切り戻し

次の一歩と関連記事

まずは「週次ジョブ移行チャレンジ」として、影響の小さい週次ジョブを1本選んで本記事の手順で移行してみてください。移行で得た知見を元に、運用チェックリストを整備すると次のステップが楽になります。

  • 関連:【第122回】大規模CSVの扱い
  • 関連:【第123回】可観測性
  • 関連:【第128回】差分同期

まとめ

小さな定期処理はまず軽量なcronやsystemdタイマーで安定運用を確立し、ロック・ログ・リトライ・監視を整備することが現場では最も効果的です。依存関係や可観測性が必要になれば、PrefectやAirflowのようなオーケストレーターへ段階的に移行します。大事なのは一度に全移行を目指さず、1本ずつ確実に移して学習を繰り返すことです。本記事のチェックリストと手順を参考に、まずは1本を移行して「小さな成功体験」を積んでください。

シリーズ:AIとPythonの実務 — Manage AI(https://manageai.online)