Chapter 05 · Updates, Array Mutations, Upserts, findAndModify, and Bulk Writes
Upserts, Equality Predicates, Generated Documents, and Race-Safe Expectations
Understand how MongoDB constructs upserted documents, why equality predicates and unique indexes matter, and how to design idempotent race-safe create-or-update paths.
Learning outcomes
AtlasMart wants “create inventory if absent, otherwise update it” without a separate existence check. That is an upsert: one update request that takes an insert path only when no document matches. The subtle part is not the option name; it is understanding how the inserted document is constructed and how concurrent callers are prevented from creating two logical records.
Explain the update-versus-insert branches of upsert and interpret upsertedId separately from matched/modified counts.
Identify which equality predicates can contribute literal fields to the inserted document and which filter expressions are only selection logic.
Use $setOnInsert for fields that should be initialized only on the insert branch.
Explain why a unique index is the database-level protection against duplicate logical identities under concurrent upserts.
Design idempotent create-or-update operations without a read-before-write race.
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:27039, 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-l3. The lab
adds a unique compound index on (tenantId, sku) because
race-safe expectations require a server-enforced identity, not
application timing.
1. Upsert is one conditional write, not “find then insert”
With upsert:false, an unmatched update simply
reports zero matches. With upsert:true, MongoDB
instead creates a new document. The inserted document is derived
from equality conditions in the filter plus the update
operations. An operator such as $gte is a
predicate, not a literal value to copy into the document.
Therefore, design filters so their role is clear: identity
equality belongs in the filter; mutable values normally belong
in $set, $inc, or other update
operators.
$setOnInsert is especially useful for creation
metadata such as createdAt,
schemaVersion, or initial ownership fields. It has
no effect on the update branch when a document already exists.
2. Result metadata tells you which branch happened
| Outcome | matchedCount | modifiedCount | upsertedId |
|---|---|---|---|
| Existing document changed | 1 | usually 1 | none/null |
| Existing document matched but no-op | 1 | 0 | none/null |
| No match; insert branch | 0 | 0 | new _id |
Applications often care about branch identity—for example,
whether to emit a “created” event. Do not infer insert versus
update from modifiedCount; an insert and a matched
no-op can both have zero modifications for different reasons.
3. Race safety comes from uniqueness, not optimism
Two processes can issue the same upsert at nearly the same time. If the logical identity is not backed by a unique index, both operations can observe no matching document and create duplicate logical records. MongoDB’s own guidance is to uniquely index the upsert filter fields when duplicate upserts must be prevented. With a unique index, the database becomes the arbiter: one insert wins and a conflicting insert attempt receives a duplicate-key error that the application can classify and, where appropriate, retry as a normal update/read path.
The gap between the read and insert is a race window. “It usually works” under low concurrency is not a correctness proof. Use one upsert operation and enforce the identity with a unique index.
4. Run the upsert construction and race-safety lab
docker rm -f atlasmart-mongo-ch05-l3 2>/dev/null || truedocker run --name atlasmart-mongo-ch05-l3 -p 127.0.0.1:27039:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimmongosh "mongodb://127.0.0.1:27039/atlasmart?directConnection=true" --quiet --eval 'printjson({version:db.version(), hello:db.hello().isWritablePrimary})'
db.inventory_upserts.drop();db.inventory_upserts.createIndex({tenantId:1,sku:1},{unique:true});// Insert path: equality predicates contribute fields; $setOnInsert only applies now.let r=db.inventory_upserts.updateOne( {tenantId:"t1",sku:"cam-100"}, {$set:{onHand:5,updatedAt:ISODate("2026-09-02T08:00:00Z")},$setOnInsert:{createdBy:"seed",schemaVersion:1}}, {upsert:true});printjson({step:"insert-upsert",matched:r.matchedCount,modified:r.modifiedCount,upsertedId:r.upsertedId,doc:db.inventory_upserts.findOne({tenantId:"t1",sku:"cam-100"})});// Update path: $setOnInsert no longer changes the existing document.r=db.inventory_upserts.updateOne( {tenantId:"t1",sku:"cam-100"}, {$inc:{onHand:2},$set:{updatedAt:ISODate("2026-09-02T08:05:00Z")},$setOnInsert:{createdBy:"SHOULD-NOT-REPLACE"}}, {upsert:true});printjson({step:"existing-upsert",matched:r.matchedCount,modified:r.modifiedCount,upsertedId:r.upsertedId,doc:db.inventory_upserts.findOne({tenantId:"t1",sku:"cam-100"})});// A range predicate is selection logic, not a literal field to materialize.r=db.inventory_upserts.updateOne( {tenantId:"t1",sku:"battery-9",onHand:{$gte:0}}, {$set:{onHand:3},$setOnInsert:{createdBy:"seed"}}, {upsert:true});printjson({step:"range-filter-upsert",upsertedId:r.upsertedId,doc:db.inventory_upserts.findOne({tenantId:"t1",sku:"battery-9"})});// Unique index is the race arbiter. A second logical identity cannot be inserted.try{db.inventory_upserts.insertOne({tenantId:"t1",sku:"cam-100",onHand:99})}catch(e){printjson({expectedDuplicate:e.codeName||e.name,keyPattern:e.keyPattern,keyValue:e.keyValue})}
from pymongo import MongoClient, ReturnDocumentfrom pymongo.errors import DuplicateKeyErrorclient=MongoClient("mongodb://127.0.0.1:27039/?directConnection=true")coll=client.atlasmart.inventory_upserts# Two callers can safely converge on one identity only if the identity is enforced uniquely.for delta in (1, 1): doc=coll.find_one_and_update( {"tenantId":"t2","sku":"strap-2"}, {"$inc":{"onHand":delta},"$setOnInsert":{"schemaVersion":1}}, upsert=True, return_document=ReturnDocument.AFTER, ) print(doc)client.close()
Expected evidence
The first camera write reports an upsertedId and
includes identity fields plus update-set values. The second
reports a match and leaves createdBy unchanged
because $setOnInsert is inactive. The range
predicate does not materialize a document field containing
{ $gte: 0 }. The explicit duplicate insert fails
because the compound unique index protects one document per
tenant/SKU.
Verification checklist
- The unique index exists before the upsert race discussion.
- Insert and update branches are distinguished through result metadata.
- $setOnInsert is visibly active only during insertion.
- The range predicate is not confused with inserted literal state.
- The duplicate-key example demonstrates the server-enforced invariant.
- No client-side “check then insert” is required.
Check your understanding
- Why is upsert safer than find-then-insert?
- Does upsert alone guarantee one logical document per SKU?
- When does $setOnInsert run?
- How do you tell that an upsert inserted?
- Why should mutable state usually not appear as broad equality in the identity filter?
Review the answers
It collapses selection and mutation into one server operation, removing the explicit application race window between a separate read and insert.
No. If concurrent operations can create the same logical identity, enforce that identity with a unique index.
Only when the upsert takes the insertion path; it does not overwrite fields when an existing document is updated.
Use upsertedId/upserted_count or equivalent result metadata, not modifiedCount.
Because it can cause an existing logical entity not to match and thereby turn an intended update into a competing insert path.
docker rm -f atlasmart-mongo-ch05-l3
Lesson 4 now needs the document itself as the return value, not just counts: MongoDB’s find-and-modify family combines selection, mutation, and before/after return semantics in one operation.
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.
- updateOne() upsert behavior — Official insert/update branches and guidance to uniquely index filter fields to avoid multiple upserts.
- $setOnInsert — Fields initialized only when an upsert inserts a document.
- Unique indexes — Database-enforced uniqueness for logical identity.
- PyMongo update operations — Driver result fields and upsert usage.