Mar 22

A Pattern for handling concurrent edits to a MongoDB Document in Node.js

Lately I’ve been working on converting my space strategy game (Astriarch) to multiplayer using Node.js, Mongodb, and Mongoose.

One problem I faced while making the game multiplayer is handling concurrent edits to the mongo game documents. Here is the scenerio: two players decide to send ships (edit the document) at the same time. If we are lucky, there are no problems, however there is a chance that both players get the latest version of the document one after the other (before either one of them has time to save the document), then concurrently edit and save the document. What this means is that the last person to save the document wins, the first person’s edits are lost.

This problem can be handled in a couple of ways:

  1. Change the model so that anytime there is the possibility of concurrent edits to the document, a new collection (or model) is created for each user’s edits, then at some point in the future those edits are merged into the document when it is guaranteed that only one process is modifying the document.
  2. Use a nonce field in your document to protect against concurrent edits as described here.

The problem with option #1 is that it is more difficult to implement, it means making your model and processing more complicated, and you still need to guarantee that at some point you can safely merge in the changes to your document by ensuring only one thread/process is modifying the document at that time.

The key to option #2 is that a user will only be able to modify the document if the nonce field has not been changed since that user last fetched the document, this is done by providing the nonce within the update command.

models.PostModel.update({"_id":postId, "nonce":doc.nonce}, data, callback);

Depending on your application, if you are not able to update the document because someone else has updated it, you may simply want to return an error for the user with the latest document so that the user may choose to perform the edits and try to save again.

For my application, I wanted to make a generic function for handling concurrent edits to a mongo document, that way I could leverage this function in each context where edits to the document had to be made.

Here is a Github Gist of the “savePostByIdWithConcurrencyProtection” function and a sample of how it is used:

Notice how the savePostByIdWithConcurrencyProtection takes a transform function that it will execute to perform the actual work of the edit on the document object, and then handles the rest of the logic of performing the update while ensuring the nonce is used in the update as well as recursively retrying until some limit is reached.

You can see an example of how to use this function in the AppendPostText method where the transform function simply appends the text passed into the function to the document.

This pattern allows reuse of the same complex concurrent edit protection and retry logic in many different areas of my application.