Данные и базы

5 мин чтенияОпубликовано Обновлено

Connection Pooling: почему больше приложений не должно означать больше хаоса в базе

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

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

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

Считать нужно все пулы одновременно

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

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

Размер пула задаётся в конкретной библиотеке или прокси. Иногда отдельные пулы создаются для каждой пары «база — пользователь», поэтому значение одного параметра ещё не описывает весь лимит. Для центрального pooler также важно число его экземпляров: несколько независимых прокси могут каждый открыть разрешённое им количество соединений.

Общий бюджет должен оставлять возможность диагностики и восстановления. Когда приложения заняли всё, администратору всё равно нужно подключиться и разобраться. В PostgreSQL предусмотрены резервируемые подключения. Повышение max_connections само по себе не добавляет вычислительной мощности и увеличивает требования к некоторым ресурсам сервера.

Свободных соединений нет. База перегружена?

Не обязательно. Возможен слишком маленький пул при ещё свободной базе. Но бывает и обратная ситуация: соединения долго заняты запросами, ожидают блокировок или не возвращаются после ошибки. Увеличить лимит в таком случае — разрешить большему числу операций ждать ту же проблему.

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

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

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

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

Очередь ожидания должна иметь конец

Ограниченный пул переносит часть нагрузки в ожидание. Это нормально, пока очередь укладывается в обещанное время ответа. Бесконечное ожидание не спасает клиента: запрос держит память и другие ресурсы приложения, пользователь повторяет действие, а работы становится ещё больше.

Нужны предел ожидания, общий срок выполнения операции и понятное поведение при отказе. Если соединение не получено вовремя, не стоит запускать тяжёлый запрос после того, как клиент уже ушёл. Автоматические повторы тоже должны учитывать ограничения, иначе отказ пула превратится в новый поток попыток.

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

Внешний pooler меняет правила работы с сессией

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

Поэтому между транзакциями нельзя без проверки полагаться на состояние прежней серверной сессии. Сессионные блокировки, временные объекты и настройки требуют внимания. Возможности работы с подготовленными запросами зависят от режима, версии и конфигурации. Здесь нужна проверка драйвера и приложения по таблице совместимости PgBouncer, а не общий запрет или обещание, что всё будет работать.

Повторное использование соединений требует аккуратного обращения с состоянием и внутри приложения. Контекст одного клиента не должен случайно остаться для следующего. Пул не является механизмом авторизации; проверки доступа и корректная очистка состояния нужны независимо от способа выдачи подключений.

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

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