Preventing MongoDB $expr $push race conditions in production

Summary

MongoDB’s $expr with $lt in the query filter and a single $push operation is atomic at the document level, so the array will not exceed the specified maximum when using this pattern alone.
However, real-world production environments introduce additional pitfalls that can lead to race conditions or inconsistent state.

Root Cause

  • Atomicity is confined to a single document update only.
  • Concurrent writes must hit the same document lock; other fields or indexes are not affected.
  • Improper client‑side retry logic or transaction boundaries can break the guarantee.

Why This Happens in Real Systems

  • Replica sets with delayed oplog replication – a concurrent update may be applied out of order on secondary nodes.
  • Multi‑document transactions – if the update is part of a larger transaction that also modifies another document, a rollback can leave the array unchanged while other changes persist.
  • Application-level caching or optimistic concurrency – stale reads can cause duplicate pushes if the cache is not refreshed.

Real-World Impact

  • Data integrity violation – arrays growing beyond the intended limit can trigger downstream logic errors or violate business rules.
  • Performance degradation – oversized arrays increase document size, leading to longer read/write times and higher storage costs.
  • Security and compliance risks – exceeding quotas or limits defined by regulations.

Example or Code

db.collection.updateOne(
  {
    _id: docId,
    $expr: { $lt: [{ $size: "$members" }, 3] }
  },
  { $push: { members: newMember } }
)

How Senior Engineers Fix It

  • Use a capped array size via $max on the field – create a unique hashed index to enforce length (if feasible).
  • Wrap the operation in a single‑document transaction to guarantee isolation with other writes.
  • Apply multi: false and ensure client retries only when a DuplicateKey or write conflict occurs.
  • Leverage findAndModify with $pull + $push in one atomic operation if pruning is needed.
  • Monitor oplog delays and set an appropriate write concern (w: "majority", j: true) to ensure durability.

Why Juniors Miss It

  • Assuming $expr provides full transactional guarantees without understanding the scope of MongoDB’s document‑level atomicity.
  • Ignoring replica set quirks like oplog ordering and write concern implications.
  • Overlooking driver‑level retry logic that can silently retry after a write conflict, causing duplicate pushes.
  • Lacking knowledge of best‑practice patterns such as using transactions or findAndModify for complex updates.

Leave a Comment