liveBook · Manning

Atomicity

As explained in MongoDB 8.0 in Action, Third Edition

Atomicity is a transaction property in which an operation completes entirely or has no effect.

Atomicity makes an operation behave as one indivisible unit: it either completes entirely or leaves no change behind. This prevents partial updates when an application encounters an error, crash, or network problem. section 8.0 section 8.3

MongoDB provides atomicity for every single-document insert, update, and delete. The operation preserves the document’s pretransaction state if it fails, while WiredTiger applies changes in memory before writing a successful result to disk. section 8.2

Choose the smallest atomic boundary that satisfies the application:

  • Model related data with embedded documents or arrays when one document can contain the required relationship; this can avoid distributed transactions. section 8.4
  • Use a multidocument transaction when operations must succeed together across documents, collections, databases, or shards. section 8.4
  • Account for optimistic concurrency control: conflicts are detected through snapshots and version or timestamp changes, then may cause an abort, restart, wait, or retry. section 8.2

Travel booking

A booking transaction reserves a flight and a hotel together. If the hotel reservation fails after the flight succeeds, aborting the transaction also removes the flight reservation, preserving the previous database state. section 8.0

Listing 8.2 Executing a multidocument transaction with Node.js and Callback API
const { MongoClient } = require('mongodb')

// Replace with your actual connection string
const uri = "your_mongodb_connection_string"

async function run(accountId) {
  const client = new MongoClient(uri)

  try {
    await client.connect()
    const session = client.startSession()

    const transactionOptions = {
      readConcern: { level: 'snapshot' },
      writeConcern: { w: 'majority' },
      readPreference: 'primary'
    }

    await session.withTransaction(async () => {
      const accounts = client.db('sample_analytics').collection('accounts')
      const customers = client.db('sample_analytics').collection('customers')
      const transactions = client.db('sample_analytics').collection('transactions')
      const currentDate = new Date()

      const account = await accounts.findOne(
        { account_id: parseInt(accountId) },
        { session }
      )
      if (!account) throw new Error('Account not found')

      const accountsUpdateResult = await accounts.updateOne(
        { account_id: parseInt(accountId) },
        {
          $set: { limit: 12000, last_transaction_date: currentDate },
          $inc: { transaction_count: 1 }
        },
        { session }
      )

      const customersUpdateResult = await customers.updateMany(
        { accounts: { $in: [parseInt(accountId)] } },
        {
          $inc: { transaction_count: 1 },
          $set: { last_transaction_date: currentDate }
        },
        { session }
      )

      const transactionsInsertResult = await transactions.insertOne(
        {
          account_id: parseInt(accountId),
          transaction_count: account.transaction_count + 1,
          bucket_start_date: currentDate,
          bucket_end_date: currentDate,
          transactions: [
            {
              date: currentDate,
              amount: 1500,
              transaction_code: 'buy',
              symbol: 'amzn',
              price: '125.00',
              total: '187500.00'
            }
          ]
        },
        { session }
      )

      console.log("Transaction committed.")
      console.log("Accounts updated:", accountsUpdateResult.modifiedCount)
      console.log("Customers updated:", customersUpdateResult.modifiedCount)
      console.log("New transaction inserted:", transactionsInsertResult.insertedId)

    }, transactionOptions)

    session.endSession()
  } catch (error) {
    console.error("Transaction aborted due to error:", error)
  } finally {
    await client.close()
  }
}

// Get accountId from command line arguments
const accountId = process.argv[2]
if (!accountId) {
  console.error("Please provide an account ID as an argument.")
  process.exit(1)
}

run(accountId).catch(console.dir)

Use the Node.js Callback API example to inspect a multidocument transaction that updates account and customer records and inserts a transaction record within one withTransaction callback. section 8.4.3

How MongoDB 8.0 in Action, Third Edition covers it

How deep it goes
Explains single-document behavior, multidocument scope, WiredTiger storage, and optimistic conflict handling, but not recovery implementation in detail.
Examples
Uses travel booking, single-document failure, and a Node.js account-and-customer transaction to demonstrate all-or-nothing behavior.
What it assumes
Assumes basic MongoDB documents and operations while defining atomicity and the other ACID properties explicitly.
What you can do after
Apply it via MongoDB single-document operations or the Node.js withTransaction Callback API to coordinate reliable updates.
How it connects
Connects atomicity with ACID transactions, consistency, isolation, durability, embedded documents, and optimistic concurrency control.

Also explained in

Compare how 13 books explain atomicity →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of MongoDB 8.0 in Action, Third Edition. The section links point to the parts of the book it draws on.