db4o その20(レプリケーション)

データベースのレプリケーションとはデータベースの冗長性を高めたり、負荷分散を実現する手段としてその複製を作る処理をことを指す。

db4oは元々レプリケーション用のAPIを実装しており、自身のデータベースを簡単に複製することができる。レプリケーション機能はその後dRS(db4o Replication System )という別なAPIに集約されたので、今回のレプリケーションの話題もdRSを使用した場合に限定している。

dRSによるレプリケーションは以下の形態を想定している。

db4o<->RDBの複製に関してはそもそもdRSが考案された際の売りの一つであり、技術的にはHibernateによりO-R Mappingを利用してバックエンドのRDBdb4oレプリケーションを生成する。
db4oを使っているとHibernateは面倒だし対象外として触れずに今回はdb4o同士のレプリケーションに限定して書くことにする。(※1)

dRSを使用したdb4oレプリケーションは非常に簡単だ。以下の手順ですぐにでも実行できる。

  • UUIDとバージョン番号生成を有効にする

UUIDはいわゆる汎用固有識別子である。レプリケーション対象のデータベースはオブジェクトの一意性を保つ必要があるためにUUIDをオブジェクトに付加すること、互いのバージョンを一致させる必要があるためにバージョン番号を付加することがdb4oレプリケーションを成功させるための必須条件となる。
UUIDとバージョン番号を付加するためには以下のイディオムを使用する。

//全ての型(といってもJDKで定義されている型を除く)を対象とする指定
Db4o.configure().generateUUIDs(ConfigScope.GLOBALLY);
Db4o.configure().generateVersionNumbers(ConfigScope.GLOBALLY);

//個別の型を対象とする指定
Db4o.configure().objectClass(PlayerImpl.class).generateUUIDs(true);
Db4o.configure().objectClass(PlayerImpl.class).generateVersionNumbers(true);

なお、チュートリアル等では全ての型を対象とする指定はパラメタにIntegerクラスを取る書き方が紹介されているが、開発最新版のdb4o 6.2.501では@depricatedとなっているため、これに合わせることとした。
ちなみに後者のように型を個別に指定する場合はレプリケート対象の型に抜けが無いように注意が必要である。というのもdRSは複製の対象となるオブジェクト全てにUUIDとバージョン番号が付加されているのを前提にしているので、この指定を行っていないユーザ定義型がレプリケーション処理中に見つかると例外が発生して処理が中断してしまうのだ。

レプリケーションセッションとは、dRSにおけるレプリケーションとそのトランザクションの単位であり対象データベースの組である。レプリケーションセッションは以下の構文で開始する。

ObjectContainer srcDb = Db4o.openFile("src.db");
ObjectContainer dstDb = Db4o.openFile("dst.db");
ReplicationSession replication = Replication.begin(srcDb, dstDb);

二つのパラメタはレプリケーション対象のObjectContainerを指定する。サンプルでは組込みモードでオープンしているが、ネットワークモードでオープンしたObjectContainerであってもレプリケーションは可能である。また、その場合それぞれ内部で生成されるReplicationProviderの実装クラスが

  • 組込みモード

FileReplicationProvider

  • ネットワークモード

ClientServerReplicationProvider

と変わる。

dRSによるレプリケーションはデフォルトで双方向モードに設定されているが、よくあるレプリケーションの形態である「マスタ->スレーブ」等、片方向のレプリケーションで事足りる場合は明示的にレプリケーションの方向を設定することができる。

//srcDb -> dstDbの一方向に設定する
replication.setDirection(replication.providerA(), replication.providerB());

dRSによるレプリケーション処理は自動で行われる訳ではなく、現在までに発生している変更差分を取り出してそれを対象に明示的にレプリケート指示を行うことで実行される。

ObjectSet changed = replication.providerA().objectsChangedSinceLastReplication();
while (changed.hasNext()) {
    replication.replicate(changed.next());
}

この例ではreplication.providerA()、つまり開かれた"src.db"に対しての変更差分を取得しそれをレプリケーションしている。("dst.db"で開かれたデータベースに変更差分が反映される。なお、レプリケーションもデータベースの更新を伴うため、ReplicationSessionインタフェースにはObjectContainerと同様にトランザクションに関する操作も定義されている。

public interface ReplicationSession {
:
    void commit();
    void rollback();
:
}

レプリケーションの変更を反映するにはcommit()、取り消すにはrollback()を使えるという訳だ。

レプリケーションの終了はclose()メソッドで行う。

replication.close();

close()メソッドはReplicationSessionの内部リソースをクリアするだけである。ObjectContainerと違い、コミットされていないトランザクションを強制的にコミットしている訳では無いので注意が必要だ。

以上、dRSにおけるレプリケーションについて簡単に書いた。


※1 Hibernateを使う是非は別にして、個人的にOODBのバックエンドにRDBを使うという構成は非常に興味深いものがある。
OODBはその特徴を生かすためにもレスポンスが要求されるが揮発しても良いデータに適用し、その複製(または等質な表)をRDBに作ることでRDBが持つ高い耐障害性、復旧性を利用する、という使い方が出来ないかと考えている。

追記:
RDBOODBを併用するアーキテクチャに関しては「データベースのハイブリッド連携」や「ハイブリッドDB」と呼ぶ場合がある。RDBOODBをデータの目的や用途によって使い分ける場合は水平ハイブリッド、OODBRDBのキャッシュのように扱う場合は垂直ハイブリッドと呼ぶようだ。