Herokuでプログラムをスケジュール起動する場合は、Heroku Schedulerが主に使われると思いますが、Heroku Scheduler自体はベストエフォート型のサービスだったり、細かい時間の制御ができなかったり色々と融通が利かない部分があります。ということで今回はClockwork + Resqueでバッチ処理を作ってみます。
大枠としてはClockworkがcronの役割で、起動時刻になったらResqueでエンキューし、Workerがデキューして処理を実行するという流れです。こうすることで、バッチ実行自体をスケールさせることが出来る上にClockworkの処理は最小限になり、I/O待ち等が発生しないため、安定して処理を実行できます。Clockworkのようなスケジュール用のプロセスは、マルチプロセスだと重複実行の可能性があり、重複実行を防ぐためのロック機構も複雑になるためシングルプロセスで実行することを推奨しています。また、Clockwork自体にバッチ処理を実行させることも出来ますが、並列処理が実行できない可能性があることから推奨されていません。threadオプションを使えば、非同期に並列処理ができますが、スケーラビリティを考えるとWorker方式の非同期処理の方が良いです。
使い方
Gemfileに以下を記述してbundle installすればOKgem 'clockwork'
clockworkのスクリプトはこんな感じで書く
require 'clockwork'
include Clockwork
module Clockwork
handler do |job|
case job
when 'aggregate.job'
Resque.enqueue(Aggregate, {hoge: 'fuga'})
when 'clean_templog.job'
Resque.enqueue(CleanTemporaryLog, nil)
end
end
every(1.hour, 'aggregate.job')
every(1.day, 'clean_templog.job')
end
workerはこんな感じで記載
class CleanTemporaryLog
@queue = :clean_templog
def self.perform message
LOGGER.info "CleanTemporaryLog Batch: Start"
begin
TemporaryLog.destroy_all("CURRENT_TIMESTAMP - INTERVAL '1 day' > createddate")
rescue => e
LOGGER.error "CleanTemporaryLog Error: #{e}"
end
LOGGER.info "CleanTemporaryLog Batch: End"
end
end
Procfileはこのように記述すればOK
resque: bundle exec rake resque:work TERM_CHILD=1 QUEUES=*
clockwork: bundle exec clockwork clock_work.rb
HerokuでClockworkを使う際の注意
HerokuはデフォルトのタイムゾーンがUTCになります。$ heroku run date -a {APP_NAME}
Running date on ⬢ {APP_NAME}... up, run.2799
Mon Sep 19 03:35:38 UTC 2016
JSTでClockworkの起動時刻を制御したい場合は、以下のように環境変数を設定すればOK
$ heroku config:set TZ=Asia/Tokyo -a {APP_NAME}
そうするとタイムゾーンが変わります。
$ heroku run date -a {APP_NAME}
Running date on ⬢ {APP_NAME}... up, run.9790
Mon Sep 19 12:36:15 JST 2016
あと、Herokuの仕様の「1日1回再起動」というのもバッチ処理においては気になるところです。Clockwork側、Worker側にはちゃんとログを仕込んでおいて、実行ログを監視するのは必須ですかね。