Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
2 tasks done
wunderb1t opened this issue Jan 26, 2023 · 3 comments
Open
2 tasks done

Comments

@wunderb1t
Copy link

wunderb1t commented Jan 26, 2023

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

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.

@vkarpov15 vkarpov15 added this to the 6.9.3 milestone Feb 6, 2023
@vkarpov15 vkarpov15 modified the milestones: 6.10.1, 6.10.3 Mar 1, 2023
@vkarpov15 vkarpov15 modified the milestones: 6.10.3, 6.10.4 Mar 10, 2023
@wunderb1t
Copy link
Author

Hello, I see this issue was dropped from the milestone, when to expect it? Thank you!

@vkarpov15 vkarpov15 added this to the 7.0.4 milestone Mar 21, 2023
@vkarpov15
Copy link
Collaborator

@wunderb1t we'll add it back to our next milestone and see if we can repro and fix.

@vkarpov15
Copy link
Collaborator

With #13254, we got a 10x speedup:

Before:

$ node ./benchmarks/get.js 
{
  "Model loop ms": 2011,
  "POJO loop ms": 7
}

After:

$ node ./benchmarks/get.js 
{
  "Model loop ms": 206,
  "POJO loop ms": 8
}

We may be able to improve this more in the future by avoiding the this.$__schema.paths[path] lookup by passing the schematype in directly, and by passing in noDottedPath as a primitive rather than relying on options. However, we are limited in how much we can speed up model property accesses - they will always be significantly slower than a plain old property accesss because we need to support custom getters.

@vkarpov15 vkarpov15 removed this from the 6.10.5 milestone Apr 6, 2023
vkarpov15 added a commit that referenced this issue Apr 17, 2023
vkarpov15 added a commit that referenced this issue Apr 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

2 participants