0

I've been using Mongoose to connect Mongodb with my nextjs application, everything was working fine in the development mode, but when I deployed the project, even a single user (me) is taking around 60 to 80 connections. And when I shared the link with 3/4 of my friends, the connection has reached 500 connections limit.

Here's my initDB file's code:

import mongoose from "mongoose";

const MONGO_URI = process.env.MONGO_URI;

if (!MONGO_URI) {
  throw new Error("Error Code: grx30xd33d");
}

let cached = global.mongoose;
if (!cached) {
  cached = global.mongoose = { conn: null, promise: null };
}

async function initDB() {
  if (cached.conn) {
    return cached.conn;
  }

  if (!cached.promise) {
    const opts = {
      bufferCommands: false,
      useNewUrlParser: true,
      useUnifiedTopology: true,
      maxPoolSize: 50,
      wtimeoutMS: 2500,
      useNewUrlParser: true,
    };

    mongoose.set("strictQuery", false);
    cached.promise = mongoose.connect(MONGO_URI, opts).then((mongoose) => {
      return mongoose;
    });
  }

  try {
    cached.conn = await cached.promise;
  } catch (e) {
    cached.promise = null;
    throw e;
  }

  return cached.conn;
}

export default initDB;

And this is my page which is using the initDB.

import initDB from "../../../helpers/initDB";
import directorySchema from "../../../models/directory/directorySchema ";

export default async function handler(req, res) {
  await initDB();

  return new Promise(async (resolve, reject) => {
    try {
      const userProf = await directorySchema
        .find()
        .limit(20)
        .sort({ _id: -1 })
        .then(async (response) => {
          if (response !== null) {
            res.json({
              success: true,
              data: response,
              message: "Data Fetched Successfully",
            });
          } else {
            return res.json({
              success: false,
              message: "Invalid data response",
            });
          }
        })
        .catch((error) => {
          console.log(error);
          res.json(error);
          res.status(405).end();
          return reject();
        });
    } catch (error) {
      console.log(error);
    }
  });
}

The response is working fine, I'm getting the required response, but the connection limit is reaching in mongodb.

Here's the screenshot of connection while writing this question. enter image description here

Agent K
  • 359
  • 4
  • 17
  • 1
    What's in your directorySchema? It's not obvious whether you use the cached connection in mongoose or not. Is it vercel / serverless app? – Alex Blex Aug 13 '23 at 12:01
  • @AlexBlex | Just to make it easier, I've added the entirely fresh code in my github repo: https://github.com/eraviraltiwari/test please check you can use the .env file as well, I've made everything public (as this is just a test one) – Agent K Aug 13 '23 at 13:01
  • It works all fine in the localhost, i.e., no connection problems with mongodb when I'm on localhost. But when I upload it to vercel, i am facing issues. If you open the alerts page, refresh it 4/5 times, and then open any article and refresh 4/5 times, and go back and forth, you will see connections are increasing rapidly without any reason. – Agent K Aug 13 '23 at 13:02
  • @AlexBlex I hope you saw the link? – Agent K Aug 14 '23 at 06:13
  • hey mate, I appreciate it's a burning issue for you, but we are all volunteers here and contribute when we can. You did all right and put fair amount of effort to crawl SO for the answer, but you must have missed this one https://stackoverflow.com/questions/63208960/vercel-creates-new-db-connection-for-every-request. It's specific to Vercel. They are aware of the issue. The official recommendation I've got on their discord events - use Atlas data API or create own REST service. Don't use mongodb native driver, and thus, no mongoose either. – Alex Blex Aug 14 '23 at 08:44

1 Answers1

1

The issue is not with next.js but with Vercel hosting. As OP stated the problem is not reproducible locally.

The issue is that serverless nature of Vercel is not quite compatible with thick stateful mongodb client. Mongodb native nodejs driver is designed to run permanently, manage socket connections to reuse them for multiple requests, keep track of replica set topology etc. Vercel on the other hand starts a new nodejs process on each HTTP request, which makes it impossible to reuse already opened connections.

The "answer from a reputable source" part:

The issue is being discussed in https://github.com/vercel/next.js/discussions/12229 and is still open at the time of answering the question.

The most practical recommendation is from the current maintainer of the next.js repo Lee Robinson is

You can use Mongo's Data API.

https://www.mongodb.com/docs/atlas/api/data-api-resources/

It indeed solves the problem with connection pool for the cost of:

  • performance: each db request is and HTTP call which x100 times slower on simple requests.
  • maintenance: it requires to keep external data API in sync with vercel app
  • dependencies: you won't use native driver, and consequently you cannot use any libraries that depend on it - mongoose in particular in context of the question.

The less dramatic approach is recommended by a contributor Chris Anthony:

  1. Ensure the connection is cached, and that you deal with the promised connection correctly. - see Next example [https://github.com/vercel/next.js/blob/canary/examples/with-mongodb/util/mongodb.js] (https://github.com/vercel/next.js/blob/canary/examples/with-mongodb/util/mongodb.js)

  2. Ensure you have configured the client to kill idle connections. Where maxIdleTimeMS isn't supported, socketTimeoutMS will do.

  3. Make sure you close all cursors. Previously , I was letting Mongo server do this.. but it was woeful at that. Now I make sure I 1st store the response , then manually close the cursor on each end point.

E.g a config with 1 connection in a pool:

    const config = {
        useNewUrlParser: true,
        useUnifiedTopology: true,
        maxPoolSize: 1,
        minPoolSize: 1,
        socketTimeoutMS: 10000,
        serverSelectionTimeoutMS: 10000,
        maxIdleTimeMS: 10000,
    };

will result with 1 connection per http request, but the driver won't be able to run requests in parallel. You will need to experiment with exact timeouts and pool size to find balance.

It's far from efficient use of the driver, but at least it will unblock use of dependant libraries like mongoose.

Closing connection is a bit tricky, if you do it before all query promises are resolved, you will get "MongoError: Topology is closed, please connect" error. It's especially true for Mongoose, as it's designed to use higher abstraction level and do not manage connections manually.

Alex Blex
  • 34,704
  • 7
  • 48
  • 75
  • Thanks for the time, I got almost every point mentioned by you. I just have a question, since I'm in the initial stage, can I move to nuxt.js instead of next.js ? Or if Nuxt.js will also have similar issues? I'm sorry if the question is off-topic. Please. – Agent K Aug 16 '23 at 14:37
  • 1
    ok, apparently it wasn't that clear. I updated the answer. next.js is fine as long as you use proper hosting, not serverless things like Vercel. The same apply to nuxt, noxt, nixt and any other frameworks, and js derivatives like typescript, coffescript, and potentially espressoscript if someone create another "js killer" language. It includes nodejs runtime, and will include deno and bun when mongo driver is ported to there. Let me repeat it once again - the issue is not with the language/framework but with the hosting platform. – Alex Blex Aug 16 '23 at 16:27