MongoDB: How Rollback Works
Table of Contents
1. Rollback Means Different Things in MongoDB
In Oracle, ROLLBACK is one thing: undo my uncommitted changes. In MongoDB, the word is used in two different places, and there is also a case where you cannot roll back at all.
| Case | What happens | Oracle similar |
|---|---|---|
| Single-document write | Already committed. You cannot roll it back. To undo it, run another write. | A statement after COMMIT |
| Multi-document transaction | abortTransaction() throws away all changes. This is the real ROLLBACK. | ROLLBACK; |
| Replica set rollback | After a failover, a node removes writes that never reached the majority. This happens automatically. | Reinstating an old primary in Data Guard (Flashback Database) |
Key point: Rollback is about uncommitted or not-yet-safe data. A write acknowledged with
w: "majority"is never rolled back, in a transaction or after a failover.
2. Transaction Rollback (abortTransaction)
Inside a transaction, your changes live in a private snapshot, and nobody else can see them. If you call abortTransaction(), MongoDB simply throws the changes away. Nothing is sent to the secondaries, so the rest of the cluster never knows the changes existed.
| MongoDB | Oracle |
|---|---|
| |
This is what happens inside:
+----------------------------------------------+
| 1. startTransaction() |
| a private snapshot is opened |
+-----------------------+----------------------+
|
v
+----------------------------------------------+
| 2. YOUR WRITES run in the transaction | other sessions cannot see them
| changes are kept as uncommitted versions | secondaries receive nothing yet
+-----------------------+----------------------+
|
+-------------+--------------+
| |
v v
+---------------------+ +-------------------------+
| commitTransaction() | | abortTransaction() |
| changes become | | (or an automatic abort) |
| permanent | | uncommitted versions |
| | | are discarded |
+---------------------+ +------------+------------+
|
v
+-------------------------+
| DATA IS BACK TO HOW IT |
| WAS BEFORE THE START |
+-------------------------+
No undo tablespace is needed. Oracle changes the data block and uses undo to reverse it. WiredTiger keeps uncommitted changes as separate versions, and an abort just drops them. This is why MongoDB has nothing like ORA-01555 or undo sizing to worry about.
MongoDB can abort a transaction for you. It does this when:
- A write fails (for example, a duplicate key error).
- Another session changed the same document (write conflict).
- The transaction runs longer than 60 seconds (
transactionLifetimeLimitSeconds). - The session ends or the connection is lost.
- The primary steps down during the transaction.
If the error carries the label TransientTransactionError, run the whole transaction again from the start.
| MongoDB | Oracle |
|---|---|
| An error aborts the whole transaction. | An error rolls back only the failed statement. The transaction continues. |
| No savepoints. You can only abort everything. | ROLLBACK TO SAVEPOINT sp1; |
| Session ends or times out, so the transaction is aborted. | Session dies, so PMON rolls back the uncommitted work. |
| Uncommitted versions are discarded. No undo tablespace. | Undo segments are used to reverse the changes. |
After commitTransaction(), abortTransaction() fails. | After COMMIT, ROLLBACK does nothing. |
3. Replica Set Rollback (After a Failover)
This is the rollback that surprises most DBAs. It has nothing to do with abortTransaction(). It happens when the primary accepted a write, acknowledged it, and then crashed or lost the network before any secondary copied it.
Here is a simple example with Node A (old primary) and Node B (new primary):
1. Node A is the primary. Write X1 is copied to Node B. (safe, on the majority)
2. Client writes X2 with w:1. Node A says OK. (X2 is only on Node A)
3. Node A loses the network before it can copy X2 to Node B.
4. Node B is elected the new primary and accepts Y1, Y2.
5. Node A comes back as a secondary and compares its oplog with Node B:
common point
|
Node A oplog: X1 --- X2 <-- X2 exists only on Node A
Node B oplog: X1 --- Y1 --- Y2 <-- Node B is the new primary
6. Node A ROLLS BACK X2 and saves it to a rollback file.
7. Node A copies Y1, Y2 from Node B.
Node A oplog: X1 --- Y1 --- Y2 (same as Node B)
This is how Node A rolls back:
+----------------------------------------------+
| 1. Node A rejoins and finds its oplog |
| has writes the new primary does not have |
+-----------------------+----------------------+
|
v
+----------------------------------------------+
| 2. Node A moves to the ROLLBACK state | visible in rs.status()
| and finds the last common point |
+-----------------------+----------------------+
|
v
+----------------------------------------------+
| 3. Node A goes back to its last stable | "recover to stable timestamp"
| checkpoint | (MongoDB 4.0 and above)
+-----------------------+----------------------+
|
v
+----------------------------------------------+
| 4. The removed writes are saved to a | nothing is lost silently
| rollback file (BSON) |
+-----------------------+----------------------+
|
v
+----------------------------------------------+
| 5. Node A replays the oplog up to the common |
| point, then follows the new primary |
+-----------------------+----------------------+
|
v
+----------------------------------------------+
| 6. Node A is a normal SECONDARY again |
+----------------------------------------------+
The removed writes are saved, not deleted. You can find them here:
<dbPath>/rollback/<db>.<collection>/removed.<timestamp>.bson
Read the file with bsondump. If you decide the data should be kept, add it back with mongorestore or an application script. This is controlled by createRollbackDataFiles, which is true by default in MongoDB 4.4 and above.
How to avoid it: use w: "majority". Once the write is on the majority, any new primary must have it, so it can never be rolled back. This is the default on replica sets from MongoDB 5.0.
One limit to know: a node can only roll back if the common point is recent. The default limit is 24 hours (rollbackTimeLimitSecs). If the node was away longer, it cannot roll back and needs a full resync.
| MongoDB | Oracle (Data Guard) |
|---|---|
| Old primary rolls back automatically when it rejoins. | You reinstate the old primary with FLASHBACK DATABASE (or the broker REINSTATE command). |
Writes with w:1 can be lost in a failover. | Maximum Performance (ASYNC) mode can lose redo in a failover. |
w: "majority" protects the write. | Maximum Availability or Protection (SYNC) protects the commit. |
| Removed writes are saved in BSON rollback files. | Diverged redo is discarded by the flashback. |
Node state is ROLLBACK while it happens. | Old primary is down until it is reinstated. |
4. How to Check for Rollbacks
Try a transaction rollback yourself:
// The collection must exist before the transaction starts
db.getSiblingDB("shop").createCollection("accounts")
const session = db.getMongo().startSession()
const accounts = session.getDatabase("shop").accounts
session.startTransaction()
accounts.insertOne({ name: "Test", balance: 100 })
// Inside the transaction: you can see it
accounts.find({ name: "Test" })
// Outside the transaction: nothing yet
db.getSiblingDB("shop").accounts.find({ name: "Test" })
session.abortTransaction() // ROLLBACK
// After the abort: it is gone for everybody
db.getSiblingDB("shop").accounts.find({ name: "Test" })
session.endSession()
Count how many transactions committed or aborted:
db.serverStatus().transactions
// Look at: totalStarted, totalCommitted, totalAborted
See if any replica set member is in the ROLLBACK state:
rs.status().members.map(m => ({ name: m.name, state: m.stateStr }))
// stateStr shows PRIMARY, SECONDARY, ROLLBACK, ...
Look for rollback messages in the log, and check for rollback files (Linux shell):
# Rollback messages in the mongod log (JSON format)
grep '"c":"ROLLBACK"' /var/log/mongodb/mongod.log
# Rollback files saved under the data directory
ls -l /var/lib/mongo/rollback/
# Read a rollback file
bsondump /var/lib/mongo/rollback/shop.orders/removed.2025-01-15T10-30-00.0.bson
Bottom line for Oracle DBAs: A transaction
abortTransaction()works likeROLLBACK, only simpler. A replica set rollback works like reinstating a failed primary, but MongoDB does it for you and keeps the removed data in a file. Usew: "majority"and the second kind will almost never touch your important data.