Статьи

Глубокое погружение в пул соединений

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

Соединительные бассейны

Создание новых аутентифицированных соединений с базой данных стоит дорого. Таким образом, вместо создания и уничтожения соединений для каждого запроса к базе данных, вы хотите максимально использовать существующие соединения. Это где пул подключений приходит.

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

Масштаб соединения

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

Открытие слишком много соединений

С другой стороны, хотя это и менее распространенная проблема, возникает проблема создания слишком большого количества объектов MongoClient, которые никогда не закрываются. В этом случае, вместо оттока, вы получаете постоянное увеличение количества соединений с вашей базой данных, так что у вас открыты десятки тысяч соединений, когда ваше приложение почти наверняка связано с гораздо меньшим количеством. Поскольку каждое соединение требует ОЗУ, вы можете потратить значительную часть своей памяти на соединения, что также отрицательно скажется на производительности вашего приложения.

Хотя каждое приложение отличается и общее количество подключений к вашей базе данных будет в значительной степени зависеть от того, сколько клиентских процессов или серверов приложений подключено, по нашему опыту, любое количество подключений, превышающее 1000-1500 подключений, должно удивить, и большинство Время вашего приложения потребует гораздо меньше, чем это.

MongoClient и пул соединений

В большинстве языковых драйверов MongoDB реализован класс MongoClient, который при правильном использовании автоматически обрабатывает пулы соединений.

Синтаксис зависит от языка, но часто вы делаете что-то подобное для создания нового клиента с поддержкой пула соединений для вашей базы данных:

mongoClient = новый MongoClient (URI, connectionOptions);

Здесь объект mongoClient хранит ваш пул соединений и предоставит соединения вашему приложению по мере необходимости. Вам следует постараться создать этот объект один раз, когда ваше приложение инициализируется, и повторно использовать этот объект во всем приложении для общения с базой данных. Наиболее распространенная проблема с пулами соединений, которую мы видим, это результаты приложений, которые слишком часто создают объект MongoClient, иногда при каждом запросе базы данных. Если вы сделаете это, вы не будете использовать свой пул соединений, поскольку каждый объект MongoClient поддерживает отдельный пул, который не используется вашим приложением.

Пример с Node.js

Давайте рассмотрим конкретный пример с использованием драйвера Node.js.

Создание новых подключений к базе данных с помощью драйвера Node.js выполняется следующим образом:

mongodb.MongoClient.connect(URI, function(err, db) {
  // database operations
});

Синтаксис для использования MongoClient здесь немного отличается от других драйверов, учитывая однопоточный характер Node, но концепция та же. Вы хотите вызывать «connect» только один раз во время фазы инициализации приложения по сравнению с каждым запросом базы данных.

Давайте внимательнее посмотрим на разницу между правильными поступками и неправильными поступками.

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

Рассмотрим следующие примеры:

var express = require('express');
var mongodb = require('mongodb');
var app = express();

var MONGODB_URI = 'mongo-uri';

app.get('/', function(req, res) { 
  
  // BAD! Creates a new connection pool for every request
  
  mongodb.MongoClient.connect(MONGODB_URI, function(err, db) {
  if(err) throw err;
  
  var coll = db.collection('test');

    coll.find({}, function(err, docs) {
      docs.each(function(err, doc) {
        if(doc) {
          res.write(JSON.stringify(doc) + "\n");
        }
        else {
          res.end();
        }
      });
    });
  });  
});

// App may initialize before DB connection is ready

app.listen(3000);
console.log('Listening on port 3000');

Первый (без объединения):

  • вызывает connect () в каждом обработчике запросов
  • устанавливает новые соединения для каждого запроса (отток соединений)
  • инициализирует приложение (app.listen ()) перед установлением соединения с базой данных
  • var express = require('express');
    var mongodb = require('mongodb');
    var app = express();
    
    var MONGODB_URI = 'mongodb-uri';
    var db;
    var coll;
    
    // Initialize connection once
    
    mongodb.MongoClient.connect(MONGODB_URI, function(err, database) {
      if(err) throw err;
     
      db = database;
      coll = db.collection('test');
    
      app.listen(3000);
      console.log('Listening on port 3000');
    });
    
    // Reuse database/collection object 
    
    app.get('/', function(req, res) { 
      coll.find({}, function(err, docs) {
        docs.each(function(err, doc) {
          if(doc) {
            res.write(JSON.stringify(doc) + "\n");
          }
          else {
            res.end();
          }
        });
      });
    });
      

Второй (с пулами):

  • звонки соединяются () один раз
  • повторно использует переменную базы данных / коллекции (повторно использует существующие соединения), ожидает инициализации приложения, пока не будет установлено соединение с базой данных

Если вы запустите первый пример и обновите свой браузер достаточно много раз, вы быстро увидите, что ваш MongoDB испытывает трудности с обработкой потока соединений и завершит работу.

Дальнейшее рассмотрение — размер пула подключений

Большинство драйверов MongoDB поддерживают параметр, который устанавливает максимальное количество соединений (размер пула), доступных вашему приложению. Размер пула соединений можно рассматривать как максимальное количество одновременных запросов, которые может обслуживать ваш драйвер. Размер пула по умолчанию варьируется от драйвера к драйверу, например, для Node он равен 5, тогда как для Python он равен 100. Если вы ожидаете, что ваше приложение получит много одновременных или длительных запросов, мы рекомендуем увеличить размер пула — отрегулируйте соответствующим образом!