Search code examples
activerecorddelayed-jobrace-condition

DelayedJob breaking validates unique constraints in ActiveRecord


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?


Solution

  • 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