My rails application runs with sidekiq. The app has_many accounts.
Each account can run a ImportResourceJob
which sends the account_id
as argument to identify the correct account to work on.
I want to prevent launching concurrently many ImportResourceJobs for the same account.
Basically, I want to check before launching a new ImportResourceJob that there is not a currently enqueued/running ImportResourceJob
for that specific account_id.
I am a bit unsure on how to do that. I have seen answers suggesting using the scan method from the sidekiq api https://github.com/mperham/sidekiq/wiki/API#scan or https://github.com/mperham/sidekiq/wiki/API#workers
workers = Sidekiq::Workers.new
workers.size # => 2
workers.each do |process_id, thread_id, work|
# process_id is a unique identifier per Sidekiq process
# thread_id is a unique identifier per thread
# work is a Hash which looks like:
# { 'queue' => name, 'run_at' => timestamp, 'payload' => msg }
# run_at is an epoch Integer.
# payload is a Hash which looks like:
# { 'retry' => true,
# 'queue' => 'default',
# 'class' => 'Redacted',
# 'args' => [1, 2, 'foo'],
# 'jid' => '80b1e7e46381a20c0c567285',
# 'enqueued_at' => 1427811033.2067106 }
end
This doesnt seem to be very precise or realiable (only updating every 5 seconds). Also seems to me unscalable if you have a lot of workers.
Is it common/good practice to have a Jobs table with :
- column
account_id
= Account has_many Jobs - column
type
= class of the job (ex:ImportResourceJob
) - column
status
=enqueud
,running
,finished
,failed
to handle those kind of things ? The idea would be to create an entry in the Jobs table before launching the job and pass the job_id to the Job. Something like this :
def launches_import_resource_job
existing_running_job = Job.find_by(type: "ImportResourceJob", account_id: account_id, status: ["enqueued", "running"])
return if existing_running_job
job = Job.create(
type: "ImportResourceJob",
account_id: account_id,
status: "enqueued"
)
ImportLmsResourcesJob.perform_later(
account_id,
job.id
)
end
then in the ImportResourcesJob itself :
class ImportResourcesJob < ApplicationJob
queue_as :default
sidekiq_options retry: false
def perform(account_id, job_id)
job = Job.find(job_id)
job.update(status: "running")
Sync360Service.call(account_id)
job.update(status: "finished")
rescue Exception => e
job.update(status: "failed")
raise e
end
end
What is the accepted/good solution to solve this problem ?