Search code examples
ruby-on-railsrace-condition

How does unique index solve the race condition in validates_uniqueness_of in RoR?


I know there is a problem with

validates_uniqueness_of :attribute 

in RoR because of the race condition in the rare case that there are two requests, one immediately following the other.

The solution I've read was to put a separate unique index for each model requests, which would raise an error in the case of the race condition.

I don't get how that works. If Person A makes a request, then Person B, they are getting assigned the same index? How does that work?


Solution

  • The race condition occurs because rails can't lock down the database and often runs multiple threads at the same time.

    To understand how the race-condition occurs, think about what is needed for a uniqueness-check, and I'll give an example of two threads:

    Lets suppose Person A and B want to save a new BlogPost with attribute name which must be unique.

    1. Person A hits the save button
    2. Person B hits the save button
    3. Rails starts thread A that goes and queries the database to see if there are any BlogPosts with name "My BlogPost"
    4. Rails starts thread B that goes and queries the database to see if there are any BlogPosts with name "My BlogPost"
    5. Thread A returns "nope, no blogposts with that name, all clear to save"
    6. Thread B returns "nope, no blogposts with that name, all clear to save"
    7. Thread A saves the blogpost for person A
    8. So does Thread B

    ...and now there's two blogposts with the same name.

    There's nothing stopping this from occurring, this is because the "lookup[" and "save" actions are two separate things.. and thus can occur in the manner described above.

    However... when you put a unique index on the database... what happens is this:

    1. Person A hits the save button
    2. Person B hits the save button
    3. Rails starts thread A that goes and queries the database to see if there are any BlogPosts with name "My BlogPost"
    4. Rails starts thread B that goes and queries the database to see if there are any BlogPosts with name "My BlogPost"
    5. Thread A returns "nope, no blogposts with that name, all clear to save"
    6. Thread B returns "nope, no blogposts with that name, all clear to save"
    7. Thread A saves the blogpost for person A
    8. Thread B tries to save the blogpost, but the database says "NOPE! I failed a uniqueness constraint"

    Result: only one BlogPost with the name.

    Now - to what you asked... and what I'm assuming is the mistaken understanding... and index is not an id. Each record does not get the same index. No record gets an index.

    You can kinda pretend that an index is a lookup-table of all the values that are already set for this column.

    What happens with a non-unique index is that you have a list of all the values... and the list of which records have that value. eg:

    Widgets: colour: blue ids: 1,3,7 green ids: 2,4 red ids: 5,6

    (totally made up example, nothing like reality)

    When the index has a uniqueness constraint, it just has the same list, but only allows the db to store one id per value and if you try to store another one... it raises an exception