-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Closed
Labels
Milestone
Description
Prerequisites
- I have written a descriptive issue title
- I have searched existing issues to ensure the performance issue has not already been reported
Last performant version
N/A
Slowed down in version
N/A
Node.js version
22.20.0
🦥 Performance issue
While profiling an application that executes many upsert updates with very large objects (~4M), we've noticed that modifiedPaths
consumes noticeable amount of CPU.
The stack trace is:
modifiedPaths (common.js:101)
modifiedPaths (modifiedPaths.js:24)
decorateUpdateWithVersionKey (decorateUpdateWithVersionKey.js:15)
_update (model.js:4011)
updateOne (model.js:3944)
It looks like the entire object is translated, while the only thing we care about is whether it contains a version key. This could most probably be improved by using lookup instead of translating the while object.
Steps to Reproduce
import mongoose from 'mongoose';
await mongoose.connect('mongodb://127.0.0.1:27017/test');
// Create a 4M object with many sub-objects and then update with upsert
const TestSchema = new mongoose.Schema({
name: String,
data: mongoose.Schema.Types.Mixed
});
const TestModel = mongoose.model('Test', TestSchema);
const bigObject = {
data: {}
};
// Create nested structure with 4 levels and target ~4MB
const createNestedLevel = (obj, currentLevel, maxLevel, keysPerLevel) => {
if (currentLevel >= maxLevel) {
// At the deepest level, add string values
for (let i = 0; i < 4; i++) { // Reduced to 4 leaves per branch
obj[`leaf${i}`] = `value_${currentLevel}_${i}_`.padEnd(100, 'x'); // ~100 chars per leaf
}
return;
}
for (let i = 0; i < keysPerLevel; i++) {
obj[`level${currentLevel}_${i}`] = {};
createNestedLevel(obj[`level${currentLevel}_${i}`], currentLevel + 1, maxLevel, keysPerLevel);
}
};
// Calculate structure: 10^4 = 10,000 final branches * 4 leaves ≈ 40,000 leaf nodes * ~100 chars ≈ 4MB
createNestedLevel(bigObject.data, 0, 4, 10);
for (let iteration = 0; iteration < 50; iteration++) {
const name = `bigObject${iteration}`;
console.time(`Upsert ${name} - Iteration ${iteration}`);
await TestModel.updateOne(
{ name },
{ $set: { name: name, data: bigObject.data } },
{ upsert: true }
);
console.timeEnd(`Upsert ${name} - Iteration ${iteration}`);
}
await mongoose.disconnect();

Expected Behavior
Lookup should be faster.