rubyruby-on-rails-3mongoid3

Strong consistency for a Rails model in Mongoid 3


I want all of my db interactions for a specific model to go through the mongo primary in my cluster, so I set the model to use strong consistency.

class Photo
  include Mongoid::Document
  with consistency: :strong

  field :number, type: Integer
  # let's say a photo number is unique in the db
  validate :unique_number
end

But this does not seem to work, because I still run into validation errors when I save two Photo photos very close together.

photo1 # db has number=1 for this object
photo1.update_attributes(number: 2)
photo2.number = 1
photo2.save! # <= this raises a validation exception

My understanding of strong consistency is that there shouldn't be a race here. It should do the write and then do the read, and since it's all off the primary there shouldn't be a conflict. What am I missing?


Solution

  • It turns out calling with(consistency: :strong) at the class level only applies it to the next query. So the class method is called when the class is loaded, setting strong consistency for the first query, but subsequent queries don't trigger the same class method leaving their persistence operations to operate with eventual consistency. From the Mongoid 3.1.7 documentation:

    Tell the next persistance [sic] operation to store in a specific collection, database or session.

    This method does not enforce the persistence options that can be passed in (like a few other methods in the class), so we can also pass in consistency: :strong.

    Hack Fix

    In order to apply this to every* persistence operation, I added it to a default_scope.

    class App
      default_scope -> { with(consistency: :strong); where({}) }
    end
    

    In this case, the default scope expects to have a Mongoid Criteria object returned, so we return a noop where clause after setting the consistency level on the in-progress persistence operation.

    * This will not be applied if the developer decides to call unscoped and strip off the default_scope.