Concurrency
Optimistic Concurrency
A version field, a filter that includes it, and a retry when the update matches nothing.
When the write is not a delta ($inc) but a new document shape two editors built from a stale read — a profile, a cart JSON, a CMS page — put a `version` (or updatedAt you treat as one) on the document. The update must include that version. Miss → someone else wrote; reload, merge or abort, retry.
const r = await db.users.updateOne(
{ _id: ObjectId("64a1b2c3d4e5f6a7b8c90001"), version: 4 },
{
$set: { city: "Pune", skills: ["Node.js", "MongoDB", "Redis"] },
$inc: { version: 1 }
}
)
if (r.matchedCount === 0) {
// reload, tell the client to retry — do not keep $set on a blind _id
}That is optimistic locking: assume no conflict, detect with matchedCount. Pessimistic is holding a transaction (or a lock document) for the whole edit — slower, easy to timeout. Mongoose's __v is this field; do not rely on it if some writes go through the raw driver and skip $inc: { __v: 1 }. updatedAt equality works until two writes share a millisecond — version as an integer is clearer.
- Use version for lost-update on a whole record (admin form, cart replace).
- Use `$inc` + filter for counters and stock — no version required.
- Retry with a cap. Infinite retry under load is a live-lock.
- Don't
$incversion in a differentupdateOnethan the$set— not one atomic write.
Interview question
How does optimistic concurrency work with MongoDB?
Store a version on the document. updateOne filters on _id and the version the client read, applies $set and $inc version in the same write. matchedCount 0 means a concurrent writer won — reload and retry or return 409. That prevents lost updates on whole-document edits. Counters still use $inc without a version field.