Как увеличение пула соединений к базе данных может уменьшить пропускную способность сервиса?
Увеличение пула соединений может уменьшить пропускную способность, если база данных уже близка к пределу CPU, дискового ввода-вывода, памяти или внутренних блокировок. Дополнительный параллелизм тогда не устраняет очередь, а усиливает конкуренцию за общие ресурсы, повышает накладные расходы и увеличивает задержки.
Пулы соединений появились, чтобы не создавать новое соединение с базой данных для каждого запроса. Установка соединения обычно требует сетевого обмена, аутентификации и выделения ресурсов, поэтому повторное использование ограниченного набора соединений снижает накладные расходы и стабилизирует нагрузку.
Однако пул не увеличивает вычислительные возможности базы данных. Он лишь определяет, сколько операций сервис может одновременно отправить в базу. Поэтому размер пула должен соответствовать реальной способности базы обслуживать запросы, а не максимальному числу потоков приложения.
Пусть запросы приложения проводят много времени в базе данных. При маленьком пуле часть запросов ждёт свободное соединение на стороне приложения, а при слишком большом запросы одновременно конкурируют внутри базы за процессор, диски, буферный кэш, блокировки и сетевые ресурсы.
Если база становится перегруженной, растёт время выполнения каждого запроса. Соединения удерживаются дольше, очередь увеличивается, а новые запросы начинают истекать по тайм-ауту. В результате увеличение пула может снизить фактическую пропускную способность и повысить долю ошибок.
Ключевой механизм — насыщение ресурса. Пока база недогружена, добавление соединений позволяет эффективнее использовать её параллелизм. После достижения предела новые соединения только увеличивают число одновременно конкурирующих операций.
Особенно заметен эффект при конфликтующих обновлениях, тяжёлых сортировках, недостатке памяти для рабочих наборов, ограниченном дисковом вводе-выводе или высокой конкуренции за блокировки. В таких условиях большее число соединений повышает переключения контекста, вытеснение полезных данных из кэша и взаимное ожидание транзакций.
Настраивать пул следует по измерениям: загрузке ресурсов базы, задержкам запросов, времени ожидания соединения, числу активных и ожидающих соединений, тайм-аутам и пропускной способности. Важно учитывать суммарный размер пулов всех экземпляров приложения: после масштабирования каждый экземпляр может сохранить свой пул, многократно увеличив общую нагрузку на базу.
Практический подход — найти диапазон, в котором увеличение пула ещё повышает пропускную способность, а затем остановиться до начала резкого роста задержек. Полезны тайм-аут ожидания соединения, ограничение длительности запросов и отдельное управление тяжёлыми операциями, но эти меры не заменяют оптимизацию запросов и устранение узких мест.
Компромисс состоит в том, что слишком маленький пул недоиспользует базу и создаёт очередь в приложении, а слишком большой перегружает базу и ухудшает всех потребителей. Поэтому универсального числа соединений нет: оно зависит от характера запросов, аппаратных ресурсов, числа экземпляров и допустимых SLO по задержке и ошибкам.
Сервис масштабировали с четырёх до двадцати экземпляров. На каждом экземпляре оставили пул из двадцати соединений, поэтому потенциальное число соединений к базе выросло с восьмидесяти до четырёхсот. Сначала пропускная способность увеличилась, но затем задержка запросов и число тайм-аутов резко выросли, хотя CPU приложения оставался свободным.
Рассматривались три варианта. Увеличить пул было просто, но это усилило бы конкуренцию; добавить ещё экземпляры — полезно для веб-слоя, но вредно для уже насыщенной базы; ограничить суммарную нагрузку и исследовать запросы — дольше, зато устраняет первопричину.
Выбрали третий вариант: уменьшили пул на экземпляр с учётом общего числа экземпляров, добавили метрики ожидания соединения и задержек запросов, а затем оптимизировали наиболее дорогие операции. В результате база перестала работать в режиме постоянного насыщения, а сервис получил более предсказуемую задержку; свободный CPU приложения при этом не считался признаком отсутствия узкого места.
Ответ: Приложение может большую часть времени ждать базу, сеть или свободное соединение, поэтому его CPU останется низким. Маленький пул выявляется не по загрузке CPU, а по времени ожидания соединения, доле занятых соединений, очереди запросов и изменению пропускной способности при контролируемом увеличении пула.
Ответ: Пул обычно задаётся для одного экземпляра, а база видит сумму соединений от всех экземпляров. При росте числа экземпляров суммарная конкуренция может увеличиться даже без изменения нагрузки на один экземпляр. Поэтому лимит нужно оценивать глобально и оставлять резерв для административных подключений, фоновых задач и других потребителей базы.
Ответ: Длинная транзакция удерживает соединение и, возможно, блокировки значительно дольше обычной. При большом пуле одновременно накапливается больше таких транзакций, растёт конкуренция и время ожидания блокировок, а новые запросы удерживают соединения ещё дольше. В результате возникает положительная обратная связь: задержка увеличивает занятость пула, а занятость пула увеличивает задержку.