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

Rails のバッチ処理などで、複数プロセスをまたいで同じ処理を並行して走らせたくないとき、単一サーバであればファイルのロックを使うのが簡単です。

ソース

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

# 同一処理を並行で走らせない様にするモジュール
class BatchLockedException < Exception; end

module BatchLock
  # 処理を排他ロックして走らせる
  def self.run(batch_name)
    FileUtils.mkdir_p("#{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 BatchLockedException.new
      end
    end
  end

  # 処理が走っているか確認する
  def self.running?(batch_name)
    FileUtils.mkdir_p("#{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.each(&:go_to_park)
  end
rescue BatchLockedException
  Rails.logger.error "既にバッチが走っています"
end

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

puts BatchLock.running?(:go_to_park) ? '実行中' : '実行中ではありません'

コメントを残す

メールアドレスが公開されることはありません。