データベース・ローダーの構成

ローダーは、変更がバックアップ・マップに対して行われた場合、または、バックアップ・マップがデータ要求を満足できない (キャッシュ・ミス) 場合に呼び出されるバックアップ・マップ・プラグインです。

プリロードの考慮事項

ローダーは、変更がバックアップ・マップに対して行われた場合、または、バックアップ・マップがデータ要求を満足できない (キャッシュ・ミス) 場合に呼び出されるバックアップ・マップ・プラグインです。eXtreme Scale がローダーとどのように対話するのかについての概要は、インライン・キャッシュを参照してください。

各バックアップ・マップには、マップのプリロードが非同期的に実行されるかどうかを示すために設定できるブール値の preloadMode 属性があります。 デフォルトでは、preloadMode 属性は false に設定されており、 マップのプリロードが完了するまでバックアップ・マップの初期化が完了しないことを示します。 例えば、preloadMap メソッドが戻されるまで、バックアップ・マップの初期化は完了しません。 preloadMap メソッドによりバックエンドから大量のデータが読み取られて、それがマップにロードされる場合は、完了するまでに比較的長い時間を要する場合があります。 このような場合、preloadMode 属性を true に設定して、マップの非同期プリロードを使用するように バックアップ・マップを構成できます。 この設定により、バックアップ・マップ初期化コードが preloadMap メソッドを呼び出すスレッドを開始し、マップのプリロードの進行中に、バックアップ・マップの初期化を完了できるようになります。

分散 eXtreme Scale のシナリオでは、プリロード・パターンの 1 つがクライアントのプリロードです。 クライアントのプリロード・パターンでは、DataGrid エージェントを使用したバックエンドからのデータの取得および分散コンテナー・サーバーへのデータの挿入という役割を、eXtreme Scale クライアントが担います。 さらに、クライアントのプリロードは 1 つの特定の区画のみの Loader.preloadMap メソッドで実行される可能性があります。 この場合、グリッドに非同期でデータをロードすることがとても重要になります。 クライアントのプリロードが同じスレッドで実行されると、バックアップ・マップは決して初期化されないため、クライアントのプリロードが常駐する区画は一度も ONLINE になりません。 このため、eXtreme Scale クライアントは要求を区画に送信することができず、最終的にそれが例外の原因となります。

eXtreme Scale クライアントが preloadMap メソッドで使用されている場合は、preloadMode 属性を true に設定してください。 代替案は、クライアントのプリロード・コードでスレッドを開始することです。

以下のコード・スニペットは、非同期プリロードが有効になるよう preloadMode 属性を設定する 方法を表しています。

BackingMap bm = og.defineMap( "map1" );
bm.setPreloadMode( true );

preloadMode 属性は、以下の例に示すように、 XML ファイルを使用して設定することもできます。

<backingMap name="map1" preloadMode="true" pluginCollectionRef="map1" 
	lockStrategy="OPTIMISTIC" />

TxID と TransactionCallback インターフェースの使用

Loader インターフェースの get メソッドと batchUpdate メソッドの両方に、get 操作または batchUpdate 操作の実行を必要とするセッション・トランザクションを表す TxID オブジェクトが渡されます。 get および batchUpdate メソッドは、トランザクションごとに複数回呼び出すことが可能です。したがって、ローダーが必要とするトランザクション・スコープのオブジェクトは通常 TxID オブジェクトのスロットに保持されます。 ローダーが TxID および TransactionCallback インターフェースをどのように使用するのかを示すため、Java Database Connectivity (JDBC) ローダー が使用されます。

複数の ObjectGrid マップを同じデータベースに格納できます。各マップは独自のローダーを持ち、各ローダーは同一のデータベースに接続しなければならない場合があります。 ローダーは、データベースに接続するときに同じ JDBC 接続を使用する必要があります。同じ接続を使用すると、各テーブルへの変更が同じデータベース・トランザクションの一部としてコミットされます。 通常、Loader 実装を作成する同じ担当者が TransactionCallback 実装も作成します。 最適な方法は、TransactionCallback インターフェースが拡張されて、ローダーにデータベースが接続され、ローダーが準備済みステートメントのキャッシングを必要とするメソッドを追加する場合です。 この方法論の理由は、ローダーが TransactionCallback インターフェースおよび TxID インターフェースを 使用する方法を調査すると明らかになります。

例として、ローダーが、以下のように拡張される TransactionCallback インターフェースを必要とする場合を示します。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.ibm.websphere.objectgrid.TxID;
public interface MyTransactionCallback extends TransactionCallback
{
    Connection getAutoCommitConnection(TxID tx, String databaseName) throws SQLException;
    Connection getConnection(TxID tx, String databaseName, int isolationLevel ) throws SQLException;
    PreparedStatement getPreparedStatement(TxID tx, Connection conn, String tableName, String sql) 
			throws SQLException;
    Collection getPreparedStatementCollection( TxID tx, Connection conn, 	String tableName );
}

これらの新しいメソッドを使用すると、Loader の get メソッドおよび batchUpdate メソッドにより、以下のようにして接続が取得されます。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.ibm.websphere.objectgrid.TxID;
private Connection getConnection(TxID tx, int isolationLevel)
{
    Connection conn = ivTcb.getConnection(tx, databaseName, isolationLevel );
    return conn;
}

前の例および以下の例において、ivTcb および ivOcb はプリロードの考慮事項のセクションで説明する方法で初期化されたローダーのインスタンス変数です。ivTcb 変数は MyTransactionCallback インスタンスへの 参照であり、ivOcb は MyOptimisticCallback インスタンスへの参照です。 databaseName 変数は、ローダーのインスタンス変数であり、バックアップ・マップの初期化中に Loader プロパティーとして設定されています。 isolationLevel 引数は、JDBC がサポートするさまざまな分離レベルに対して定義されている JDBC 接続定数の 1 つです。 ローダーがオプティミスティック実装を使用している場合は、get メソッドは通常 JDBC 自動コミット接続を使用して データをデータベースからフェッチします。 この場合、ローダーは以下のように実装される getAutoCommitConnection メソッドを備えている場合があります。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.ibm.websphere.objectgrid.TxID;
private Connection getAutoCommitConnection(TxID tx)
{
    Connection conn = ivTcb.getAutoCommitConnection(tx, databaseName);
    return conn;
}

batchUpdate メソッドに以下の switch ステートメントがあれば、再呼び出しします。

switch ( logElement.getType().getCode() )
{
    case LogElement.CODE_INSERT:
        buildBatchSQLInsert( tx, key, value, conn );
        break;
    case LogElement.CODE_UPDATE:
        buildBatchSQLUpdate( tx, key, value, conn );
        break;
    case LogElement.CODE_DELETE:
        buildBatchSQLDelete( tx, key, conn );
        break;
}

各 buildBatchSQL メソッドは、MyTransactionCallback インターフェースを使用して、準備済みステートメントを取得します。 以下は、EmployeeRecord エントリーを更新する SQL UPDATE ステートメントをビルドして、それをバッチ更新用に追加する buildBatchSQLUpdate メソッドを示すコード・スニペットです。

private void buildBatchSQLUpdate( TxID tx, Object key, Object value, 
	Connection conn ) 
throws SQLException, LoaderException
{
    String sql = "update EMPLOYEE set LASTNAME = ?, FIRSTNAME = ?, DEPTNO = ?,
    SEQNO = ?, MGRNO = ? where EMPNO = ?";
    PreparedStatement sqlUpdate = ivTcb.getPreparedStatement( tx, conn, 
			"employee", sql );
    EmployeeRecord emp = (EmployeeRecord) value;
    sqlUpdate.setString(1, emp.getLastName());
    sqlUpdate.setString(2, emp.getFirstName());
    sqlUpdate.setString(3, emp.getDepartmentName());
    sqlUpdate.setLong(4, emp.getSequenceNumber());
    sqlUpdate.setInt(5, emp.getManagerNumber());
    sqlUpdate.setInt(6, key);
    sqlUpdate.addBatch();
}

batchUpdate ループは、準備済みステートメントをすべてビルドした後で、getPreparedStatementCollection メソッドを呼び出します。 このメソッドは、以下のように実装されます。

private Collection getPreparedStatementCollection( TxID tx, Connection conn )
{
    return ( ivTcb.getPreparedStatementCollection( tx, conn, "employee" ) );
}
アプリケーションによりセッションの commit メソッドが呼び出されると、セッション・コードは、トランザクションによって変更された各マップのローダーに、トランザクションによって変更されたすべての変更がプッシュされた後で、TransactionCallback メソッドの commit メソッドを呼び出します。 すべてのローダーにより、必要なすべての接続と準備済みステートメントを取得するために MyTransactionCallback メソッド が使用されたため、TransactionCallback メソッドは、バックエンドが変更をコミットすることを 要求するために使用する接続を認識しています。 したがって、各ローダーが必要とするメソッドを持つ TransactionCallback インターフェースを 拡張することによって、以下の利点が得られます。
  • TransactionCallback オブジェクトは、トランザクション・スコープ・データの TxID スロットの 使用をカプセル化するので、ローダーは TxID スロットに関する情報を必要としません。 ローダーは、ローダーが必要とする機能をサポートするための、 MyTransactionCallback インターフェースを使用する TransactionCallback に 追加されるメソッドに関してのみ認識する必要があります。
  • TransactionCallback オブジェクトは、2 フェーズ・コミット・プロトコルを回避できるようにするため、 同じバックエンドに接続する各ローダー間で、接続の共有が確実に起こるようにすることができます。
  • TransactionCallback オブジェクトは、 バックエンドへの接続が適切な場合接続に呼び出されたコミットまたはロールバックを通して 確実に完了できるようにします。
  • TransactionCallback は、トランザクションの完了時にデータベース・リソースのクリーンアップが実行されることを保証します。
  • TransactionCallback は、WebSphere® Application Server、 または他の Java 2 Platform, Enterprise Edition (J2EE) 準拠の アプリケーション・サーバーなどの管理された環境から、管理対象の接続を取得している場合は、隠蔽されます。 この利点により、環境が管理されている、いないにかかわらず、同じローダーのコードを使用できます。 TransactionCallback プラグインのみを変更する必要があります。
  • TransactionCallback の実装がトランザクション・スコープのデータの TxID スロットを使用する方法の詳細については、TransactionCallback プラグインを参照してください。

OptimisticCallback

これまでに述べたように、ローダーは、並行性制御にオプティミスティック・アプローチを使用する場合があります。その場合、オプティミスティック・アプローチを実装するために、buildBatchSQLUpdate メソッドの例に若干の変更を加える必要があります。 オプティミスティック・アプローチを使用する方法は、いくつかあります。 行の各更新をバージョン管理するために、タイム・スタンプの列かシーケンス番号の カウンターの列のいずれかを設ける方法が一般的です。 従業員のテーブルには、行が更新されるたびに増分するシーケンス番号の列があります。 次に、buildBatchSQLUpdate メソッドのシグニチャーを変更して、鍵と値のペアの代わりに LogElement オブジェクトが渡されるようにします。 初期バージョンのオブジェクトを取得し、そのバージョンのオブジェクトを更新するには、 バックアップ・マップにプラグインされた OptimisticCallback オブジェクトも使用する必要があります。 以下は、preloadMap のセクションで説明されている、初期化された ivOcb インスタンス変数を使用する変更済み buildBatchSQLUpdate メソッドの例です。

modified batch-update method code example
private void buildBatchSQLUpdate( TxID tx, LogElement le, Connection conn )
	throws SQLException, LoaderException
{
    // Get the initial version object when this map entry was last read
    // or updated in the database.
    Employee emp = (Employee) le.getCurrentValue();
    long initialVersion = ((Long) le.getVersionedValue()).longValue();
    // Get the version object from the updated Employee for the SQL update
    //operation.
    Long currentVersion = (Long)ivOcb.getVersionedObjectForValue( emp );
    long nextVersion = currentVersion.longValue();
    // Now build SQL update that includes the version object in where clause
    // for optimistic checking.
    String sql = "update EMPLOYEE set LASTNAME = ?, FIRSTNAME = ?,
    DEPTNO = ?,SEQNO = ?, MGRNO = ? where EMPNO = ? and SEQNO = ?";
    PreparedStatement sqlUpdate = ivTcb.getPreparedStatement( tx, conn, 
			"employee", sql );
    sqlUpdate.setString(1, emp.getLastName());
    sqlUpdate.setString(2, emp.getFirstName());
    sqlUpdate.setString(3, emp.getDepartmentName());
    sqlUpdate.setLong(4, nextVersion );
    sqlUpdate.setInt(5, emp.getManagerNumber());
    sqlUpdate.setInt(6, key);
    sqlUpdate.setLong(7, initialVersion);
    sqlUpdate.addBatch();
}

この例は、初期バージョンの値を取得するために LogElement が使用されることを示しています。トランザクションがマップ・エントリーに最初にアクセスするとき、マップから取得した初期の従業員のオブジェクトに関して LogElement が作成されます。この初期 Employee オブジェクトは、OptimisticCallback インターフェースの getVersionedObjectForValue メソッドにも渡され、その結果は LogElement に保存されます。 この処理が実行されるのは、初期 Employee オブジェクトへの参照がアプリケーションに与えられ、そのアプリケーションが初期 Employee オブジェクトの状態を変更する何らかのメソッドを呼び出す時の前です。

この例は、Loader が getVersiondObjectForValue メソッドを使用して、現行の更新済み Employee オブジェクトのバージョン・オブジェクトを取得しているところを示しています。Loader インターフェースの batchUpdate メソッドを 呼び出す前に、eXtreme Scale は OptimisticCallback インターフェースの updateVersionedObjectForValue メソッドを 呼び出して、更新された Employee オブジェクトに対する新しいバージョン・オブジェクトが 生成されるようにします。batchUpdate メソッドが ObjectGrid に戻された後、LogElement は新規の初期バージョン・オブジェクトになるように、現行バージョン・オブジェクトで更新されます。 アプリケーションは Session の commit メソッドの代わりに、 マップ上の flush メソッドを呼び出す可能性があるので、このステップが必要になります。 同一のキー用の単一のトランザクションによって、ローダーを複数回呼び出すことは可能です。 その理由のため、eXtreme Scale は、 従業員テーブル内の行が更新されるたびに LogElement が新しいバージョン・オブジェクトで更新 されることを保証しています。

ローダーには、初期バージョンのオブジェクトと次期バージョンのオブジェクトが用意されているので、次期バージョンのオブジェクト値に SEQNO 列を設定し、where 文節で 初期バージョンのオブジェクト値を使用する SQL UPDATE ステートメントを実行できます。 この方法は、過剰 update ステートメントと呼ばれることがあります。 この過剰 update ステートメントを使用することにより、リレーショナル・データベースは、このトランザクションがデータベースからデータを読み取り後データベースを更新するまでの間に、別のトランザクションにより行が変更されていないかどうかを検証できます。 別のトランザクションが行を変更していた場合、 バッチ更新によって戻されるカウント配列は、このキーに関してゼロ行が更新されたことを示します。 ローダーは、SQL update 操作が実際に行を更新したことを検証します。 更新されていない場合は、ローダーは com.ibm.websphere.objectgrid.plugins.OptimisticCollisionException 例外を表示して、 複数の並行トランザクションがデータベース表の同一行に対して更新を試みたため、batchUpdate メソッドが失敗したことをセッションに通知します。 この例外はセッションにロールバックを行わせるので、 アプリケーションはトランザクション全体を再試行する必要があります。 この方法は、再試行が成功することを予測して行われるため、 オプティミスティックと呼ばれます。 データがまれにしか変更されないか、または並行トランザクションによる同一行の更新がほとんど試行されない場合は、オプティミスティック・アプローチは実際に適切に機能します。

ローダーが OptimisticCollisionException コンストラクターのキー・パラメーターを使用して、オプティミスティック batchUpdate メソッドの失敗の原因になったキーまたはキーのセットを識別することが重要です。 キー・パラメーターには、キー・オブジェクトそのものを使用することもできますし、複数のキーが 原因でオプティミスティック更新が失敗した場合は、キー・オブジェクトの配列とすることもできます。 eXtreme Scale は、OptimisticCollisionException コンストラクター の getKey メソッドを使用して、どのマップ・エントリーに失効データが含まれていて、 例外の発生原因となったのかを判別します。 ロールバック処理の一環として、失効した各マップ・エントリーをマップから除去します。 失効したエントリーを除去する必要があるのは、同じキーまたは複数のキーにアクセスする後続のいずれかのトランザクションで、Loader インターフェースの get メソッドが呼び出されて、 データベースの現在のデータによってマップ・エントリーが更新されるようにするためです。

ローダーがオプティミスティック・アプローチを実施するそれ以外の方法として、以下のようなものがあります。
  • タイム・スタンプまたはシーケンス番号の列を無くします。 この場合、OptimisticCallback インターフェースの getVersionObjectForValue メソッドは、 単に、値オブジェクト自身をバージョンとして戻します。 この方法では、ローダーは初期バージョン・オブジェクトの各フィールドを組み込む where 文節をビルドする必要があります。 この方法は効率的ではなく、列タイプのすべてが過剰 SQL UPDATE ステートメントの where 文節での使用に適しているわけではありません。 この方法は通常使用しません。
  • タイム・スタンプまたはシーケンス番号の列を無くします。 ただし、前の方法とは異なり、where 文節にはトランザクションによって変更された値フィールドのみが含まれています。 変更されたフィールドを検出する方法の 1 つに、バックアップ・マップのコピー・モードを CopyMode.COPY_ON_WRITE モードに設定することがあります。 このコピー・モードは、BackingMap インターフェースの setCopyMode メソッドに渡される値インターフェースを必要とします。 BackingMap は、提供される値インターフェースを実装する動的プロキシー・オブジェクトを作成します。 このコピー・モードにより、ローダーは com.ibm.websphere.objectgrid.plugins.ValueProxyInfo オブジェクトに 各値をキャストできます。 ValueProxyInfo インターフェースには、 トランザクションによって変更された属性名のリストをローダーが取得できるメソッドがあります。 このメソッドにより、ローダーは属性名の値インターフェースで get メソッドを呼び出して、 変更されたデータを取得し、変更された属性のみを設定する SQL UPDATE ステートメントをビルドすることができます。 where 文節は、基本キーの列と変更された各属性の列を持つようにビルドされます。 この方法は前の方法よりも効果的ですが、ローダーにさらに多くのコードを書き込む必要があり、 さまざまな置換を処理するために、さらに多くの準備済みステートメントのキャッシュが必要になる可能性があります。 ただし、トランザクションが、通常ごく一部の属性しか変更しない場合、 この制限は問題になりません。
  • 一部のリレーショナル・データベースには API があるため、 オプティミスティックなバージョン管理に役立つ列データを自動的に保守します。 ご使用のデータベースの資料を参照して、この可能性が該当するかどうかを判断してください。