2016-09-22

Clockworkを使ってHerokuでバッチ処理

Herokuでプログラムをスケジュール起動する場合は、Heroku Schedulerが主に使われると思いますが、Heroku Scheduler自体はベストエフォート型のサービスだったり、細かい時間の制御ができなかったり色々と融通が利かない部分があります。ということで今回はClockwork + Resqueでバッチ処理を作ってみます。

大枠としてはClockworkがcronの役割で、起動時刻になったらResqueでエンキューし、Workerがデキューして処理を実行するという流れです。こうすることで、バッチ実行自体をスケールさせることが出来る上にClockworkの処理は最小限になり、I/O待ち等が発生しないため、安定して処理を実行できます。Clockworkのようなスケジュール用のプロセスは、マルチプロセスだと重複実行の可能性があり、重複実行を防ぐためのロック機構も複雑になるためシングルプロセスで実行することを推奨しています。また、Clockwork自体にバッチ処理を実行させることも出来ますが、並列処理が実行できない可能性があることから推奨されていません。threadオプションを使えば、非同期に並列処理ができますが、スケーラビリティを考えるとWorker方式の非同期処理の方が良いです。

使い方

Gemfileに以下を記述してbundle installすればOK

gem '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側にはちゃんとログを仕込んでおいて、実行ログを監視するのは必須ですかね。

参考URL

このエントリーをはてなブックマークに追加