2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Rails] Active Jobをロックしたい

Last updated at Posted at 2023-02-10

はじめに

Active Job にてRailsバックグラウンドジョブを実行する際の、排他制御(ロック)に関するメモ

目的: ジョブのキューイング自体をロックしたい
(ジョブの処理をロックするだけではなく、そもそもEnqueしない)

Active Job自体の使い方は↑こちらの記事を大いに参考にしています。
(ジョブ内での処理のロック方法は、こちらにも記載されています)

環境

Ruby on Rails (最近6.0系にアップデート), MySQL, Resque

  • scheduler が各ジョブを時間ごとに管理し
  • Redis がジョブのキューを保持し
  • Resque がキューをもとにジョブを管理・実行

やりたいこと

そもそも Active Job でロックを導入する理由はなんでしょうか?

それは、同じジョブがEnqueされすぎて詰まるのを避けるためです。

例えば、処理に30分かかるジョブがあったとします。にもかかわらず、
スケジューラーで15分ごとに非同期実行しようとしたらどうなるでしょうか。

schedule.rb
every 15.minutes do
  HogeHogeJob.peform_async; # 実際には処理に30分かかる
end

Redisは何も考えず、スケジューラーの伝えるままにジョブをキューに入れまくります。
結果、1つ目の HogeHogeJob が終わらないうちに2つ目が実行。。
どんどん積み上がり、最悪DBもデッドロックされ、処理が遅くなっていきます。

「いや、ちょうど良い時間を設定すればイイだけじゃん」と思われるかもしれませんが、
処理時間が読みきれないジョブなどを考えると、対策しておきたいですね。

ジョブの処理をロック(with_advisory_lock)

こちらにも書かれている方法。処理をロックするのであれば、こちらで十分かと思います。
今回はwith_advisory_lock Gemを使います。

Gemを追加した上で、ジョブの perform メソッドの処理をwith_advisory_lockでロックします。

HogeHogeJob.rb
class HogeHogeJob < ApplicationJob
  queue_as :default

  def perform
    ActiveRecord::Base.with_advisory_lock("#なんかしらのロック名") do
      # 実際の処理
      # 〜〜〜
    end
  end
end

とすると、#なんかしらのロック名 で処理がロックされ、それが終わるまで
次の #なんかしらのロック名 は実行されません。

with_advisory_lock はRails 6系もサポートしているのが嬉しい

次のジョブをキャンセルする

ちなみに with_advisory_lock は、ロック中にきた次のジョブは実行待ちとなり、キャンセルはされません。
次のジョブをドロップしたい場合は Redlock なんかを使うと良いです

ジョブのキューイングをロックしたい

今回やりたいことはこちら。
上記の処理をロックする方法では、そもそも HogeHogeJob がキューイングされることはロックされません。

  • scheduler
  • → Redisにキュー
  • → Resqueが実行(ここで初めてロック

もし複数のジョブをカツカツで回している場合などでも、キューイング自体は止められません。

activejob-locking

そこで今回は activejob-locking というGemを使って、ジョブのキューイング自体をロックします。

activejob-locking もRails6系をサポートしてくれてます。

使い方は簡単。Gemを追加し、

HogeHogeJob.rb
class HogeHogeJob < ApplicationJob
  include ActiveJob::Locking::Serialized
  queue_as :default

  def lock_key
    self.class.name
  end

  def perform
    # 実際の処理
    # 〜〜〜
  end
end

とすると、
activejob-lockingがクラス名でロック管理し、同じジョブがキューイングされることを防いでくれます。
この場合、1つ目のジョブが終わるのを待って、2つ目のジョブがキューされるようになります。

次のジョブをキャンセルする

上記の

  include ActiveJob::Locking::Serialized

では、2つ目のジョブは1つ目が終わるのを待ち、その後実行されます。

もし、ロック時には他のジョブがキャンセルされるようにしたい場合は、

  include ActiveJob::Locking::Unique

とすれば2つ目はエンキューされた後、1秒後にドロップされるようになります。
ちなみに1秒という時間は、オプションの :lock_acquire_time にて変更できます。

その他参考

初学者なりに書いてますので、表現のおかしい箇所などあればぜひご助言ください🙇

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?