Liferay Portal. Боевая эксплуатация. Пул соединений

Разбираемся с разрывом соединения с базой данных и настраиваем соединение с базой данных через пул.

Конфигурация

  • Liferay Portal 6.1
  • Apache Tomcat 7
  • Oracle 11g
  • Portlet - портлет, обеспечиваюший отображение данных из БД

Взаимодействие между приложением (Liferay Portal и Portlet) и базой данных настроено через пул соединений.
Настройки пула взяты из инструкций по установки портала:

 
<Resource
        name="jdbc/LiferayPool" 
        auth="Container" 
        type="javax.sql.DataSource" 
        driverClassName="oracle.jdbc.driver.OracleDriver" 
        url="jdbc:oracle:thin:@rat.lan.iac.spb.ru:1521:***" 
        username="***" 
        password="***" 
        maxActive="100" 
        maxIdle="30" 
        maxWait="10000" 
/>
 

Проблема обрыва соединения

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

ERROR: getRequestTypeList(102)... rTypeList = []
08:11:03,628 ERROR [JDBCExceptionReporter:75] ORA-02396: exceeded maximum idle time, please connect again
08:11:03,630 ERROR [JDBCTransaction:109] Could not toggle autocommit java.sql.SQLRecoverableException: Closed Connection
08:11:03,630 ERROR [JDBCTransaction:109] JDBC rollback failed java.sql.SQLException: ORA-01012: not logged on
08:11:03,631 ERROR [JDBCExceptionReporter:75] Already closed.
08:11:03,632 ERROR [JDBCEлxceptionReporter:75] Already closed.
08:11:03,632 ERROR [TransactionInterceptor:121] Application exception overridden by rollback exception org.springframework.transaction.TransactionSystemException: Could not roll back Hibernate transaction; nested exception is org.hibernate.TransactionException: JDBC rollback failed
...
Caused by: java.sql.SQLException: ORA-01012: not logged on

Визуально для конечного пользователя сайта это выглядело следующим образом:

  • При серфинге по страницам портала, кроме нашего портлета, все страницы отображались без ошибок
  • При обращении к нашему портлету выводилось сообщение об отсутствии данных, но при перезагрузке страницы данные отображались корректно.

Теория

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

Рассуждения

А что будет в случае если соединение, хранящееся в пуле, будет закрыто на уровне БД?
Узнает ли пул об этом? И не получит ли приложение закрытое соединение под видом рабочего?

Действительно. В наших настройках пула по-умолчанию пул не производит контроль состояния своих соединений.
И получив такое "плохое" соединение, приложение столкнется с исключением типа "ORA-02396: exceeded maximum idle time, please connect again" при попытке выполнения запроса.

Отсюда следует, что для избежания подобных конфузов необходимо либо:

  • заставить приложение самостоятельно проводить проверку соединений и запрашивать новое в случае провала. Как это реализовано в LIferay Portal в отличие от самописного портлета!
  • заставить пул мониторить и поддерживать работоспособное состояние соединений и выдавать стопроцентно рабочие соединения. Данный вариант предпочтительнее, так как не требует доп. программирования и исключает ситуацию с потерей времени на установку соединения в момент запроса приложением.

Решение

Настраиваем пул соединений в контексте портлета и в контексте LIferay Portal:
META-INF/context.xml и ROOT.xml

<Context>
...
<Resource
        name="jdbc/SpbArchPool" 
        auth="Container" 
        factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" 
        type="javax.sql.DataSource" 
        driverClassName="oracle.jdbc.driver.OracleDriver" 
        url="jdbc:oracle:thin:@rat.lan.iac.spb.ru:1521:*" 
        username="***" 
        password="***" 
        maxActive="100" 
        maxIdle="30" 
        maxWait="10000" 
        validationQuery="select 1 from dual" 
        testWhileIdle="true" 
/>
...
</Context>
 

Обращаем внимание на параметры:

  • factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" - указание на реализацию пула
  • validationQuery="select 1 from dual" - запрос который будет запускаться для проверки работоспособности соединения
  • testWhileIdle="true" - включаем проверку состояния, интервал устанавливается параметром timeBetweenEvictionRunsMillis и равен 5 секундам по умолчанию
    http://people.apache.org/~fhanik/jdbc-pool/jdbc-pool.html - описание всех возможных параметров

Теперь каждые 5 секунд пул будет посылать на исполнение запрос "select 1 from dual". Тем самым соединение будет поддерживаться в рабочем состоянии, а в случае потери работоспособности будет удалено из пула.

P.S. Да, забыл упомянуть, что по умолчанию в СУБД Oracle соединения не умирают за сроком давности.
Но в нашем случае было установлена настройка

ALTER SYSTEM SET RESOURCE_LIMIT=TRUE SCOPE=BOTH;
ALTER PROFILE "DEFAULT" LIMIT IDLE_TIME 15;

Проверить установлены ли данные настройки можно следующими запросами:

SHOW PARAMETER RESOURCE_LIMIT;
SELECT PROFILE, RESOURCE_NAME, LIMIT FROM DBA_PROFILES
WHERE PROFILE='default' AND RESOURCE_NAME ='idle_time';

Поэтому все и умирало при использовании пула.