Содержание

четверг, 31 марта 2011 г.

Работа с базой данных за firewall: какие timeout бывают, как и зачем их проставлять

В приложениях, работающих с сетью, очень важно выставлять различные timeout, особенно если соединения идут через firewall. В данном топике я расскажу какие timeout бывают при работе с базой данных и как их выставлять, если у вас нет прямого доступа к сокетам и\или JDBC (например вы работаете через spring, hibernate, DataSource, JPA, connection pool).

1. Timeout при открытии соединения с базой данных

Данный тип timeout нужно выставлять для того, чтобы быстро диагностировать проблему с открытием соединения, которую вы сразу сможете увидеть в логах, а не гадать, почему вся ваша система встала колом и судорожно пытаться снять треддамп. Вышеупомянутый timeout выставляется при открытие соединения, и в Java Socket API задается вторым параметром метода Socket.connect. В JDBC API это можно сделать статическим методом DriverManager.setLoginTimeout(int). Но в enterprise приложении у вас обычно нет доступа ни к тому, ни к другому, зато если вы можете дотянуться до DataSource, то вам поможет метод CommonDataSource.setLoginTimeout(int) . Но, к сожалению, некоторые реализации, например, такая как DBCP не поддерживает этот метод. Если вы работаете с БД через hibernate, то вам поможет свойство конфигурации hibernate.c3p0.timeout.

2. Timeout на запрос в базу данных

Данный тип timeout может оказаться полезным, если вы хотите ограничить по времени какой-нибудь очень длинный запрос в базу данных. Например, у вас есть требование, что если запрос выполняется дольше какого-то времени, то его результат уже никому не нужен. Выставление описанного timeout делается через JDBC API при помощью метода Statement.setQueryTimeout(int) . Реализация данного метода специфична для JDBC драйвера. Например, oracle thin driver реализует его следующим образом: фоновый поток начинает считать время с момента запуска выполнения запроса и по истечении заданного интервала вызывает Statement.сancel(), который в свою очередь посылает специальный сигнал СУБД, и поток выполнения запроса выбрасывает ошибку "ORA-01013: user requested cancel of current operation". Но как уже мы обсуждали выше, доступ к JDBC есть не всегда в нашем приложении. Если вы работаете с hibernate, то для того чтобы выставить этот таймаут для всех запросов вам поможет свойство конфигурации "org.hibernate.timeout". Если вы хотите выставить timeout для какого-то одного конкретного запроса, то в случае hibernate вам поможет метод Criteria.setTimeout(int). Если вы работаете через JPA, то данный таймаут можно выставить с помощью свойства "javax.persistence.query.timeout" в persistence.xml, для того чтобы он подействовал на все запросы. Либо же выставите хинт при создании конкретного запроса Query.setHint или проставьте атрибут аннотации NamedQuery#hints().

3. Timeout на транзакцию

Данный тип timeout, очень похож на предыдущий, только относится не к одному запросу, а ко всей транзакции целиком. Если вы работаете с hibernate, то вышеупомянутый timeout можно проставить непосредственно на объекте транзакции hibernate Transaction.setTimeout(int). Если вы работаете с транзакциями через аннотации Spring, то это тоже легко сделать через атрибут анатации @Transaction(timeout=...). Перед выполнением каждого запроса Hibernate будет проверять сколько времени для данной транзакции осталось и соответствующим образом будет проставлять таймаут на оставшееся время для запроса, как я писал в разделе выше.

4. Timeout на зависшие запросы

Данный тип timeout может быть полезен, если firewall обрезал ваше соединение, или вы попали в deadlock. От предыдущих типов timeout этот главным образом отличается тем, что срабатывает только в случае полного отсутствия передачи данных по соединению. В Socket API это будет метод Socket.setSoTimeout(int). Однако некоторые JDBC драйверы позволяют установить данный timeout глобально для всех взаимодействий с базой данных. Например, для oracle thin jdbc драйвера существует свойство "oracle.jdbc.ReadTimeout", которое проставляется либо непосредственно при создании соединения через DriverManager.getConnection(java.lang.String, java.util.Properties), либо каким-нибудь специфичным методом DataSource, который вы используете. Например, для DBCP пула это будет BasicDataSource.setConnectionProperties.

5. Использование таймаутов в пулах соединений

Например, DBCP пул имеет свойство validationQueryTimeout, которое использует описанный "Timeout на запрос в базу данных" при валидации соединений в пуле.
Пул соединений Oracle Universal Connection Pool (UCP) имеет свойство TimeToLiveConnectionTimeout, которое по действию аналогично описанному "Timeout на транзакцию", где точка начала отсчета это момент, когда соединение забирают из пула, и точка конца отсчета, когда его возвращают обратно в пул. Реализуется он так же как и в hibernate, только внутри UCP.
Если посмотреть внимательнее, то пулы имеют еще много различных таймаутов, таких как MaxWait и RemoveAbandonedTimeout, но они не имеют ничего общего с сетвевыми таймаутами,  поэтому в данной заметке я их не затрагиваю.