Сервис начинает нарушать SLA после увеличения пула соединений к базе данных. Как проверить, не перенесло ли это изменение узкое место вместо его устранения?
Нужно сравнить несколько размеров пула при одинаковой нагрузке и проверить не только задержку сервиса, но и состояние базы данных. Если после увеличения пула пропускная способность почти не растёт, а растут число активных соединений, блокировки, ожидание диска или конкуренция за CPU, узкое место было перенесено или усилено, а не устранено.
Пулы соединений появились как способ не создавать физическое соединение с базой данных для каждого запроса. Повторное использование соединений уменьшает накладные расходы на установление соединения и позволяет ограничить количество одновременно выполняемых операций.
Однако пул является не только оптимизацией, но и ограничителем конкуренции. Поэтому его размер должен соответствовать способности базы данных обрабатывать параллельную работу, а не просто числу потоков или экземпляров приложения.
Слишком маленький пул создаёт очередь внутри приложения: запросы ждут свободное соединение, хотя база данных ещё может принять дополнительную работу. Увеличение пула в таком случае способно уменьшить задержку.
Слишком большой пул действует иначе. Он может отправить в базу данных больше одновременно выполняемых операций, чем она способна эффективно обслужить. Возникают конкуренция за CPU и диск, блокировки, рост времени выполнения запросов и увеличение очередей уже внутри базы данных. В результате приложение получает больше соединений, но пользователи — более высокую задержку.
Нельзя делать вывод только по тому, что после изменения вырос достигнутый RPS. Рост пропускной способности может сопровождаться ухудшением p95, p99, временем ожидания соединения или долей тайм-аутов.
Сначала фиксируют воспроизводимый профиль нагрузки: одинаковые операции, их доли, интенсивность запросов, длительность теста и критерии SLA. Затем проводят серию прогонов с несколькими значениями пула, не меняя одновременно другие параметры приложения и базы данных.
Для каждого прогона сопоставляют:
Если пул мал, его увеличение обычно уменьшает ожидание соединения и улучшает задержку без резкого роста внутренних ожиданий базы данных. Если пул уже достаточно велик, дальнейшее увеличение даёт насыщение: RPS выходит на плато, а задержка и очереди растут. Это признак того, что ограничение находится ниже по цепочке — например, в CPU, дисковой подсистеме, блокировках или конкретном типе запросов.
Важно учитывать суммарную конкуренцию. Пул размером 30 соединений на одном экземпляре приложения означает 300 потенциальных соединений при десяти экземплярах. Анализировать нужно не только размер пула на экземпляр, но и общий объём параллельной работы, включая фоновые задачи и другие потребители базы данных.
Полезно отдельно проверить время ожидания соединения и время выполнения операции после получения соединения. Если растёт первое, пул может быть слишком мал. Если первое невелико, но растёт второе вместе с ожиданиями базы данных, увеличение пула, скорее всего, уже не помогает и может усугублять конкуренцию.
Компромисс состоит в выборе точки, где пул достаточно велик для использования доступной мощности базы данных, но не настолько велик, чтобы создавать избыточную конкуренцию. Оптимальное значение нельзя надёжно вывести только из конфигурации приложения: его проверяют экспериментом на реалистичной нагрузке.
Интернет-магазин увеличил пул с 10 до 40 соединений на экземпляр, потому что при нагрузочном тесте запросы долго ждали свободное соединение. В первом прогоне время ожидания действительно снизилось, но p99 вырос, а база данных стала чаще показывать ожидания блокировок и диска. Пропускная способность почти не изменилась.
Рассматривались два варианта. Можно было оставить большой пул: это уменьшало очередь в приложении, но увеличивало конкуренцию в базе данных и риск перегрузки при масштабировании. Можно было вернуть меньший пул без дополнительного анализа: это снижало давление на базу, но сохраняло часть искусственной очереди.
Команда провела серию тестов с разными размерами пула, отдельно измеряя ожидание соединения, время выполнения SQL-операций и показатели базы данных. Выяснилось, что после определённого значения ожидание в приложении почти исчезало, а дальнейшее увеличение пула только повышало задержку запросов к базе.
Выбрали значение около точки насыщения базы с небольшим запасом и дополнительно оптимизировали наиболее конкурентный запрос. В результате p99 вернулся в SLA, а увеличение числа экземпляров приложения перестало приводить к пропорциональному росту числа соединений без контроля.
Нет, это только гипотеза. Ожидание может расти потому, что соединения удерживаются медленными запросами, транзакциями или блокировками. Нужно разделить время ожидания свободного соединения и время фактического выполнения операции, а затем проверить, что происходит в базе данных с уже выданными соединениями.
CPU — лишь одна из возможных границ. Причиной могут быть диск, сетевые ожидания, блокировки, лимит IOPS, журналирование транзакций или конкуренция за внутренние структуры базы. Низкий общий CPU также может скрывать перегрузку одного ядра или ожидание ресурсов, поэтому вывод делают по совокупности метрик, а не по одному проценту загрузки.
Виртуальный пользователь не обязательно постоянно выполняет операцию с базой данных: между действиями могут быть паузы, запросы могут обслуживаться кэшем, а одна операция может занимать соединение разное время. Кроме того, в реальной системе есть несколько экземпляров приложения и другие потребители базы. Размер пула определяют по наблюдаемой конкуренции и предельной способности базы, проверяя суммарный эффект масштабирования.