出力を入力へ

プログラミングに関する自分が考えた事を中心にまとめます

wheneverのカスタムjob_typeで月末バッチを実現する

cronジョブをRubyで書くためのgem wheneverで 月末バッチを実現するためのカスタムスクリプトを実装したい. このとき,wheneverのカスタムjob_typeで実現したのでそのまとめ.

月末バッチの実装

cronで月末バッチをスケジュールする場合,月末の判定が面倒. 月末となる日は30日や31日,もしくは28日や29日(2月のうるう年)のような条件があるので, 日付を決め打ちにすることができない. cron書式とは別に月末判定を実施して処理する仕組みが必要である.

よくある実装としてはcronで呼び出されるスクリプトの冒頭で, 翌日が1日か判定して1日でなければ処理を終了する方法である. 例えば以下のような方法で翌日の日付を判定することで月末かを確認できる.

if [ `date - d tomorrow "+%d"` == "01" ]; then
  run_batch.sh
fi

cronだったら一行で実現したいので以下みたいになる

0 0 * * * "[ `date - d tomorrow \"+\%d\"` == \"01\" ] && run_batch.sh"

cronにおける注意点として,ダブルクオテーションに対するエスケープはもちろん, % に対するエスケープ処理が必要な点にある. 詳細はman 5 crontab を参照.

wheneverにおける実装

ではRubyでcron書式を実装する wheneverではどのように実装するか. wheneverでは以下のように記載する.

every '0 0 27-31 * *' do
  rake "app_server:task"
end

これを bundle exec whenever でcron書式に変換すると以下のように変換される.

0 0 27-31 * * /bin/bash -l -c 'cd /home/thaim/work && RAILS_ENV=production bundle exec rake app_server:task --silent'

これは wheneverの仕組みによるもので, パスの変更やRAILS_ENVの指定などをユーザが考慮する必要がなくなる. 一方で,今回のような月末判定ロジックを組込むにはwheneverの仕組みと重複するので難しい.

対策として,独自の変換ロジックとして job_typeをカスタマイズする方法である. 上記 wheneverのrakeはwheneverが実装するjob_typeによる変換方式が適用されたものである. これを月末判定ありのjob_typeを定義してあげればよい. 例えば以下のような rake_lastday job_typeを定義する.

job_type :rake_lastday, "[ `date -d tomorrow \"+%d\"` == \"01\" ] && cd :path && :environment_variable=:environment bundle exec rake :task --silent :output"

これを利用すると 月末判定付きのcronが生成されるようになる.

every '0 0 27-31 * *' do
  rake_lastday "app_server:task"
end

という記述が以下のように変換される

0 0 27-31 * * /bin/bash -l -c ''[ `date -d tomorrow "+\%d"` == "01" ] && cd /home/thaim/work && RAILS_ENV=production bundle exec rake app_server:task --silent'

補足: 拡張書式としてのL

ちなみに,cron書式にて日付に 'L’ を使えば月末日を自動で判定してくれるよ, みたいな記事がStackOverflowとか英語Wikipediaに記載がある. ただし,これは一般的なcronでは実装されていない,独自方言であり基本的には使えない.

例えば JavaのジョブスケジューラライブラリQuartzなんかには 拡張書式として月末日のLがサポートされている. ただし,一般的なcronでは扱われているような書式ではないので この書式がサポートされていることを期待しない方がよさそう.