Next.js и проблема лимита подключений к MongoDB

Next.js — это популярная среда рендеринга на стороне сервера (SSR), которая позволяет разработчикам создавать быстрые и масштабируемые веб-приложения. MongoDB, с другой стороны, представляет собой документно-ориентированную базу данных NoSQL, которая широко используется для хранения и извлечения данных в веб-приложениях. Хотя Next.js и MongoDB могут без проблем работать вместе, существует потенциальная проблема, о которой следует знать разработчикам: проблема с ограничением количества подключений MongoDB.

Что такое проблема ограничения подключения MongoDB?

MongoDB имеет ограничение на количество соединений, которые могут быть открыты в любой момент времени. Это ограничение устанавливается параметром maxPoolSize в драйвере MongoDB. По умолчанию для maxPoolSize установлено значение 100, что означает, что в любой момент времени может быть открыто не более 100 подключений. Если приложение попытается открыть больше подключений, чем максимальное ограничение, MongoDB начнет отклонять новые подключения, и приложение может перестать отвечать на запросы.

В приложении Next.js каждый входящий запрос создает новый экземпляр приложения. Это означает, что если несколько запросов выполняются одновременно, приложение может попытаться открыть больше подключений, чем максимальное ограничение, и может возникнуть проблема ограничения подключений MongoDB.

Как решить проблему ограничения подключения MongoDB в Next.js?

Чтобы решить проблему ограничения соединений MongoDB в приложении Next.js, мы можем использовать шаблон проектирования Singleton. Шаблон Singleton гарантирует, что существует только один экземпляр класса, к которому можно получить глобальный доступ. В контексте приложения Next.js мы можем использовать шаблон Singleton для создания одного соединения MongoDB, которое можно использовать во всех экземплярах приложения.

Неправильное использование

export default async function handler(req, res) {
  const MONGODB_URI = `URI`;
  const MONGODB_DB = 'DB';

  let client = new MongoClient(MONGODB_URI, {
    useUnifiedTopology: true,
    useNewUrlParser: true
  });
  await client.connect();
  let db = client.db(MONGODB_DB);

  try {
    if (req.method === 'GET') {
      const collection = db.collection('COLLECTION');
      let data;
      if (req.query.id) {
        data = await collection.findOne({
          _id: new ObjectId(req.query.id.toString())
        });
      } else {
        data = await collection.find({}).toArray();
      }

      res.status(200).json({
        message: 'Success! ',
        data: data
      });
    }
  } catch (error) {
    console.log(error);
  } 
}

Основная проблема здесь в том, что при каждом запросе открывается новое соединение, а старое остается открытым. Неизбежным исходом в данной ситуации является такой результат. Количество подключений увеличится, и MongoDB перестанет отвечать на запросы.

Плохое решение

export default async function handler(req, res) {
  const MONGODB_URI = `URI`;
  const MONGODB_DB = 'DB';

  let client = new MongoClient(MONGODB_URI, {
    useUnifiedTopology: true,
    useNewUrlParser: true
  });
  await client.connect();
  let db = client.db(MONGODB_DB);

  try {
    if (req.method === 'GET') {
      const collection = db.collection('COLLECTION');
      let data;
      if (req.query.id) {
        data = await collection.findOne({
          _id: new ObjectId(req.query.id.toString())
        });
      } else {
        data = await collection.find({}).toArray();
      }

      res.status(200).json({
        message: 'Success! ',
        data: data
      });
    }
  } catch (error) {
    console.log(error);
  } finally {
    await client.close();
  }
}

Решить проблему можно, закрыв соединение вручную по окончании операции. Однако закрытие старого соединения и открытие нового каждый раз приведет к резкому снижению производительности.

Лучшее решение

Напомним, что у нас есть доступ к глобальному объекту Node.js. Сохраняя соединение MongoDB в глобальном объекте и проверяя, существует ли оно уже, мы можем предотвратить создание нового соединения и избежать любых потенциальных проблем, которые могут возникнуть из-за нескольких соединений.

export default async function handler(req, res) {
  const MONGODB_URI = `URI`;
  const MONGODB_DB = 'DB';

  async function getDb() {
    if (!global.mongoClient) {
      global.mongoClient = new MongoClient(MONGODB_URI, {
        useUnifiedTopology: true,
        useNewUrlParser: true,
      }).connect();
    }

    const client = await global.mongoClient;
    return client.db(MONGODB_DB);
  }

  let db = await getDb();

  try {

    if (req.method === 'GET') {
      const collection = db.collection('COLLECTION');
      let data;
      if (req.query.id) {
        data = await collection.findOne({
          _id: new ObjectId(req.query.id.toString())
        });
      } else {
        data = await collection.find({}).toArray();
      }

      res.status(200).json({
        message: 'Success! ',
        data: data
      });
    }

  } catch (error) {
    console.log(error);
  }

}

Использование шаблона Singleton для создания одного подключения к MongoDB в приложении Next.js может помочь предотвратить проблему с ограничением подключения к MongoDB и гарантировать, что приложение останется стабильным и отзывчивым.

Заключение

Я не совсем уверен, что использование API Next.js является оптимальным подходом для подключения к базе данных и выполнения операций CRUD. Однако бывают случаи, когда нам может понадобиться использовать этот подход. В таких случаях важно убедиться, что приложение стабильно и работает правильно.