mrt:mongo-update-until-current

Atomic Mongo updates using the Update If Current pattern


License
MIT
Install
meteor add mrt:mongo-update-until-current@=1.0.1

Documentation

mongo-update-until-current

Advanced atomic updates of Mongo documents using the Update if Current pattern, retried until the update can be successfully applied.

Updates of Mongo documents are automatically atomic when the update is entirely specified by the modifier argument of the update method. For example, if two methods execute

collection.update(id, {$inc: {count: 1}});

at the same time, or one method executes

collection.update(id, {$push: {array: "banana"}});

at the same time as another method executes

collection.update(id, {$push: {array: "carrot"}});

there's no chance that one of the updates will be lost. In the first case, count will always be incremented twice, and in the second case both “banana” and “carrot” will be added to the array.

However, suppose you have an update which is too complicated to be expressed as a Mongo modifier. You might want to first read the document, modify the document yourself, and then write the updated document to the database:

var doc = collection.findOne(id);
// prepend "apple"
doc.array.splice(0, 0, 'apple');
collection.update(id, {$set: {array: doc.array}});

The problem here is that any other update to the array that happens between the time of the findOne and the update will be lost, overwritten by this update which doesn’t take into account what has changed.

The Update if Current pattern performs the update only if the relevant part of the document hasn’t been changed. The update is thus only applied if it is valid.

The updateUntilCurrent function provided by this package wraps the Update if Current pattern in a retry loop. If the first update doesn’t succeed, it is tried again until it does.

The assumption is that most of the time there won’t be contention for the part of the document which is being updated. Occasionally, two methods will perform the update at the same time, one will succeed and the other fail, and the second will retry and then succeed the second time.

On the other hand, this isn’t the implementation to use if you do have many simultaneous updates to the same document. If that happens, you’ll want to restructure your data model so that either you are able to perform your update using just the built-in Mongo modifier expression language, or so that you don’t have one document that is “hot” and having many simultaneous updates.

The updateUntilCurrent function takes four arguments:

  • The collection to perform the atomic update in.

  • A selector which matches one document in the collection to update. This can be (and often is) a string, the id of the document.

  • An updater function, described below.

  • An optional error callback, called if there is too much contention and the update could not be applied after multiple retries. If the error callback isn’t specified, an error is thrown if the update can’t be applied.

The updater function takes one argument, the document that was read using the passed selector. It returns two values in an array: a selector to check whether the relevant part of the document hasn’t changed, and a modifier to perform the update.

As a simple example, suppose we didn’t have $inc, and wanted to increment a count.

updateUntilCurrent(collection, id, function (doc) {
  return [{count: doc.count}, {$set: {count: doc.count + 1}}];
});

The first expression is the selector that checks that the count field hasn’t changed, and so we won’t lose anything by setting it ourselves. The second expression is a modifier to set the count field to the new value.

For the array prepend example, it would look like this:

updateUntilCurrent(collection, id, function (doc) {
  var array = _.clone(doc.array);
  array.splice(0, 0, 'apples');
  return [{array: doc.array}, {$set: {array: array}}];
});

Note that we need to clone the array value so that we still have the original value to use in the selector.

Install

As of Meteor 0.6.5.1, we need a patched version of Meteor’s mongo-livedata package.

In your Meteor application directory, first create a packages directory if it doesn’t already exist:

$ mkdir packages

then fetch the patched version of the mongo-livedata package:

$ git clone https://github.com/awwx/meteor-expose-mongo-result.git -b for-0.6.5.1 packages/mongo-livedata

this will create a mongo-livedata directory in your packages directory, which will override the standard Meteor mongo-livedata package.

To install this mongo-update-until-current package and its other dependencies, you can use Meteorite as usual:

$ mrt add mongo-update-until-current

Donate

An easy and effective way to support the continued maintenance of this package (and the development of new and useful packages) is to donate through Gittip.

Gittip is a platform for sustainable crowd-funding.

Help build an ecosystem of well maintained, quality Meteor packages by joining the Gittip Meteor Community.

Hire

Need support, debugging, or development for your project? You can hire me to help out.