Automattic / Automattic/mongoose

Property getting on Model objects extremely slow comparing with Plain objects.

Open
#12,953 3 comments 2 reactions 0 assignees View on GitHub
performance
Dominant language
JavaScript
Stars
27.5k
Forks
4k
Avg merge
2d 7h
Merged PRs (30d)
35

Description

### Prerequisites

- [x] I have written a descriptive issue title
- [X] I have searched existing issues to ensure the performance issue has not already been reported

### Last performant version

6.9.0

### Slowed down in version

6.9.0

### Node.js version

13.14.0

### 🦥 Performance issue

Suppose we have 1k documents in MongoDB collection and we fetched them all.
Then we want to work with all document properties inside loop because we have familiar application structure.
The problem is that property getting on Model objects extremely slow comparing with Plain objects.

Here is ours tests output result:
```
ModelObject loop ms: 390 (size: 1010)
PlainObject loop ms: 8 (size: 1010)

ModelObject loop ms: 371 (size: 1010)
PlainObject loop ms: 6 (size: 1010)

ModelObject loop ms: 421 (size: 1010)
PlainObject loop ms: 7 (size: 1010)

ModelObject loop ms: 595 (size: 1010)
PlainObject loop ms: 7 (size: 1010)

ModelObject loop ms: 481 (size: 1010)
PlainObject loop ms: 7 (size: 1010)
```

### Steps to Reproduce

```
const mongoose = require('mongoose');

// Create connection.
mongoose.connect(mongoConfig.server, {
useNewUrlParser: true,
useUnifiedTopology: true,
authSource: 'my_authSource',
user: 'my_username',
pass: 'my_password',
});

let db = mongoose.connection;
db.on('error', (err) => {
global.logger.error(`Mongoose: ${err}`);
});
db.once('open', async () => {
global.logger.info("Mongoose: Connected to DB.");
});

/**
* @singleton
*/
class TestModel {
static _instance;
_model;

/**
* @private
*/
constructor() {}

static uniqueString = function(type) {
return type + '-' + Math.random().toString(36).substr(2, 16);
};

/**
* @return {PairModel}
*/
static create() {
if (this._instance) {return this._instance;}
this._instance = new this();

let pairSchema = new mongoose.Schema({
field1: {type: String, default: TestModel.uniqueString('field1')},
field2: {type: String, default: TestModel.uniqueString('field2')},
field3: {type: String, default: TestModel.uniqueString('field3')},
field4: {type: String, default: TestModel.uniqueString('field4')},
field5: {type: String, default: TestModel.uniqueString('field5')},
field6: {type: String, default: TestModel.uniqueString('field6')},
field7: {type: String, default: TestModel.uniqueString('field7')},
field8: {type: String, default: TestModel.uniqueString('field8')},
}, { versionKey: false });

this._instance._model = mongoose.model('test', pairSchema);

return this._instance;
}

async insertTests() {
const tests = [];

for (let i = 0; i < 1000; i++) {
tests.push(new this._model())
}

try {
return this._model.insertMany(tests, {
ordered: false
});
}
catch (err) {
throw new Error(`${this.constructor.name} insertMany: ${err}`);
}
}

async all() {
return await this._model.find({});
}

}

const testModel = TestModel.create();

async function test() {
await testModel.insertTests(); // generate 1000 objects
let tests = await testModel.all();
let len = tests.length;
let loopStart = Date.now();

// run loop with mongoose objects
for (let k = 0; k < 100; k++) {
for (let test of tests) {
test.field1;
test.field2;
test.field3;
test.field4;
test.field5;
test.field6;
test.field7;
test.field8;
}
}

console.log(`ModelObject loop ms: ${Date.now() - loopStart} (size: ${len})`);
console.log(`----------------------------------`);

const plainTests = [];

for (let test of tests) {
plainTests.push(test.toObject());
}

loopStart = Date.now();

// run loop with plain objects
for (let k = 0; k < 100; k++) {
for (let test of plainTests) {
test.field1;
test.field2;
test.field3;
test.field4;
test.field5;
test.field6;
test.field7;
test.field8;
}
}

console.log(`PlainObject loop ms: ${Date.now() - loopStart} (size: ${len})`);
console.log(`----------------------------------`);
}

test();
```

### Expected Behavior

Iteration time should be similar to Plain objects loop time.

Contributor guide

Open the contributing guide

Research direction

Start with the reported testModel.all() path, the _model.find({}) query, and the property-access loop in the reproduction. Compare repeated field access on the returned model objects with access after toObject(); done means model-property iteration is substantially closer to the plain-object timing.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, mongodb, node.js
Domain
backend, databases, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.