Mongoose: 10x slowdown on Windows when upgrading from Mongoose 4 to 5.

Created on 7 Jun 2018  ยท  21Comments  ยท  Source: Automattic/mongoose

My tests are taking 10x as long on Windows when I upgrade from Mongoose 4 to 5. The slowdown doesn't appear to happen on my Ubuntu laptop.

I created a repo with two branches to illustrate the problem,
https://github.com/deklanw/pensieve-mongoose-4-to-5-speed-testing

I included the .env file for testing. If need be just change this line
MONGODB_URI=mongodb://localhost:27017/pensieve-test
to reflect your testing environment.

Just,
yarn install
yarn test

I tested on my Windows desktop and laptop to make sure. They're both Windows 10 x64. The problem occurred on both. The problem doesn't occur on my Ubuntu laptop.

Here are the versions:
Windows 10 x64 laptop: mongodb v3.6.5 node v10.1.0 yarn v1.6.0
Windows 10 x64 desktop: mongodb v3.6.3 node v10.3.0 yarn v1.7.0
The Ubuntu laptop has the same versions of everything as the Windows 10 desktop.

@lineus Tested this on OSX, I believe, and didn't find the slowdown. So it appears to be Windows related.

docs underlying library issue

All 21 comments

Here are the test outputs for the Mongoose 4 and 5 branches, respectively, on my Windows 10 desktop. Running the tests repeatedly gave similar results.

```$ node scripts/test.js
โœจ Test database loaded Cards controller
GET /api/cards
GET /api/cards 200 21.914 ms - 345
โˆš should return array of cards for user (44ms)
POST /api/cards
POST /api/cards 200 24.228 ms - 278
โˆš should create single card for user
DELETE /api/cards/:id
DELETE /api/cards/5b195db0dd72452680504f5c 200 2.678 ms - 14
โˆš should delete single card for user

Decks controller
GET /api/decks
GET /api/decks 400 0.135 ms - 64
โˆš should return 400 if no authentication provided
GET /api/decks 200 7.843 ms - 335
โˆš should return decks if token is provided
POST /api/decks
POST /api/decks 200 2.426 ms - 275
โˆš should create single deck for user
DELETE /api/decks/:id
DELETE /api/decks/5b195db0dd72452680504f6d 200 2.033 ms - 14
โˆš should delete single deck for user

Sessions controller
GET /api/sessions/:id
GET /api/sessions/5b195db0dd72452680504f67 200 4.788 ms - 368
โˆš should return a single session for user

Users controller
POST /api/users/login
POST /api/users/login 200 62.342 ms - 384
โˆš should return user if authentication is valid (65ms)
POST /api/users/login 200 58.972 ms - 384
โˆš should return token if authentication is valid (61ms)
POST /api/users/signup
POST /api/users/signup 200 82.471 ms - 363
โˆš should create user if signup is valid (84ms)
GET /api/users/profile
GET /api/users/profile 200 2.203 ms - 128
โˆš should return user for valid token
PUT /api/user/profile
PUT /api/users/profile 200 4.476 ms - 128
โˆš should update single user for valid token

recallRate
getRecallRate
โˆš should return rate of 50% for current review date
โˆš should return recall rate of 100% for current reviewed timestamp
getCardAverage
โˆš should return average recall rate for array of cards

Server
GET / 302 1.067 ms - 27
โˆš GET / redirects to /docs
GET /invalid 404 1.272 ms - 146
โˆš response to 404 unsupported routes

Card model
get
โˆš should return single card for user
getAll
โˆš should return all cards for user
countAll
โˆš should return the length of all cards for a user
create
โˆš should create single card for user
update
โˆš should update single card for user
delete
โˆš should delete single card for user
deleteAllByDeck
โˆš should delete all cards for a deck

Deck model
get
โˆš should return single deck for user
all
โˆš should return all decks for user
new
โˆš should create single deck for user
update
โˆš should update single deck for user
โˆš should not update fields that are not defined

Review model
create
โˆš should create new review object for user
get
โˆš should return review object by id

Session model
get
โˆš should return single session object for user
โˆš should return populated card objects for session
create
โˆš should create single session object for user

User model
get
โˆš should return user with matching id
โˆš should not return password field
update
โˆš should update user with matching id
โˆš should not update fields that are not defined
โˆš should not return password field
delete
โˆš should delete user with matching id
create
โˆš should create user with matching information (72ms)
authenticate
โˆš should authenticate user with correct information (116ms)

Cards mailer
sendDueCardsEmail
โˆš should send email for valid user
โˆš should not send email if users notification preference is off โˆš should not send email if number of due cards are less than users session size
sendNewCardsEmail
โˆš should send email for valid user
โˆš should not send email if user`s notification preference is off
โˆš should not send email if number of new cards are less than 3

49 passing (642ms)

Done in 2.84s.

```$ node scripts/test.js
โœจ  Test database loaded
  Cards controller
    GET /api/cards
GET /api/cards 200 2005.845 ms - 345
      โˆš should return array of cards for user (2027ms)
    POST /api/cards
POST /api/cards 200 1992.893 ms - 278
      โˆš should create single card for user (1997ms)
    DELETE /api/cards/:id
DELETE /api/cards/5b195defb5d63f0834d10423 200 3.603 ms - 14
      โˆš should delete single card for user

  Decks controller
    GET /api/decks
GET /api/decks 400 0.158 ms - 64
      โˆš should return 400 if no authentication provided
GET /api/decks 200 8.937 ms - 335
      โˆš should return decks if token is provided
    POST /api/decks
POST /api/decks 200 3.616 ms - 275
      โˆš should create single deck for user
    DELETE /api/decks/:id
DELETE /api/decks/5b195defb5d63f0834d10434 200 3.060 ms - 14
      โˆš should delete single deck for user

  Sessions controller
    GET /api/sessions/:id
GET /api/sessions/5b195defb5d63f0834d1042e 200 7.425 ms - 368
      โˆš should return a single session for user

  Users controller
    POST /api/users/login
POST /api/users/login 200 61.300 ms - 384
      โˆš should return user if authentication is valid (63ms)
POST /api/users/login 200 60.517 ms - 384
      โˆš should return token if authentication is valid (62ms)
    POST /api/users/signup
POST /api/users/signup 200 88.379 ms - 363
      โˆš should create user if signup is valid (91ms)
    GET /api/users/profile
GET /api/users/profile 200 1.918 ms - 128
      โˆš should return user for valid token
    PUT /api/user/profile
PUT /api/users/profile 200 5.914 ms - 128
      โˆš should update single user for valid token

  recallRate
    getRecallRate
      โˆš should return rate of 50% for current review date
      โˆš should return recall rate of 100% for current reviewed timestamp
    getCardAverage
      โˆš should return average recall rate for array of cards

  Server
GET / 302 0.829 ms - 27
    โˆš GET / redirects to /docs
GET /invalid 404 0.936 ms - 146
    โˆš response to 404 unsupported routes

  Card model
    get
      โˆš should return single card for user
    getAll
      โˆš should return all cards for user
    countAll
      โˆš should return the length of all cards for a user
    create
      โˆš should create single card for user
    update
      โˆš should update single card for user
    delete
      โˆš should delete single card for user
    deleteAllByDeck
      โˆš should delete all cards for a deck

  Deck model
    get
      โˆš should return single deck for user
    all
      โˆš should return all decks for user
    new
      โˆš should create single deck for user
    update
      โˆš should update single deck for user
      โˆš should not update fields that are not defined

  Review model
    create
      โˆš should create new review object for user
    get
      โˆš should return review object by id

  Session model
    get
      โˆš should return single session object for user
      โˆš should return populated card objects for session
    create
      โˆš should create single session object for user

  User model
    get
      โˆš should return user with matching id
      โˆš should not return `password` field
    update
      โˆš should update user with matching id
      โˆš should not update fields that are not defined
      โˆš should not return `password` field
    delete
      โˆš should delete user with matching id
    create
      โˆš should create user with matching information (57ms)
    authenticate
      โˆš should authenticate user with correct information (115ms)

  Cards mailer
    sendDueCardsEmail
      โˆš should send email for valid user
      โˆš should not send email if user`s notification preference is off
      โˆš should not send email if number of due cards are less than user`s session size
    sendNewCardsEmail
      โˆš should send email for valid user
      โˆš should not send email if user`s notification preference is off
      โˆš should not send email if number of new cards are less than 3


  49 passing (5s)

Done in 7.12s.

Thanks for reporting, will investigate ASAP

I am not sure if it's relevant, but the connection process takes 5 seconds on Windows, I'll try with mongoose v4 and see if it's a v5 thing or that's the normal duration.

Running the following code to calculate the connection time:

const mongoose = require('mongoose');


const run = async () => {
  let beforeConnection = Date.now();
  await mongoose.connect('mongodb://localhost/somedb');
  let afterConnection = Date.now();

  console.log(`Time to connect is: ${(afterConnection - beforeConnection) / 1000}s`);
};

run().catch(console.error);

output using mongoose v4.13.14:

Time to connect is: 0.031s

output using mongoose v5.1.5:

Time to connect is: 1.035s

Windows 10 1803 (Build 17134.81)
Node v10.4.0
MongoDB v3.2.7

Just to clarify, are you running mongodb locally or are you connecting to a remote mongodb @AbdelrahmanHafez ?

Locally

It's not just the connection either, deleting, and creating data are 10x+ slower as well.

Using the following code:

const mongoose = require('mongoose');
const { Schema } = mongoose;
mongoose.Promise = Promise;


const userSchema = new Schema();

const User = mongoose.model('User', userSchema);

const users = [];

for (let i = 0; i < 1000; i++) users.push(new User());

const run = async () => {
  await mongoose.connect('mongodb://localhost/test');

  const before = Date.now();

  await User.remove();
  await User.create(users);

  const after = Date.now();

  console.log(`Duration: ${(after - before) / 1000}s`);
};

run().catch(console.error);

v4.13.14 outputs:

Duration: 0.461s

v5.1.5 outputs:

Duration: 4.621s

Perhaps we should remove the needs repro script label from this issue now?
I believe https://github.com/Automattic/mongoose/issues/6566#issuecomment-397935727 is a good start.

Needs repro just means that the mongoose devs haven't been able to confirm this as a bug. In this case, it means we haven't had the time to boot up windows and try it. Perhaps we should split that label into 2 labels, one that says theres no repro script and one that says theres a repro script but the issue is blocked on mongoose devs?

a bug? label would be great, needs repro script on an issue that's been abandoned for a while may give the wrong idea to the developers.

Speed is greatly reduced for me too. I'm Using MongoDb 4.0, Mongoose 5.2.1, Nodejs 10.5.0 and Windows 10.
Maybe MongoDB 4.0 issue?
SERVER-35657:
https://docs.mongodb.com/manual/release-notes/4.0/#known-issues-in-4-0-0

@cosminn777 I don't think so, I am using MongoDB v3.2.7 and I am experiencing the same slowdown.

@AbdelrahmanHafez you are right. From my tests I get slowdown on both MongoDB 4.0.0 and MongoDB 3.6.5

I am doing many creation/editing operations in DB and I do encounter heavy slowdown.

The issue might be from one of the underlying .js libraries that Mongoose is using.

Are you connecting by name or IP?

On the example you posted, you documented localhost, if you replace that with 127.0.0.1 does it workaround the issue?

Regards,
NewEraCracker

@NewEraCracker interesting!

This actually reduced the connection time from 1.04s to 0.04s, and the read/write operation in https://github.com/Automattic/mongoose/issues/6566#issuecomment-397935727 from 4.563s to 0.577s. Which is way better, yet slightly slower than mongoose v4.14.13.

This is a good place to start debugging the issue.

However, I am curious on how you found out that was the problem, was it a happy accident or by debugging it? If so, please elaborate on your debugging process.

There's a similar issue starting with PHP 5.3 and the native MySQL driver (mysqlnd) that cause same delays. I spotted this back in 2011 I think.

I am guessing something in node or mongoose or a library is now preferring IPv6 but MongoDB is only listening in IPv4.

There are two possible workarounds I am aware of:

  • Use 127.0.0.1 instead of localhost in windows for the connection string.
  • Set the following entry in the hosts file:
    127.0.0.1 localhost

Already switched to 127.0.0.1 in the connection string, and the tests run much faster now.

Thank you!

Yeah I can confirm that switching to 127.0.0.1 from localhost speeds up initial connection from 1000ms to 30ms on windows. Currently digging in to why this is the case.

@NewEraCracker is spot on, good call. The reason for this slowdown is that, starting in mongodb driver 3.0.0, the mongodb driver tries to connect using ipv6 first, and falls back to ipv4 if that fails unless you set the family option or use an ipv4 address like 127.0.0.1. The 1 second delay is because the driver's trying to connect to localhost using ipv6, but presumably either your mongodb server isn't configured to accept ipv6 connections or localhost isn't configured to point to an ipv6 address. Below are the relevant links

https://github.com/mongodb-js/mongodb-core/commit/107bae5083d40b8521b5796469ddbc3ea2063c7a
https://jira.mongodb.org/projects/NODE/issues/NODE-1222?filter=allissues
https://github.com/mongodb-js/mongodb-core/pull/260

As a workaround, do this:

const mongoose = require('mongoose');

const run = async () => {
  let beforeConnection = Date.now();
  await mongoose.connect('mongodb://localhost/somedb', { family: 4 });
  let afterConnection = Date.now();

  console.log(`Time to connect is: ${(afterConnection - beforeConnection) / 1000}s`);
};

run().catch(console.error);

We'll add documentation around this option

Was this page helpful?
0 / 5 - 0 ratings