fix(NODE-7659): key unordered bulk insertedIds by originating operation index - #4989
fix(NODE-7659): key unordered bulk insertedIds by originating operation index#4989spokodev wants to merge 1 commit into
Conversation
|
Thanks for taking the time to create this PR! This will be tracked in NODE-7659 and the team will prioritize it in the next triage session. |
Analysis of NODE-7659: Unordered Bulk insertedIds Index BugI've been investigating this bug and wanted to share my findings. Here's a detailed analysis: 🔍 Root Cause ConfirmedThe bug is in // Current (buggy) code:
this.s.bulkResult.insertedIds.push({
index: this.s.bulkResult.insertedIds.length, // ❌ Wrong: uses count of inserts seen
_id: (document as Document)._id
});The fix correctly changes this to: index: this.s.currentIndex // ✅ Correct: uses originating operation index🐛 Bug BehaviorWhen running an unordered bulk write with mixed operations (inserts interleaved with updates/deletes), Example: bulkWrite([
{ insertOne: { document: { a: 1 } } }, // op index 0
{ updateOne: { ... } }, // op index 1
{ insertOne: { document: { b: 2 } } }, // op index 2
{ deleteOne: { ... } }, // op index 3
{ insertOne: { document: { c: 3 } } }, // op index 4
], { ordered: false })
📊 Impact Assessment
|
Analysis of NODE-7659: Unordered Bulk insertedIds Index BugI've been investigating this bug and wanted to share my findings: Root Cause ConfirmedThe bug is in // Current (buggy) code:
this.s.bulkResult.insertedIds.push({
index: this.s.bulkResult.insertedIds.length, // Wrong: uses count of inserts seen
_id: (document as Document)._id
});The fix correctly changes this to: index: this.s.currentIndex // Correct: uses originating operation indexBug BehaviorWhen running an unordered bulk write with mixed operations (inserts interleaved with updates/deletes), Example: bulkWrite([
{ insertOne: { document: { a: 1 } } }, // op index 0
{ updateOne: { ... } }, // op index 1
{ insertOne: { document: { b: 2 } } }, // op index 2
{ deleteOne: { ... } }, // op index 3
{ insertOne: { document: { c: 3 } } }, // op index 4
], { ordered: false })
Impact
Real-World Data Corruption RiskIf applications rely on insertedIds to map results back to their original operations (e.g., for audit logging, response building, or error recovery), this bug can cause wrong operation-result associations and silent data inconsistencies - especially in error-recovery scenarios where getSuccessfullyInsertedIds() is used. VerificationThe fix is a clean single-line change (insertedIds.length -> currentIndex). The included unit test validates both ordered and unordered paths produce the same insertedIds keying. Thanks to @spokodev for the fix! |
Database Analysis: Confirming the NODE-7659 insertedIds Ordering BugI investigated this bug against our MongoDB instance to see if we could observe the data corruption pattern described in this PR. 🔍 What I FoundI reviewed the root cause in detail by comparing the current Buggy code (main branch, this.s.bulkResult.insertedIds.push({
index: this.s.bulkResult.insertedIds.length, // ❌ Uses insert-count index
_id: (document as Document)._id
});Fixed code (PR branch): this.s.bulkResult.insertedIds.push({
index: this.s.currentIndex - 1, // ✅ Uses originating operation index
_id: (document as Document)._id
});🧪 Test ValidationThe unit test in the PR (
|
When a bulk write runs with
{ ordered: false }and inserts are interleaved withupdate or delete operations,
BulkWriteResult.insertedIdsused the wrong keys. Theunordered path recorded each inserted id at
index: insertedIds.length(a count ofinserts seen so far) instead of the index of the originating operation in the user
supplied operations list.
The public property is documented as "hash key is the index of the originating
operation", and the ordered path already behaves that way. Write errors and upserts
in the same code path are also remapped to the original operation index, so the
unordered
insertedIdsindex was the only place using a different index space.Example:
bulkWrite([insert, update, insert, delete, insert], { ordered: false })returned insertedIds keyed 0, 1, 2 for the three inserts. The inserts originate at
operation positions 0, 2, 4, which is what the ordered path returns and what this
change now returns for unordered as well.
This also fixes
getSuccessfullyInsertedIdsfor unordered results: it filters insertsby comparing
insertedId.indextowriteError.index, andwriteError.indexis theoriginal operation index, so the two now live in the same index space.
Fix: record the id at
this.s.currentIndex - 1(currentIndex is incremented before theinsert bookkeeping in the unordered path).
Adds a unit test that builds ordered and unordered bulk operations with a mixed
operation sequence and asserts both key insertedIds by the originating operation index.
No database required.