Railsで同じ処理が並行して走らないようにする

 複数プロセスを立ち上げていても、script/runner 等で叩くときも並行して同じ処理を走らせないようにするとき、ファイルのロックを使うのが簡単です。

ソース

#{RAILS_ROOT}/lib/batch_lock.rb という名前で下記を保存する。

# 同一処理を並行で走らせない様にするモジュール
class BatchLockException < Exception; end
module BatchLock
  # 処理を排他ロックして走らせる
  def self.run(batch_name)
    FileUtils.mkdir(RAILS_ROOT + '/tmp/batch_lock') unless File.exists?(RAILS_ROOT + '/tmp/batch_lock')
    # batch_name をスコープとして排他処理される。
    File.open("#{RAILS_ROOT}/tmp/batch_lock/#{batch_name}", 'w') do |f|
      if f.flock(File::LOCK_EX | File::LOCK_NB)
        yield
      else
        raise BatchLockException.new
      end
    end
  end
 
  # 処理が走っているか確認する
  def self.running?(batch_name)
    FileUtils.mkdir(RAILS_ROOT + '/tmp/batch_lock') unless File.exists?(RAILS_ROOT + '/tmp/batch_lock')
    # ロック用のファイル名は、バッチごとにユニークにする。
    File.open("#{RAILS_ROOT}/tmp/batch_lock/#{batch_name}", 'w') do |f|
      ! f.flock(File::LOCK_EX | File::LOCK_NB)
    end
  end
end

使い方

同時に走らせたくない処理をブロックで実行する。

begin
  BatchLock.run(:go_to_park) do
    # ユーザがみんな公園に行く
    User.find(all).each(&:go_to_park)
  end
rescue BatchLockException
  logger.error "既にバッチが走っていた…。"
end

今バッチが走ってるか確認する。

p BatchLock.running?(:go_to_park) ? 'バッチ進行中' : 'バッチは走ってない'
Railsで同じ処理が並行して走らないようにする