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

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 the MongoDB database, the operations performed on single documents are always atomic, and operations that involve multiple documents are often introduced as “multi-document transactions” and are not atomic.

The single document provides the required support for many practical use cases, as documents containing multiple nested documents can be pretty 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 implement a two-phase commit in our application to support multi-document transactions, enabling multi-document updates. In case of any error, using a two-phase commit ensures that data is consistent and the state that preceded the transaction is recoverable.

Read: How to create a new database in MongoDB

Example

The test scenario involves transferring funds from account A to account B. In a relational database system, we subtract the funds 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 were used in this example:

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

Insert documents into the accounts collection, specifically documents for accounts and B.

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

Insert a document with transfer information into the transactions collection.

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() }
)
MongoDB two phase commit example

The documents contain the following fields:

  • The source and destination fields refer to the _id field from the accounts collection.
  • The value field defines the amount of transfer that affects 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, or 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 are in the initial state.
  • Currently, there is only one document in the transactions collection.
  • If the collection contains more documents than the query will return all the transactions with an initial state, unless you define additional query conditions.
  • The following code is used to return the documents where the state is initial.
> var t = db.transactions.findOne( { state: "initial" } )
> t
Two phase commit example

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

The operation returns a writeResult() object with the status of the operation. nMatched and nModified both display one 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 the pendingTransactions field to apply the transaction to the account.
  • Now, to update the source account, use the code below
db.accounts.update(
{ _id: t.source, pendingTransactions: { $ne: t._id } },
{ $inc: { balance: -t.value }, $push: { pendingTransactions: t._id } }
)

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

  • Now, to update the destination account, use the code below
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 the MongoDB Shell:

Two phase commit example in MongoDB

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 transaction’s state, we use the update() method

  • Set the transaction’s 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 the MongoDB Shell and see after updating the documents using the find() method

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

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 code below

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 code below

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 have successfully updated 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 code below

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

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

Two phase commit 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. The purpose of this example is to illustrate 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. The key thing to remember is that, in reality, two-phase commits are technically impossible in MongoDB. I would suggest getting an ACID technology if you want to perform them.

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

You may also like to read the following article.

Top 200 SQL Server Interview Questions and Answers

Free PDF On Top 200 SQL Server Interview Questions And Answers

Download A 40 pages PDF And Learn Now.