Fawn: Transactions in MongoDB
Using two phase commits for transactional edits across multiple collections

It is well known that MongoDB does not support transactions involving multiple documents. Atomicity in MongoDB is restricted to single document writes and, as a result, if you find yourself in a situation where you need to update multiple documents in “all or nothing” fashion, you’re stuck. For the most part, you can and should model your data in a way that avoids super close ties between documents but there are cases where you need a transaction.
Take the classic example of transferring cash between two bank accounts. To transfer $20, the steps are:
- Subtract 20 from the sender’s balance
- Add 20 to the receiver’s balance
As you can see, It’s possible for the first step to succeed while the second step, for whatever reason, fails. This leads to inconsistent data, leaving the sender $20 short and the receiver, expectant. This kind of operation must be done in a transactional manner i.e. if any of the steps fail, all the steps fail. This is where two phase commits come into play.
With Fawn, you can save, update, and delete documents and files across collections in a transactional manner.
If you’re new to two phase commits, the process is described well in the MongoDB docs. The main idea is:
- store all the information about the steps of an operation in a transaction and store the transaction.
- retrieve the transaction and perform it’s steps. after performing each step, update the state of the transaction to reflect that.
- A transaction is complete when it’s in a final state
In the event of an error, the transaction can be retrieved and all the steps that completed before the error can be reverted.
I wrote a library called Fawn that implements transactions using this two phase commit system. With Fawn, you can save, update, and delete documents and files across collections in a transactional manner. Let’s look at the cash transfer example again, with Fawn:
In this example, we have two update steps and should any of them fail, all prior steps will be reversed. I’ll explain how updates are implemented; the other functions provided by a Fawn task (save, remove, saveFile, removeFile) follow a very similar process.
To begin, we create a Transaction, a task in this case. It’s just an object with an array of steps. When the update function is called, we add an object to the steps array that contains all the information we need to perform this update in the future:
Now we need a way to actually perform an update. To start an update, we’ll need a way to store the data we’re about to change. This data will be used in the event of a rollback:
We’ll need a way to update a step’s state:
Now that we have those functions, the process is:
- store the document about to be changed
- update the step’s state to “pending”
- update the document
- update the step’s state to “done”
to rollback a task, we loop backwards through it’s steps and reverse each one. Here’s the rollback function for an update:
And for a task:
Finally, we need to run each of the steps sequentially. First, save the task then run each update step. If an error occurs rollback the task:
And that’s how you implement transactional updates. As I mentioned before, this process can be tweaked lightly for ‘save’ and ‘remove’ functions.
If you’re using MongoDB with Node.js and you need transactions, there’s no need to reinvent the wheel, you could just use Fawn. If you’re working in other languages, you could use the steps described in this article to create a two phase commit module of your own. Keep in mind that MongoDB is not a relational database so you should try as much as possible to model your data in a way that minimizes the need for transactions but if you find yourself in a situation that requires transactions, two phase commits might be your answer.