Chapter 05 · Updates, Array Mutations, Upserts, findAndModify, and Bulk Writes
Bulk Writes Across Operations and Namespaces: Ordered vs Unordered Execution and Error Analysis
Batch heterogeneous writes safely, interpret partial success, compare ordered and unordered execution, and use modern cross-namespace bulk APIs without assuming all-or-nothing behavior.
Learning outcomes
AtlasMart needs to apply many independent writes efficiently: change prices, update inventory, insert new stock records, and sometimes touch more than one collection. MongoDB bulk APIs reduce client/server round trips and return aggregate/per-operation evidence. They do not turn a list of writes into one transaction. Partial success is a first-class outcome that applications must inspect.
Build single-collection bulk writes from insert, update, replace, and delete operations.
Contrast ordered execution, which stops at the first ordinary write error, with unordered execution, which attempts remaining operations.
Read bulk result counts and per-operation write errors without assuming the whole list succeeded or failed.
Use MongoDB 8.0+ cross-namespace Mongo.bulkWrite()/MongoClient.bulk_write() with explicit version requirements.
Design retries from operation identity and observed results rather than blindly replaying a partially successful batch.
Mandatory examples use MongoDB Community Server
8.3.8 in the pinned
mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim
image, a disposable standalone mongod published
only on 127.0.0.1:27041, and mongosh 2.10.0.
PyMongo examples target the 4.17 line where driver behavior
matters. Authentication and TLS are intentionally disabled
only inside this isolated loopback lab; do not copy that
posture to a shared or remotely reachable server. Default
standalone read/write concern semantics are used, no
replica-set/sharding guarantees are claimed, and the reset
path removes atlasmart-mongo-ch05-l5.
Cross-namespace bulk APIs are intentionally demonstrated
because MongoDB 8.3.8 and PyMongo 4.17 satisfy their minimums.
Mongo.bulkWrite() requires MongoDB 8.0+; PyMongo
MongoClient.bulk_write() requires PyMongo 4.9+ and MongoDB
8.0+.
1. Bulk is a batching API, not an all-or-nothing transaction
A collection-level bulkWrite() accepts a sequence
of write models such as insert, update, replace, and delete. The
server/driver can execute them more efficiently than many
independent round trips. But each successful write can become
durable before a later operation fails. Therefore, the business
meaning of partial completion must be designed explicitly.
Ordered mode preserves the provided order and stops when an ordinary write error occurs. Unordered mode can execute in an arbitrary order and attempts the other operations even when some fail. Unordered mode is useful when operations are independent and maximum progress is desired; it is unsafe if later operations semantically depend on earlier ones.
2. Error analysis starts from operation indexes and persisted state
A duplicate-key error in operation 1 does not erase operation 0. In unordered mode, operation 2 may also succeed. The exception/result object reports write-error indexes and aggregate counts, but robust recovery also queries the actual business keys when necessary. A retry strategy must know which operations are naturally idempotent, which use stable unique identities, and which would double-apply if replayed.
| Operation shape | Blind replay risk | Safer identity |
|---|---|---|
| $set status=featured | Often idempotent if same value | Stable document key + desired final state |
| $inc onHand:-1 | Double decrement on replay | Idempotency/event key or transaction/workflow guard |
| insertOne random _id | Duplicate logical row | Deterministic _id or unique business key |
| deleteOne by unique id | Second replay becomes no-op | Unique immutable target key |
3. Modern bulk writes can span namespaces
Starting with MongoDB 8.0, mongosh exposes
Mongo.bulkWrite(), which can address multiple
database/collection namespaces in one call. PyMongo’s
MongoClient.bulk_write() provides a similar
client-level API and requires PyMongo 4.9 or later with MongoDB
8.0 or later. This is a convenience and batching boundary, not a
cross-namespace transaction guarantee. If product and inventory
changes must commit or abort together as one invariant, use a
transaction or redesign the aggregate; do not infer atomicity
from the fact that one bulk API call contained both operations.
4. Run ordered, unordered, and cross-namespace bulk experiments
docker rm -f atlasmart-mongo-ch05-l5 2>/dev/null || truedocker run --name atlasmart-mongo-ch05-l5 -p 127.0.0.1:27041:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimmongosh "mongodb://127.0.0.1:27041/atlasmart?directConnection=true" --quiet --eval 'printjson({version:db.version(), hello:db.hello().isWritablePrimary})'
db.products_bulk.drop(); db.inventory_bulk.drop();db.products_bulk.createIndex({sku:1},{unique:true});db.inventory_bulk.createIndex({sku:1},{unique:true});db.products_bulk.insertMany([{sku:"cam-100",name:"AtlasCam 100",priceCents:12500},{sku:"battery-9",name:"Battery 9",priceCents:1900}]);db.inventory_bulk.insertMany([{sku:"cam-100",onHand:10},{sku:"battery-9",onHand:20}]);// Single-collection bulk: deliberate duplicate-key error at operation 1.const ops=[ {updateOne:{filter:{sku:"cam-100"},update:{$inc:{priceCents:100}}}}, {insertOne:{document:{sku:"cam-100",name:"DUPLICATE",priceCents:1}}}, {updateOne:{filter:{sku:"battery-9"},update:{$inc:{priceCents:50}}}}];try{db.products_bulk.bulkWrite(ops,{ordered:true})}catch(e){printjson({orderedError:e.codeName||e.name,result:e.result,writeErrors:e.writeErrors})}printjson({afterOrdered:db.products_bulk.find().sort({sku:1}).toArray()});// Reset prices and run unordered: operation after the duplicate is attempted.db.products_bulk.updateOne({sku:"cam-100"},{$set:{priceCents:12500}}); db.products_bulk.updateOne({sku:"battery-9"},{$set:{priceCents:1900}});try{db.products_bulk.bulkWrite(ops,{ordered:false})}catch(e){printjson({unorderedError:e.codeName||e.name,result:e.result,writeErrors:e.writeErrors})}printjson({afterUnordered:db.products_bulk.find().sort({sku:1}).toArray()});// MongoDB 8.0+: cross-namespace bulk through the Mongo connection object.const cross=db.getMongo().bulkWrite([ {namespace:"atlasmart.products_bulk",name:"updateOne",filter:{sku:"cam-100"},update:{$set:{campaign:"fall"}}}, {namespace:"atlasmart.inventory_bulk",name:"updateOne",filter:{sku:"cam-100"},update:{$inc:{onHand:-1}}}, {namespace:"atlasmart.inventory_bulk",name:"insertOne",document:{sku:"strap-2",onHand:7}}],{ordered:true,verboseResults:true});printjson({crossNamespace:cross,product:db.products_bulk.findOne({sku:"cam-100"}),inventory:db.inventory_bulk.find().sort({sku:1}).toArray()});
from pymongo import MongoClient, InsertOne, UpdateOneclient=MongoClient("mongodb://127.0.0.1:27041/?directConnection=true")ops=[ UpdateOne(namespace="atlasmart.products_bulk", filter={"sku":"battery-9"}, update={"$set":{"campaign":"python"}}), UpdateOne(namespace="atlasmart.inventory_bulk", filter={"sku":"battery-9"}, update={"$inc":{"onHand":-2}}), InsertOne(namespace="atlasmart.inventory_bulk", document={"sku":"case-3","onHand":4}),]result=client.bulk_write(ops, ordered=True, verbose_results=True)print({"matched":result.matched_count,"modified":result.modified_count,"inserted":result.inserted_count})client.close()
Expected evidence
In the ordered run, the camera price changes before the duplicate-key error and the battery price operation after the error is not attempted. In the unordered run, the duplicate still errors but the later battery update is attempted. The cross-namespace call reports aggregate/verbose results while changing product and inventory collections in the same bulk request. Those writes remain individually observable, not transactionally coupled by the bulk API itself.
Verification checklist
- A unique SKU index makes the duplicate failure deterministic.
- The fixture is reset before comparing ordered and unordered outcomes.
- The persisted documents are queried after both exceptions instead of trusting only exception text.
- Cross-namespace examples declare MongoDB 8.0+ and driver/mongosh requirements.
- The lesson states clearly that bulk writes are not automatically transactions.
- Retry discussion distinguishes idempotent final-state writes from non-idempotent increments.
Check your understanding
- What happens after the first ordinary write error in an ordered bulk?
- What does unordered mean?
- Does one Mongo.bulkWrite call across two collections make the changes atomic together?
- Why is replaying $inc blindly dangerous after a network/error ambiguity?
- What minimum versions matter for PyMongo client-level multi-namespace bulk?
Review the answers
Remaining operations are not attempted; earlier successful writes remain successful.
MongoDB/driver may execute operations in an arbitrary order and attempts remaining independent operations even when some fail.
No. It batches operations across namespaces; use transaction semantics when an all-or-nothing cross-document invariant is required.
A write that actually succeeded may be applied again, double-changing the value unless the workflow has an idempotency guard or retryable-write semantics appropriate to the topology/operation.
PyMongo 4.9+ and MongoDB Server 8.0+; this lesson uses PyMongo 4.17 with MongoDB 8.3.8.
docker rm -f atlasmart-mongo-ch05-l5
Chapter 06 now leaves mutation syntax and asks the modeling question underneath it: which data belongs in one document so that the atomicity and locality demonstrated in this chapter align with the application’s actual aggregate boundaries?
Authoritative references
- MongoDB release notes — Current stable server series and patch history.
- MongoDB 8.3 release notes — 8.3.8 is the latest released 8.3 patch at review time; 8.3.9 is upcoming.
- mongosh release notes — mongosh 2.10.0 was released August 13, 2026.
- PyMongo release notes — Current PyMongo 4.17 line and driver changes.
- Collection bulkWrite() — Single-collection batch operations and ordered/unordered options.
- Mongo.bulkWrite() — MongoDB 8.0+ mongosh API for bulk writes across multiple namespaces.
- bulkWrite command — Server command semantics for multiple operations and namespaces.
- PyMongo bulk write guide — Collection and MongoClient bulk_write(), minimum versions, results, and ordered/unordered behavior.