👨‍💼

RailsのActiveJobで特定のジョブを連続実行させない方法

2022/02/06に公開

前提

  • ActiveJob を使います
    • アダプタは sidekiq
  • バージョンは Ruby3.1, Rails7.0.1 です

やりたいこと

  • ジョブを登録する時に○分待つように指定する
  • ジョブ登録時に同じジョブが存在したら、前に登録したジョブは実行しない

方法

1.Sidekiq APIを使って実装

  • Sidekiq APIを使って、ジョブ登録前にスケジュールに入っているジョブを取得し、同じジョブの場合は削除します。
  • Sidekiq::ScheduledSet.new でスケジュールされているジョブを全て取得するので、大規模サービスなどジョブ数が多い場合はパフォーマンスが懸念されます。
class SampleJob < ApplicationJob
  queue_as :default

  def perform
    puts 'hello'
  end
end
require 'sidekiq/api'

class Sample
  def run
    delete_scheduled_job

    SampleJob.set(wait: 5.minutes).perform_later
  end

  private

  def delete_scheduled_job
    jobs = Sidekiq::ScheduledSet.new
    jobs.select { |job|
      job.args[0]["job_class"] == "SampleJob" && job.args[0]["queue_name"] == "default"
    }.map(&:delete)
  end
end

2.Rails.cacheを使って実装

  • Rails.cacheを使います。ジョブ登録後にjob_idをキャッシュして、ジョブが実行される時にキャッシュされているjob_idと自身のjob_idが一致するか確認し、一致した場合はジョブを実行します
    • キャッシュが揮発する可能性を考慮して、キャッシュが見つからない場合もジョブを実行します
  • サンプルではredisを使用しています
  • 1の方法はsidekiqの機能を使いましたが、こちらはアダプタには依存しません
require 'sidekiq/api'

class Sample
  def run
    job = SampleJob.set(wait: 5.minutes).perform_later
    Rails.cache.write('cached_job_id', job.job_id)
  end
end
class SampleJob < ApplicationJob
  queue_as :default

  def perform
    cached_job_id = Rails.cache.write('cached_job_id')

    return if cached_job_id.present? && cached_job_id != self.job_id

    puts 'hello'

    Rails.cache.delete('cached_job_id') if cached_job_id.present?
  end
end

Discussion