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

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

По мере масштабирования вашего приложения количество запросов к MongoDB увеличивается. Сложность вашего приложения также растет, поэтому требования к запросам становятся больше - также как и потенциально скрытые узкие места.

Допустим, вы работаете над приложением с двумя типами пользователей:

1) Обычные пользователи приложения, ожидающие быстрой загрузки. На этих пользователей приходится большая часть трафика приложений.

2) Администраторы системы, которым необходимо выполнять задачи, которые могут охватывать тысячи пользователей и связанных с ними элементов - административные действия, требующие интенсивного использования и ресурсоемких.

Первая группа пользователей (обычные пользователи) - это платящие клиенты, которых в любой момент времени тысячи. Любые действия, которые эти пользователи делают в приложении, должны быть очень быстрыми - например, вход в систему, просмотр страниц и другие функции приложения. Запросы к базе данных, необходимые для этих функций, очень малы и должны выполняться очень быстро.

Функции приложения, используемые вторым набором пользователей (администраторов), обычно очень хорошо работают с большими объемами данных, что, в свою очередь, требует больших и сложных запросов к базе данных.

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

Чтобы понять, что здесь произошло, нам нужно понять размер пула MongoDB.

Пример:

Полный пример приложения, приведенный ниже, можно найти на Github. Примеры кода в этой статье сосредоточены на драйвере MongoDB для Node.js, однако основные концепции остаются неизменными для различных драйверов MongoDB.

В приведенном выше примере кода у нас есть минимальный сервер Express.js с двумя конечными точками, /slow и /fast. Создается одно соединение с базой данных, которое затем используется для всех запросов MongoDB - это намного эффективнее, чем открытие и закрытие TCP-соединений для каждого запроса, который приложение делает к базе данных.

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

Маршрут /slow использует пример запроса, результат которого будет возвращен за 5 секунд - он предназначен для имитации медленного запроса в вашем приложении, такого как администратор, загружающий каждого пользователя, или приложение со списком задач, загружающее большое количество элементов.

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

В примере кода переменная среды MONGO_POOLSIZE может использоваться для динамического изменения размера пула при запуске приложения. После этого мы можем приступить к тестированию нашего приложения с различными конфигурациями пулов. Чтобы точно это проверить, мы можем использовать инструмент нагрузочного тестирования приложения artillery.io.

Приведенный выше файл конфигурации yaml можно использовать с клиентом artillery.io для запуска теста производительности. Этот тест производительности будет нацелен на наше запущенное приложение на http://localhost:3000 и имитировать нового пользователя, прибывающего к приложению каждую секунду в течение 5 секунд. Каждый пользователь посетит как /slow, так и /fast конечную точку и будет ждать ответа от сервера.

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

$ MONGO_POOLSIZE=3 node index (запускает приложение с размером пула 3)

$ artillery run loadtest.yaml

Если размер пула равен трем, все тесты завершены успешно с максимальным временем отклика 9017 мс (наш медленный запрос имитирует запрос, который занимает 5000 мс).

Что произойдет, если мы уменьшим размер пула до 1? Это означает, что наше соединение с базой данных сможет одновременно выполнять только один запрос MongoDB.

$ MONGO_POOLSIZE=1 node index

$ artillery run loadtest.yaml

Хотя каждый запрос был успешным, максимальное время ответа увеличилось с 9017 мс до 21142,7 мс. Почему? Наши одновременные запросы теперь были заблокированы первым медленным запросом, поэтому в результате пострадали все наши быстрые запросы.

Что будет, если мы увеличим размер пула до 10? Это означает, что наше соединение с базой данных сможет одновременно выполнять до десяти запросов MongoDB.

$ MONGO_POOLSIZE=10 node index

$ artillery run loadtest.yaml

Максимальное время ответа теперь составляет 5019 мсек - время, необходимое для выполнения нашего примера медленного запроса. Это показывает, что никакие другие запросы не блокировали соединение с базой данных во время этого теста, а это означает, что наши /slow и /fast конечные точки предоставили ожидаемый результат пользователю, не будучи узкими местами.

Итак, что это за настройка размера пула и почему она тормозит масштабирование моего приложения?

Представьте соединение MongoDB как туннель. Размер пула определяет, сколько полос трафика может одновременно входить в туннель. Если у вас однополосный туннель с автобусом и Ferrari, Ferrari не сможет проехать через туннель, пока автобус не проедет. Это означает, что иногда медленный автобус может заставить даже Ferrari замедлиться, если на нем недостаточно полос.

Когда мы добавляем дополнительные полосы, то есть увеличиваем размер нашего пула соединений, мы можем освободить место для Ferrari, чтобы он мог быстро проехать и не задерживаться на медленной шине.

Что мне делать с этим?

Если вы ожидаете, что ваше приложение потребует длительных запросов или множества одновременных запросов, вам следует протестировать свое приложение, моделируя ожидаемую производственную нагрузку с помощью такого инструмента, как JMeter или artillery.io, чтобы исследовать влияние увеличения размера пула для Подключение MongoDB. Оптимальный размер пула для вашего приложения также должен учитывать других клиентов, которые используют один и тот же сервер MongoDB, поскольку использование ресурсов на сервере MongoDB также должно отслеживаться.

Такие инструменты, как Azure Application Insights, можно использовать для измерения производительности запросов MongoDB в вашем приложении, как подробно описано в этом сообщении блога.