Как бэ вступление
По работе возникла необходимость синхронной рабты с кластером БД (4 базы, в которых должны храниться одни и те же данные). Для этого я несколько месяцев подряд писать тулзу data_proc (коммерческая разработка), которая обрабатывает данные в поточном режиме и устойчива к connection-loss/database-failure ошибкам. Единственный недостаток — это хранение данных на локальном диске в виде журналов, объём которых достаточно велик, если база несколько часов находится offline.
Помимо data_proc у нас есть ещё куча других приложений, для которых пришлось писать балансировщик нагрузки для SELECT-запросов, с чем мы успешно справились. Тем не менее, вопрос балансировки нагрузки и кластеризации (с целью упрощения data_proc) остался, и мне предложили разобраться с C-JDBC, о чём я и буду сейчас писать.
C-JDBC или Sequoia
Собственно, было предложено покопаться в проекте C-JDBC (Clustered JDBC), который имеет лицензию LGPL. Выкачал дистрибутив, сорсы и доки для Linux, поставил в /opt. Стал разбираться, как скрестить это чудо с ORACLE. Честно прочитал бОльшую часть документации на этот продукт, и после некоторых бдений с когфигами и осознавания того, что я делаю не так, выяснил, что ан-нет, не получается никак. Это чудо генерило исключения при попытке получить схему БД ORACLE. Да, я бы всё понял, если бы это не было java.lang.NullPointerException. После долгих скитаний в Google я, было, совсем отчаялся.
«А, может, я не ту версию скачал?» — подумал я, и пошёл снова на официальный сайт C-JDBC, на котором заметил, что C-JDBC имеет своё продолжение — проект Sequoia, с изменённой лицензией на Apache License. Скачав последнюю версию, стал ковырять…
Первые грабли
Первыми граблями оказалось портирование конфига с C-JDBC на Sequoia. Очень жестокий XML-валидатор ругался и матерился, пока я всё-таки не выяснил, что ему действительно было нужно от меня. Разобравшись с валидатором, я всё-таки написал конфиг, объединяющий две схемы двух разных БД в кластер по технологии RAIDb-1. Контроллер вроде бы запустился, да даже вроде бы поднял так называемые backends (проще говоря, законнектился к базам).
Отлично, теперь можно слабать простенькую табличку:
CREATE TABLE TM_FIRST
(
nID NUMBER,
strMessage VARCHAR2(256),
nCode NUMBER
)
Что я незамедлительно и проделал на обеих схемах.
Пришло время написать какой-нить код, который что-то делает с этим кластером. Что ж, берём в руки Eclipse, создаём проект и заводим вот такой классик:
package test.cjdbc;
import java.sql.*;
import org.continuent.sequoia.driver.DataSource;
/** CJDBC controller test
* @author Vladimir Sadovnikov
*
*/
public class Tm_CJDBCTest
{
/**
* @param args
*/
public static void main(String[] args)
{
DataSource ds = new DataSource();
ds.setURL("jdbc:sequoia://localhost:25322/CJDBC");
ds.setUser("user");
ds.setPassword("userpass");
try
{
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
try
{
PreparedStatement stm = conn.prepareStatement("INSERT INTO Tm_First (nID, strMessage, nCode) VALUES (?, ?, ?)");
try
{
stm.setInt(1, 666);
stm.setString(2, "Hello, World!");
stm.setInt(3, 13);
stm.addBatch();
stm.execute();
conn.commit();
}
catch (SQLException ex)
{
conn.rollback();
throw ex;
}
finally
{
stm.close();
}
stm = conn.prepareStatement("SELECT * from Tm_First");
try
{
ResultSet rset = stm.executeQuery();
try
{
while (rset.next())
{
String result = "fetched item: ";
result += rset.getInt(1) + ";";
result += rset.getString(2) + ";";
result += rset.getInt(3) + "\n";
System.out.println(result);
}
}
finally
{
rset.close();
}
}
catch (SQLException ex)
{
conn.rollback();
throw ex;
}
finally
{
stm.close();
}
}
finally
{
conn.close();
}
}
catch (SQLException ex)
{
ex.printStackTrace();
}
}
}
Запускаем класс на выполнение, в базы вставляется по строчке, контроллер рапортует об успехе, выдав мне на SELECT-запрос строчку. Ура-ура-ура: в базах данные вроде бы синхронны. Что ж, пора брать в руки молоток.
Краш-тест
Что ж, посмотрим, как контроллер отреагирует на отказ в работе одной из БД.
Для этого, не мудрствуя лукаво, я делаю следующий трюк:
ALTER USER cjdbc ACCOUNT LOCK
То есть, блокирую аккаунт, через который контроллер Sequoia подключается к базе. После чего вывожу список коннекций:
select s.sid s.serial#, s.osuser, s.username, s.program FROM v$session s where USERNAME like 'CJDBC' and OSUSER IS NULL
И поочереди сношу их через
ALTER SYSTEM KILL SESSION IMMEDIATE;
После этого база контроллеру недоступна, и я с чистой совестью снова прогоняю тест. В результате чего в одной базе две записи, в другой — одна. И никаких дополнительных метаданных, по которым можно было бы восстановить зеркальность баз, нет ни в первой, ни во второй.
Что ж, может это очень умная штука, которая использует какие-то ораклёвые фичи, о которых я не знаю? OK, тогда разрешаем аккаунт:
ALTER USER cjdbc ACCOUNT UNLOCK
И снова прогоняем тест. В первой базе три записи, во второй — по-прежнему одна, а SELECT бодро выдаёт три записи через ResultSet.
Может быть, оно после рестарта как-то восстановится? Ладно, вырубаю контроллер (жёстко, по ctrl-c). Запускаю снова. Sequoia подключается к обеим базам, вытаскивает схемы и… больше ничего не делает.
Ладно, прогоняю ещё раз тест… В первой базе четыре записи, во второй — всего две, и выдёргивается через SELECT только две.
Но не может же быть!
subj, ведь в официальной документации от Sequoia говорится следующее:
You have a Java application or a Java-based application server that accesses one or several databases. The database tier becomes the bottleneck of your application or it is a single point of failure or both. Sequoia can help you resolve these problems by providing:
* performance scalability by adding database nodes and balancing the load among these nodes.
* high availability of the database tier, i.e. Sequoia tolerates database crashes and offers transparent failover using database replication techniques.
* improved performance with fine grain query caching and transparent connection pooling.
* SQL traffic logging for performance monitoring and analysis.
* support for clusters of heterogeneous database engines.
Вспоминаю, что в доках упоминалась такая фигня, как RecoveryLog. Может, причина в том, что я не сконфигурировал его? Да, похоже, надо конфигурить, но это тема для отдельного поста.
Заключение
Первое знакомство с C-JDBC оказалось неудачным, в Sequoia был пофиксен ряд багов, что, тем не менее, не мешало ей валить исключения при определённых настройках. Тестирование работы RAIDb-1 оказалось полностью провальным (до первого сбоя), опция RecoveryLog не использовалась. Похоже, что в документации нас хотят обмануть. Но исследование продолжается.