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 (
|
|
@DijieDeng — thank you for digging into this, and sorry for the slow reply. Your follow-up has it exactly right: On the state of the PR: the evergreen contexts on this head are from 14-15 July, and this PR's base is now 47 commits behind @johnmtll — would a rebase help this reach a triage pass, or is there something else it needs? |
Hi @spokodev, thanks again for all your hard work on this, and apologies for the late reply! The only thing missing is integration test coverage for getSuccessfullyInsertedIds, so these index mismatches don't come back. Something like: an unordered bulkWrite that mixes inserts with updates/deletes, where one insert fails with a duplicate key error. Then assert that MongoBulkWriteError.insertedIds holds only the successful inserts, keyed by their original operation index. Please also rebase onto main so CI runs against current code. Once that's done, I think we're good to merge! 👍 |
…operations
The existing unordered bulkWrite case is four inserts, so the count of inserts
seen and the originating operation index coincide and the mismatch is invisible.
This adds an unordered bulkWrite whose inserts sit at operation indexes 0, 2 and
4, with the middle one failing on the unique index. getSuccessfullyInsertedIds
filters insertedIds by comparing their index against writeErrors[].index, and
that error index is remapped through batch.originalIndexes in common.ts, so the
two must be in the same numbering. With the running count the assertion sees
{ 0: 0, 1: 2 } instead of { 0: 0, 4: 4 }.
9f4a9c2 to
ac1dbbc
Compare
|
@johnmtll — thank you, both done. Rebased onto The existing unordered
One thing I should be straight about: I could not run the integration suite locally — there is no mongod available on this machine — so CI here will be its first execution. What I did verify locally is that the file parses and lints clean. |
|
The rebase and the test are on One thing that needs a hand on your side: evergreen created a patch for this head, but it is sitting at "patch must be manually authorized", so nothing has actually run against the rebased code — the previous head carries 28 status contexts, this one carries 1. https://evergreen.mongodb.com/patch/6abc0ed2a1a0550007bf920d |
Description
Summary of Changes
Fixes
BulkWriteResult.insertedIdsfor unordered bulk writes so each key is the index of the operation that produced the insert. Before this change, the key was a running count of inserts, which could result in mismatches.Notes for Reviewers
getSuccessfullyInsertedIdsfor unordered results. That function drops inserts whoseinsertedId.indexmatches awriteError.index, so the two values need to use the same indexing.What is the motivation for this change?
Tracked in NODE-7659. Unordered bulk writes returned
insertedIdskeys that didn't match the documented behaviour. This also causedgetSuccessfullyInsertedIdsto return the wrong ids when an unordered bulk write partly failed.For bug fixes
Current (incorrect) behavior:
When you run a bulk write with
{ ordered: false }and mix inserts with updates or deletes,insertedIdsis keyed 0, 1, 2… by the order of the inserts alone. It isn't keyed by each insert's position in the full list of operations. When an insert fails,getSuccessfullyInsertedIdscompares these keys withwriteErrors[].index, so it can drop the wrong inserts or keep the failed one.Expected behavior:
insertedIdsis keyed by the index of the originating operation, the same as the ordered path. For example,bulkWrite([insert, update, insert, delete, insert], { ordered: false })should return keys 0, 2 and 4.How to reproduce:
Affected versions:
Release Highlight
Fix bulk write result insertedId mismatches on bulkwrites with mixed op types
For unordered bulk writes that mixed inserts with updates or deletes, BulkWriteResult.insertedIds was keyed by a running count of inserts, not by each insert's index in the operations list. It's now keyed by the operation index, which matches ordered bulk writes and the documentation.
Double check the following
npm run check:lint)type(NODE-xxxx)[!]: descriptionfeat(NODE-1234)!: rewriting everything in coffeescript