Reputation: 639
When a user is created, I am trying to add 1 to the count if a record exists, or create a record with count: 1
if not.
I have something like this:
class User < ApplicationRecord
after_commit :record, on: :create
def record
SomeWorker.perform_async(id)
end
end
After a user is created, SomeWorker
will fire in sidekiq, and will call something like:
class SomeWorker
include Sidekiq::Worker
def perform(id)
Record.where(some_conditions).first_or_create()
end
end
It works like charm when I am not using sidekiq and just call it right after a user is created. However, when I seed data that creates 10 users, I expect to create 1 record with count: 10
, but it creates 10 records with count: 1
.
Any ideas what is happening and how I can fix it?
Upvotes: 1
Views: 852
Reputation: 13014
It's the classic race condition. Please note that "Sidekiq is multithreaded so your Workers must be thread-safe.". All your workers are running in parallel and none of them initially finds a matching record in where
and ends up creating the new record with count 1.
I will advice to instead create/find the record beforehand and send it's id for incrementing the count
value. Though, I'm not sure why you are using Sidekiq to offload a basic UPDATE
operation.
class User < ApplicationRecord
after_commit :record, on: :create
def record
record = Record.create_with(count: 0).find_by(some_conditions)
SomeWorker.perform_async(record.id)
end
end
class SomeWorker
include Sidekiq::Worker
def perform(id)
Record.find(id).increment!(:count)
end
end
Also, please note that increment!
is not suitable for concurrency either, but it should work in your case.
a = Record.first #<Record id: 1, count: 1>
b = Record.first #<Record id: 1, count: 1>
a.increment!(:count) #<Record id: 1, count: 2>
b.increment!(:count) #<Record id: 1, count: 2> ## SHOULD BE 3
b.reload #<Record id: 1, count: 3> It's actually three, but object was stale on update.
SQL fires the query below which bumps the value from the last set value.
UPDATE `records` SET `count` = COALESCE(`parent_id`, 0) + 1 WHERE `records`.`id` = 1
Upvotes: 1