MongoDB two-phase commit

This MongoDB article will help you to get familiar with a two-phase commit in the MongoDB database. We will learn how to perform a two-phase commit in the MongoDB database with the help of some examples.

  • MongoDB two-phase commit
  • MongoDB two-phase commit example

MongoDB two phase commit

Synopsis

The document provides a pattern for writing data to multiple documents using a two-phase commit approach. It also provides a pattern for doing multi-document updates.

Background

With MongoDB database, The operation performed on single documents are always atomic and operations in which multiple documents are often introduced as “multi-document transactions” are not atomic.

The single document provides the required support for many practical use cases. Since the documents, that contain multiple nested documents can be quite complex.

The following certain issues arise when we execute a transaction composed of sequential operations:

  • Atomicity: If one operation fails then the previous operation within the transaction must roll back to the previous state.
  • Consistency: If network or hardware interrupts the transaction then the database must be able to recover a consistent state.

We can perform a two-phase commit in our application to multi-document transactions to provide support of multi-document updates. In case of any error, using a two-phase commit ensures that data is constant and the state that preceded the transaction is recoverable.

Read: How to create a new database in MongoDB

MongoDB two phase commit example

In this section, I will explain how to perform a two-phase commit with the help of an example.

Example:

The test scenario is where we transfer the funds from account A to account B. In a relational database system, we subtract the fund from A and add the funds to B in a single multi-statement transaction. So, we follow two-phase commit operations to perform a comparable result.

The following two collections use in this example:

  1. A colletion named accounts to store account information
  2. To keep information about fund transfer transactions, there is a collection called transactions.

Insert documents into accounts collection, a document of account A and B.

db.accounts.insert([
{ _id: "A", balance: 1000, pendingTransactions: [] },
{ _id: "B", balance: 1000, pendingTransactions: [] }
])
MongoDB two phase commit
Insert documents into accounts collection

Insert documents into the transactions collection, a document with transfer information.

Insert a document containing the transfer information into the transactions collection to begin the transfer of 100 from account A to account B. The transaction state is “initial“, and the lastModified field is set to the current date:

db.transactions.insert(
{ _id: 1, source: "A", destination: "B", value: 100, state: "initial", lastModified: new Date() }
)
Two phase commit in MongoDB

The documents contain the following fields:

  • The source and destination fields refer to the _id field from accounts collection.
  • The value field, which defines the amount of transfer affecting the balance of the source and destination accounts.
  • The state field reflects the current state of the transfer. 
  • The state field can have the value of initial, pending, applied, done, cancelling, and cancelled.
  • The lastModified field refers to the last modification date.

Read MongoDB remove an element from the array

Transfer Funds Between Accounts Using Two-Phase Commit

Retrieve the transaction to start

  • From the transactions collection find the documents that is in the initial state.
  • Currenlty there is only one documents in the transactions collection.
  • If the collection contains more documents then the query will return all the transactions with an initial state unless you define additional query conditions.
  • The following code used to return the documents where state is initial.
> var t = db.transactions.findOne( { state: "initial" } )
> t
Two phase commit example
Print the contents of the variable

Update transaction state to pending

  • Here we use the update() method to update the field value
  • We set the transaction state from initial to pending
  • Also use the $currentDate operator to set the lastModified field to the current date.
db.transactions.update(
    { _id: t._id, state: "initial" },
    {
      $set: { state: "pending" },
      $currentDate: { lastModified: true }
    }
)
MongoDB two phase commit example
MongoDB update the documents

The operation returns a writeResult() object with the status of the operation. nMatched and nModified both display 1 after a successful update.


Apply the transaction to both accounts

In this, we apply the transaction t to both accounts using the update() method. In the update condition,

  • Include the condition pendingTransactions: {$ne: t._id} to prevent the transaction from being applied twice if the step is repeated.
  • Update both the balance field and pendingTransactions field, To apply the transaction to the account.
  • Now to update the source account use the below code
db.accounts.update(
{ _id: t.source, pendingTransactions: { $ne: t._id } },
{ $inc: { balance: -t.value }, $push: { pendingTransactions: t._id } }
)

Here, we subtract from its balance the transaction value and add to its pendingTransactions array the transaction _id.

  • Now to update the destionation account use the below code
db.accounts.update(
{ _id: t.destination, pendingTransactions: { $ne: t._id } },
{ $inc: { balance: t.value }, $push: { pendingTransactions: t._id } }
)

Here, we added to its balance the transaction value and added to its pendingTransactions array the transaction _id.

To check the documents of the collection use the find() method

db.accounts.find()

Execute both the code in MongoDB Shell:

Two phase commit example in MongoDB
MongoDB two-phase commit

We successfully updated the transaction of both accounts.

Note: What is the use of $ne?

The $ne operator is used to select documents in which the field value does not equal the provided value. This adds documents that do not contain the field.


Update transaction state to applied

To update the transactions state, we use the update() method

  • Set the transactions state to applied and update the lastModified field
db.transactions.update(
   { _id: t._id, state: "pending" },
   {
     $set: { state: "applied" },
     $currentDate: { lastModified: true }
   }
)

Now execute the above code in MongoDB Shell and see after updating the documents using the find() method

db.transactions.find()
Update transaction state to applied, two phase commit
Updated documents

Here, we successfully updated the documents in the transactions collection.


Update both accounts’ list of pending transactions

In this topic, we remove the applied transaction _id from the pendingTransactions array for both accounts.

To update the source account use the below code

db.accounts.update(
   { _id: t.source, pendingTransactions: t._id },
   { $pull: { pendingTransactions: t._id } }
)

Use the find() method to see the updated documents of the source account

> db.accounts.find()
{ "_id" : "A", "balance" : 900, "pendingTransactions" : [ ] }
{ "_id" : "B", "balance" : 1100, "pendingTransactions" : [ 1 ] }

To update the destination account use the below code

db.accounts.update(
   { _id: t.destination, pendingTransactions: t._id },
   { $pull: { pendingTransactions: t._id } }
)

Use the find() method to see the updated documents of the destination account

> db.accounts.find()
{ "_id" : "A", "balance" : 900, "pendingTransactions" : [ ] }
{ "_id" : "B", "balance" : 1100, "pendingTransactions" : [ ] }

Now, we successfully update the accounts list of pending transactions.

Update transaction state to done

In this topic, we complete the transaction by setting the state of the transaction to be done and also updating the lastModified field using the below code

db.transactions.update(
   { _id: t._id, state: "applied" },
   {
     $set: { state: "done" },
     $currentDate: { lastModified: true }
   }
)

After executing this code in MongoDB Shell, use the find() method to see the document of transactions collection:

Two phase commit update transaction state
Update transaction state

We successfully updated the state and lastModified field in the transactions collection.

Using Two-Phase Commits in Production Applications

The example we took above is quite simple. Basically, the purpose of the example is to understand the concept of a two-phase commit in MongoDB.

Production implementations would likely be more complex. Typically, accounts need information about the current balance, pending credits, and pending debits.

Important

Question: Does MongoDB support 2 phase commit?

Answer: The documentation is NOT a definitive design on two-phase commits. And, the key thing to remember here is in reality two-phase commits are technically impossible in MongoDB and I would suggest you get an ACID tech if you want to perform them.

You may also like to read the following article.

So, in this tutorial, we have learned about the “MongoDB two-phase commit” and we have covered this with an example.

  • MongoDB two-phase commit
  • MongoDB two-phase commit example