This is pretty simple, I'm running a crawler that finds Cogs. Cogs can belong to one single Widget. Sometimes it finds a bunch of Cogs that all should belong to the same Widget. In the Cog model it runs:
# Find or create widget
match = Widget.where("name ILIKE (?)", name).first
match = Widget.create(name: name) unless match
The delayed jobs that run all at parallel are essentially like this:
- CREATE Cog, name: "cog1", (Widget, name: "Foo")
- CREATE Cog, name: "cog2", (Widget, name: "Foo")
- CREATE Cog, name: "cog3", (Widget, name: "Foo")
- CREATE Cog, name: "cog4", (Widget, name: "Foo")
This is unavoidable, but I thought would be handled by the match code above. I also have this in the Widget model:
validates :name, presence: true, uniqueness: true
Unfortunately, with 4 DelayedJob workers running across 4 cores these jobs run at exactly the same time, causing multiple Widgets to be created despite both of the checks. How do I prevent the race conditions when creating Widgets so I don't get duplicates?
Because the unique check and the insert are performed separately, there is a race condition. You need an index to enforce the uniqueness constraint.
The docs mention this: http://guides.rubyonrails.org/active_record_validations.html#uniqueness