Versioning in Mongoose

Today I encountered a interesting piece of the Mongoose internals added in v3. In a nutshell, Mongoose uses versioning to prevent you from accidentally updating the wrong element when editing a member of an Array.

As Aaron Heckmann explains:

To see how this could be problematic we need to take a closer look at the underlying operation used to update the comment. When post.save() is executed, an update is issued to MongoDB that looks like the following:

posts.update({ _id: postId } , { $set: { 'comments.3.body': updatedText }})

Notice comments.3.body, this is called positional notation. This tells MongoDB to set the body of the comment in the comments array at index position 3 to the updated text.

If, for instance, while you were editing comments.3 in the example above, a new comment was added, then comments.3 would not be the correct comment, and your update would likely be successful, but not in the way you intended.

Mongoose’s solution to this problem is to add a version number the document that is incremented every time the array changes. If you try to update an array element using positional notation with the wrong version key, you’ll get a nice VersionError. The text of the error is “No Matching Document Found”, which isn’t very helpful, but the ability to test for instanceof mongooose.Error.VersionError is quite useful.

In my case, I just test for the VersionError, and re-fetch the document and re-update the array if I encounter one. I put a limit on the number of times this loop can continue so as to not accidentally melt my database.

I can see how, at a huge scale, something like this might become an issue if you’re adding and and updating a lot of subdocuments concurrently, but for my case, it works like a dream.