Applications and events

Choose transactions or change streams

Use transactions for atomic multi-document state changes and change streams for observing committed changes without confusing the two jobs.

9 minute lesson

~~~

A single MongoDB document update is atomic. That is not a limitation to work around — it is the reason embedding related data in one document is the first tool for consistency. Reach for a transaction only when one business invariant truly spans multiple documents or collections.

The textbook case is a transfer between two account documents. Either both writes happen or neither does:

const session = db.getMongo().startSession()
try {
  session.startTransaction()
  const accounts = session.getDatabase('bank').accounts

  accounts.updateOne({ _id: 'alice' }, { $inc: { balance: -100 } })
  accounts.updateOne({ _id: 'bob' }, { $inc: { balance: 100 } })

  session.commitTransaction()
} catch (error) {
  session.abortTransaction()
  throw error
}

Transactions require a replica set or sharded cluster — a plain standalone mongod refuses to start one. They also add coordination cost, hold resources while open, and can abort under contention, so your code must be ready to retry. If every operation in your application runs in a transaction, that is not caution. It is a document model that put data which belongs together into separate collections.

Change streams observe, they do not change

A change stream is a different job entirely: it lets you subscribe to committed changes on a collection, database, or cluster.

const stream = db.orders.watch([
  { $match: { operationType: 'insert' } }
])

stream.on('change', event => {
  console.log(event.fullDocument._id, 'created')
})

This fits search-index updates, cache invalidation, and analytics feeds: react to what happened, after it happened. Like transactions, change streams need a replica set, because they read from the replication machinery.

Consumers still carry responsibility. Your process will restart, so persist the stream’s resume token and pass it back to watch() to continue where you left off. Expect to see some events more than once after a resume, and make handlers idempotent.

Classify before you build

Try these three: an account transfer, a “notify analytics when an order is created” requirement, and a search-index update. The transfer changes two documents under one invariant — transaction. The other two observe committed changes and react — change streams. If a case seems to need both, split it: change the state atomically first, let observers react afterward.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →