Содержание

пятница, 22 апреля 2011 г.

Настройка пула соединений к базе данных за firewall

В данном топике я опишу на какие настройки database connection pool надо смотреть, чтобы ваше приложение, соединяющееся с базой данных через firewall, работало быстро и без ошибок. Основная проблема состоит в том, что если в вашем пуле есть холостые соединения (idle connections), не использовавшиеся некоторое время, то они вполне могут быть закрыты firewall, что вызовет определенные проблемы. Однако решать их можно разными способами, причем эффективность сильно зависит от реализации пула. Так же данный топик будет интересет тем, кто собирается мигрировать свой пул на другого провайдера, потому что, как показывает практика, одинаковые по смыслу настройки могут иметь немного разную функциональность и совершенно разные значения по умолчанию.


Основные приемы решения обозначенной проблемы - это либо выкидывать соединения простоявшие без дела определенное время и создавать вместо них новые, либо периодически прогревать их. Эти опции часто завязаны друг на друга, причем их не две, а целая пачка, так что для каждого конкретного пула надо внимательно читать документации, чтобы не получилось так, что вы выставили не все опции, и в итоге желаемая функциональность даже не включилась. Прогревание соединений можно осуществить при помощи их валидации в фоновом процессе. Причем валидация при получении из пула не подойдет, потому-что, во-первых, вы добавляете к каждой бизнес транзакции еще одно синхронное обращение к базе данных, что уменьшает throughput, во-вторых, если соединение уже закрыто firewall, то вы можете зависнуть на довольно продолжительное время на этом самом валидирующем запросе. Валидация на возвращении соединения в пул с точки зрения прогревания бесполезна, так как в этом случае ваше соединение скорее всего уже прогрето только что выполнившимися запросами приложения. Так что нас будет интересовать функция валидации именно в фоновом режиме, пока соединения находятся в холостом состоянии (idle).

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

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

DBCP

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

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

Чтобы включить прогревание соединений в DBCP вам надо обязательно проставить целых три параметра, иначе ничего не заработает: validationQuery, testWhileIdle и timeBetweenEvictionRunsMillis. timeBetweenEvictionRunsMillis - тот же самый параметр, что и для инвалидации холостых соединений. Дело в том, что как проверка времени простоя соединений, так и прогревание происходят в одном потоке, а данный параметр указывает на сколько времени поток будет засыпать.

Будьте внимательнее, в данном пуле есть еще параметр numTestsPerEvictionRun, который определяет количество обработанный соединений за один раз. По-умолчанию он равен 3. Таким образом, если вы знаете, что firewall закрывает холостые соединения через время, которые мы обозначим как FIREWALL_INTERWAL, то при выборе параметров пула следующее неравенство должно выполняться с некоторым запасом:
maxIdle < (FIREWAL_INTERVAL/timeBetweenEvictionRunsMillis)*numTestsPerEvictionRun
Оно будет гарантировать, что все ваши соединения будут всегда прогреты.

Таким образом, чтобы включить только проверку холостых соединений, достаточно проставить параметры minEvictableIdleTimeMillis и timeBetweenEvictionRunsMillis (прогревка соединений по умолчанию отключена). Если же вы хотите, чтобы проверки не было, а работала только прогревка соединений, то надо выставить validationQuery, testWhileIdle и timeBetweenEvictionRunsMillis, а параметр minEvictableIdleTimeMillis необходимо выставить -1.

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

Для того чтобы разово сбросить все холостые соединения, можно выставить параметр maxIdle через JMX в 0, затем дать приложению выбрать все холостые соединения из пула и вернуть значение обратно.

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

Tomcat Connection Pool

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

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

В поведении maxIdle тоже есть различия. Если работает фоновый поток проверки соединений (timeBetweenEvictionRunsMillis > 0), то значение данного параметра игнорируется. Количество соединений в пуле будет расти до значения maxActive и уменьшиться только если какое-то из соединений станет невалидным. Таким образом для того, чтобы в запущенном приложении сбросить все холостые соединения, в отличии от DBCP, надо выставить maxAge = 1.

Еще важная особенность реализации в том, что если вы изначально отключили timeBetweenEvictionRunsMillis (в данном пуле, в отличие от DBCP, оно по умолчанию равно 5 секунд, что, кстати, по-моему, слишком уж мало), то задать его после запуска приложения через JMX уже не получится, таким образом вам обязательно придется перезапускать приложение. А minEvictableIdleTimeMillis по умолчанию равен 60 секунд, против 30 минут в DBCP. Так что имейте это ввиду при миграции.

Так же формула
maxIdle < (FIREWAL_INTERVAL/timeBetweenEvictionRunsMillis)*numTestsPerEvictionRun
для данного пула не верна, поскольку параметр numTestsPerEvictionRun к пулу неприменим, а проверка проводиться для всех соединений за раз. Таким образом достаточно выставить timeBetweenEvictionRunsMillis чуть меньше чем время FIREWAL_INTERVAL и ничего не высчитывать.

Подробности про данный пул можно почитать тут. Исходники есть здесь, а бинарники тут.

Oracle Universal Connection Pool (OUCP)

Данный пул бесплатный, не является open source, зато имеет хорошую документацию. Если вы работаете с базой данных Oracle, то наверняка рассматривали возможность использование данного пула. Я же не могу не упомянуть о нем, просто потому, что он имеет еще одну настройку специально адресованную для решения нашей проблемы. Maximum Connection Reuse Time заставит пул закрывать ваше соединение через указанное время. Для того, чтобы все работало, необходимо задать так же Timeout Check Interval. А еще мне показалось, что данный параметр ломает поведение Time-To-Live Connection Timeout. Если же вы хотите функционал аналогичный описанным пулам выше, то можно использовать Inactive Connection Timeout. Честно говоря, я не понимаю, почему для работы с firewall документация рекомендует использовать именно первый параметр. Наверное, бывают firewall, которые закрывают даже работающие соединения после определенного времени. Так что если у вас именно такой жесткий firewall, то вам придется использовать именно этот пул. Кстати, ничего о значениях по умолчанию в документации не указано, поэтому от греха подальше всегда выставляйте их явно. Возможности валидации соединений я в данном пуле не обнаружил.

c3p0

Данный пул идет в поставке с hibernatе, поэтому довольно часто используется различными приложениями. В своем арсенале имеет достаточное количество всяких разных настроек. Проверка неиспользуемых соединений включается параметрами c3p0.maxIdleTime и c3p0.idleConnectionTestPeriod (hibernate.c3p0.timeout и hibernate.c3p0.idle_test_period, если вы выставляет их в конфигурации хибернейта). По умолчанию эти параметре отключены, так что если вы хотите выбрать стратегию инвалидации не используемых соединения, то вам необходимо явно выставить оба этих параметра.

Реализовать стратегию прогревания соединений с помощью validation query невозможно, так как в арсенале данного пула не предусмотрена опция выполнения валидирующего запроса в фоновом режиме. Доступны только опции testConnectionOnCheckin testConnectionOnCheckout, которые, как я уже писал выше, не имеют особого смысла в данной стратегии.

BoneCP

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

Проверка неиспользуемых соединений контролируется параметром idleConnectionTestPeriodInMinutes, который по умолчанию равен 60 минут.

Валидация соединений, контролируется параметрами idleMaxAgeInMinutes и сonnectionTestStatement. По умолчанию idleMaxAgeInMinutes равен 240 минут, т.е. его значение по умолчанию не очень коррелирует с idleConnectionTestPeriodInMinutes, так как получается, что соединение будет закрыто раньше, чем на нем можно будет запускать валидирующий запрос. Поэтому выбирайте стратегию и отключайте один из этих параметров. Кстати, если вы не выставили сonnectionTestStatement, то в отличие от DBCP и Tomcat JDBC pool валидация все равное будет происходить, только использоваться будет запрос к метадате, что конечно же намного медленнее, чем простой SELECT к какой-нибудь пустой табличке.

После всех исследований, описанных выше, я остановился на Tomcat пуле соединений. Как проверенное, быстрое и наиболее гибкое решение.