Atomic Operations

MongoDB supports atomic operations on single documents.  MongoDB does not support traditional locking and complex transactions for a number of reasons:

  • First, in sharded environments, distributed locks could be expensive and slow.  Mongo DB's goal is to be lightweight and fast.
  • We dislike the concept of deadlocks.  We want the system to be simple and predictable without these sort of surprises.
  • We want Mongo DB to work well for realtime problems.  If an operation may execute which locks large amounts of data, it might stop some small light queries for an extended period of time.  (We don't claim Mongo DB is perfect yet in regards to being "real-time", but we certainly think locking would make it even harder.)

MongoDB does support several methods of manipulating single documents atomically, which are detailed below.

Modifier operations

The Mongo DB update command supports several modifiers, all of which atomically update an element in a document.  They include:

  • $set - set a particular value
  • $unset - set a particular value (since 1.3.0)
  • $inc - increment a particular value by a certain amount
  • $push - append a value to an array
  • $pushAll - append several values to an array
  • $pull - remove a value(s) from an existing array
  • $pullAll - remove several value(s) from an existing array

These modifiers are convenient ways to perform certain operations atomically.

"Update if Current"

Another strategy for atomic updates is "Update if Current".  This is what an OS person would call Compare and Swap.  For this we

  1. Fetch the object.
  2. Modify the object locally.
  3. Send an update request that says "update the object to this new value if it still matches its old value".

Should the operation fail, we might then want to try again from step 1.

For example, suppose we wish to fetch one object from inventory.  We want to see that an object is available, and if it is, deduct it from the inventory.  The following code demonstrates this using mongo shell syntax (similar functions may be done in any language):

> t=db.inventory
> s = t.findOne({sku:'abc'})
{"_id" : "49df4d3c9664d32c73ea865a" , "sku" : "abc" , "qty" : 30}
> qty_old = s.qty;
> --s.qty;
> t.update({_id:s._id, qty:qty_old}, s); db.$cmd.findOne({getlasterror:1});
{"err" :  , "updatedExisting" : true , "n" : 1 , "ok" : 1} // it worked

For the above example, we likely don't care the exact sku quantity as long as it is as least as great as the number to deduct.  Thus the following code is better, although less general -- we can get away with this as we are using a predefined modifier operation ($inc).  For more general updates, the "update if current" approach shown above is recommended.

> t.update({sku:"abc",qty:{$gt:0}}, { $inc : { qty : -1 } } ) ; db.$cmd.findOne({getlasterror:1})
{"err" : , "updatedExisting" : true , "n" : 1 , "ok" : 1} // it worked
> t.update({sku:"abcz",qty:{$gt:0}}, { $inc : { qty : -1 } } ) ; db.$cmd.findOne({getlasterror:1})
{"err" : , "updatedExisting" : false , "n" : 0 , "ok" : 1} // did not work

The ABA Nuance

In the first of the examples above, we basically did "update object if qty is unchanged".  However, what if since our read, sku had been modified?  We would then overwrite that change and lose it!

There are several ways to avoid this problem ; it's mainly just a matter of being aware of the nuance.

  1. Use the entire object in the update's query expression, instead of just the _id and qty field.
  2. Use $set to set the field we care about.  If other fields have changed, they won't be effected then.
  3. Put a version variable in the object, and increment it on each update.
  4. When possible, use a $ operator instead of an update-if-current sequence of operations.

"Insert if Not Present"

Another optimistic concurrency scenario involves inserting a value when not already there. When we have a unique index constraint for the criteria, we can do this. The following example shows how to insert monotonically increasing _id values into a collection using optimistic concurrency:

function insertObject(o) {
    x = db.myCollection;
    while( 1 ) {
        // determine next _id value to try
        var c = x.find({},{_id:1}).sort({_id:-1}).limit(1);
        var i = c.hasNext() ? c.next()._id + 1 : 1;
        o._id = i;
        x.insert(o);
        var err = db.getLastErrorObj();
        if( err && err.code ) {
            if( err.code == 11000 /* dup key */ )
                continue;
            else
                print("unexpected error inserting data: " + tojson(err));
        }
        break;
    }
}

Find and Modify (or Remove)

See the findandmodify Command documentation for more information.

Applying to Multiple Objects Atomically

You can use multi-update to apply the same modifier to each object. This will be pseudo-atomic by default. Its one operation, but occasonaly someone else can get it. To make it full atomic you can use $atomic

non atomic

db.foo.update( { x : 1 } , { $inc : { y : 1 } } , false , true );

atomic

db.foo.update( { x : 1 , $atomic : 1 } , { $inc : { y : 1 } } , false , true );


Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

PLEASE POST QUESTIONS IN THE USER GROUPS FORUM. Post non-question comments and helpful hints here.

blog comments powered by Disqus