可用性向上のためのレプリカ生成

レプリカ生成は、耐障害性を強化し、分散 eXtreme Scale トポロジーのパフォーマンスを向上させます。レプリカ生成は、バックアップ・マップをマップ・セットと関連付けることによって使用可能になります。

マップ・セットについて

マップ・セットは、区画キーによってカテゴリー化されるマップの集まりです。 この区画キーは、個別マップのキーから、そのハッシュ・モジュロを取って区画数とすることで派生します。マップ・セット内の 1 つのマップ・グループが区画キー X を持つとすると、それらのマップはデータ・グリッド内の対応する区画 X に保管されます。別のグループが区画キー Y を持つとすると、それらのマップはすべて区画 Y に保管されます。以下同様です。マップ内のデータは、マップ・セットに定義されたポリシーに基づいて複製されます。レプリカ生成は、分散トポロジーで発生します。

マップ・セットには、区画数とレプリカ生成ポリシーが割り当てられます。 マップ・セット・レプリカ生成構成は、マップ・セットがプライマリー断片に加えて持つ必要がある同期および非同期のレプリカ断片の数を示します。例えば、1 つの同期レプリカと 1 つの非同期レプリカが存在する場合、マップ・セットに割り当てられたすべての BackingMap は、それぞれ、データ・グリッドの使用可能なコンテナー・サーバー・セット内に自動的に配布されるレプリカ断片を持ちます。また MapSet レプリカ生成構成により、クライアントは同期複製されたサーバーからデータを読み取れるようになります。これにより、読み取り要求の負荷を eXtreme Scale 内のその他のサーバーにも分散することができます。レプリカ生成は、バックアップ・マップのプリロード時にプログラミング・モデルに影響するだけです。

マップのプリロード

マップはローダーに関連付ける ことができます。ローダーは、オブジェクトがマップに見つからない場合 (キャッシュ・ミスの場合) に、そのオブジェクトをフェッチするためにも、またトランザクションのコミット時に変更をバックエンドに書き込むためにも使用されます。 ローダーは、マップへのデータのプリロードに使用することもできます。Loader インターフェースの preloadMap メソッドは、マップ・セット内のその対応する区画がプライマリーとなると、各マップで呼び出されます。preloadMap メソッドは、レプリカでは呼び出されません。このメソッドは、提供されたセッションを使用して、対象となる参照データのすべてをバックエンドからマップにロードしようとします。関係するマップは、preloadMap メソッドに渡される BackingMap 引数によって識別されます。
void preloadMap(Session session, BackingMap backingMap) throws LoaderException;

区画に分割されたマップ・セットでのプリロード

マップは、N 個の区画に分割することができます。 したがってマップは、複数のサーバーに渡ってストライプすることができます。この場合、各エントリーは、これらのサーバーのうちの 1 つにのみ保管されているキーによって識別されます。アプリケーションは、マップのすべてのエントリーを保持する場合に単一 JVMのヒープ・サイズによる制限を受けなくなるため、非常に大きいマップを eXtreme Scale に保持できるようになります。Loader インターフェースの preloadMap メソッドがプリロードされるアプリケーションは、それがプリロードするデータのサブセットを識別する必要があります。常に、固定数の区画が存在します。 この数を判別するには、以下のコード例を使用してください。

int numPartitions = backingMap.getPartitionManager().getNumOfPartitions();
int myPartition = backingMap.getPartitionId();
このコード例は、アプリケーションが、データベースからプリロードするデータのサブセットを識別できることを示しています。アプリケーションは、マップが最初に区画に分割されていない場合でも、 これらのメソッドを常に使用しなければなりません。これらのメソッドによって柔軟性が実現されます。管理者が後でマップを区画に分割した場合でも、ローダーは正常に機能し続けます。

アプリケーションは、バックエンドから myPartition サブセットを検索する照会を発行する必要があります。テーブル内のデータを簡単に区画に分割できるなんらかの自然な照会がある場合を除き、データベースが使用される場合は、所定レコードの区画 ID の列を持つ方が、処理が容易である可能性があります。

パフォーマンス

プリロードの実装では、複数のオブジェクトを単一トランザクションでマップに保管して、データをバックエンドからマップにコピーします。トランザクションごとに保管されるレコードの最適数は、複雑さやサイズなど、いくつかの要因によって決まります。 例えば、トランザクションに 100 エントリーを超えるブロックが含まれると、以後は、エントリーの数を増やすに従ってパフォーマンス利益が減少していきます。 最適数を知るためには、まず 100 エントリーから始めて、徐々に数を増やしていきます。これをパフォーマンス利益がゼロに減少するまで続けます。 トランザクションが大きいほど、レプリカ生成パフォーマンスが向上します。 ただし、プライマリーのみがプリロード・コードを実行することに注意してください。 プリロードされたデータは、プライマリーから、オンラインになっているすべてのレプリカに複製されます。

マップ・セットのプリロード

アプリケーションが複数のマップを持つマップ・セットを使用する場合、各マップはそれぞれ独自のローダーを持ちます。各ローダーに、プリロード・メソッドがあります。各マップは、eXtreme Scale によって順次にロードされます。1 つのマップをプリロード・マップに指定して全マップをプリロードすると、より効率的になる可能性があります。このプロセスは、アプリケーション規則です。例えば、部門と従業員という 2 つのマップが、部門マップと従業員マップの両方をプリロードするために、部門 Loader を使用するとします。このプロシージャーにより、トランザクション上、アプリケーションで部門が必要な場合、その部門の従業員がキャッシュされます。部門 Loader が部門をバックエンドからプリロードするときに、その部門の従業員もフェッチします。その後で、部門オブジェクトとそれに関連する従業員オブジェクトが、単一のトランザクションを使用して、マップに追加されます。

リカバリー可能なプリロード

非常に大きいデータ・セットをキャッシュする必要がある場合があります。このデータのプリロードは、非常に時間がかかる可能性があります。 アプリケーションがオンラインになる前に、プリロードを完了しなければならない場合もあります。 プリロードをリカバリー可能にすると、便利です。100 万個のレコードをプリロードする必要があるとします。プライマリーがこれらのレコードをプリロードし、 800,000 件目のレコードの時点でプライマリーが失敗するとします。通常、新規プライマリーとして選択されたレプリカは、複製状態をクリアして、最初からプリロードを開始します。eXtreme Scale では、ReplicaPreloadController インターフェースを使用できます。アプリケーションのローダーで、ReplicaPreloadController インターフェースを実装する必要が生じることもあります。この例では、単一メソッド Status checkPreloadStatus(Session session, BackingMap bmap); をローダーに追加します。Loader インターフェースのプリロード・メソッドが正常に呼び出されるためには、このメソッドが eXtreme Scale ランタイムによって呼び出されます。レプリカがプライマリーにプロモートされると、常に eXtreme Scale がこのメソッド (Status) の結果をテストして、その振る舞いを決定します。

表 1. 状況値および応答
返される状況値 eXtreme Scale の応答
Status.PRELOADED_ALREADY この状況値は、マップが完全にプリロードされていることを示しているため、eXtreme Scale はプリロード・メソッドをまったく呼び出しません。
Status.FULL_PRELOAD_NEEDED eXtreme Scale はマップをクリアし、プリロード・メソッドを正常に呼び出します。
Status.PARTIAL_PRELOAD_NEEDED eXtreme Scale は、マップを現状のままにして、プリロードを呼び出します。このストラテジーによって、アプリケーション・ローダーは、この時点以降プリロードを継続することができます。

プライマリーは、マップのプリロード中、返す必要のある状況をレプリカ側で判別できるように、複製中のマップ・セット内のマップに必ず何らかの状態を残す必要があります。RecoveryMap などと呼ばれる追加のマップを使用することができます。マップがプリロード中のデータで一貫して複製されるようにするため、この RecoveryMap は、プリロード中の同じマップ・セットの一部である必要があります。 推奨の実装は、以下のとおりです。

プリロードがレコードの各ブロックをコミットすると、プロセスも、RecoveryMap 内のカウンターまたは値をそのトランザクションの一部として更新します。プリロードされたデータと RecoveryMap データは、レプリカにアトミックに複製されます。レプリカがプライマリーに格上げされると、RecoveryMap をチェック して何が起こったかを確認できるようになります。

RecoveryMap は、状態キーを持つ単一エントリーを保持できます。このキーに対するオブジェクトが存在しない場合には、完全な preload (checkPreloadStatus returns FULL_PRELOAD_NEEDED) が必要です。 この状態キーに対するオブジェクトが存在し、値が COMPLETE の場合は、プリロードが完了し、checkPreloadStatus メソッドで PRELOADED_ALREADY が返されます。 これ以外の場合、値オブジェクトは、プリロードを再開する場所を示し、checkPreloadStatus メソッドは PARTIAL_PRELOAD_NEEDED を返します。 ローダーは、プリロードが呼び出されたときにローダーに開始点がわかるように、ローダーのインスタンス変数にリカバリー・ポイントを保管できます。また、各マップが個別にプリロードされる場合、RecoveryMap もマップごとにエントリーを保持できます。

Loader での同期レプリカ生成モードにおけるリカバリーの処理

eXtreme Scale ランタイムは、プライマリーが失敗したときにコミット済みデータを失わないよう設計されています。次のセクションでは、使用されるアルゴリズムについて説明します。これらのアルゴリズムは、レプリカ生成グループが同期レプリカ生成を使用する場合にのみ 適用されます。ローダーはオプションです。

eXtreme Scale ランタイムは、すべての変更がプライマリーからレプリカに同期複製されるように構成することができます。 同期レプリカが配置されると、その同期レプリカは、プライマリー断片にある既存データのコピー を受け取ります。この間もプライマリーはトランザクションを受け取り続け、受け取ったトランザクションを非同期にレプリカにコピーします。 レプリカはこの時点ではオンラインであるとは見なされません。

レプリカがプライマリーに 追いついた後、レプリカはピア・モードに入り、同期レプリカ生成 が始まります。プライマリーでコミットされたトランザクションはすべて同期レプリカに 送信され、プライマリーは各レプリカからの応答を 待ちます。ローダーを使用する、プライマリーでの同期コミット・シーケンスは、 以下の一連のステップのようになります。

表 2. プライマリーでのコミット・シーケンス
Loader を使用する場合のステップ Loader を使用しない場合のステップ
エントリーのロックを取得します。 同じ
変更をローダーにフラッシュします。 操作しない
キャッシュに変更を保存します。 同じ
変更をレプリカに送信し、確認応答を待機します。 同じ
TransactionCallback プラグインでローダーをコミットします。 プラグイン・コミットが呼び出されますが、何も実行しません。
エントリーのロックを解除します。 同じ

変更がレプリカに送信された後、ローダーにコミットされることに注意してください。変更がレプリカでコミットされる条件を判別するには、このシーケンスを訂正します。初期化時に、以下のようにプライマリーで tx リストを初期化します。

CommitedTx = {}, RolledBackTx = {}

同期コミットの処理中に、以下のシーケンスを使用します。

表 3. 同期コミット処理
Loader を使用する場合のステップ Loader を使用しない場合のステップ
エントリーのロックを取得します。 同じ
変更をローダーにフラッシュします。 操作しない
キャッシュに変更を保存します。 同じ
コミット済みトランザクションで変更を送信し、トランザクションをレプリカにロールバックし、確認応答を待機します。 同じ
コミット済みトランザクションおよびロールバック済みトランザクションのリストをクリアします。 同じ
TransactionCallBack プラグインでローダーをコミットします。 TransactionCallBack プラグイン・コミットがやはり呼び出されますが、通常、何も行われません。
コミットが成功した場合、トランザクションがコミット済みトランザクションに追加され、成功しなかった 場合はロールバック済みトランザクションに追加されます。 操作しない
エントリーのロックを解除します。 同じ

レプリカ処理の場合、以下のシーケンスを使用します。

  1. レプリカが変更されます。
  2. コミット済みトランザクション・リスト内のすべての受信済みトランザクションをコミットします。
  3. ロールバック済みトランザクション・リスト内のすべての受信済みトランザクションをロールバックします。
  4. トランザクションまたはセッションを開始します。
  5. トランザクションまたはセッションに変更を適用します。
  6. 保留リストにトランザクションまたはセッションを保存します。
  7. 応答を返信します。

レプリカがレプリカ・モードである間は、レプリカ上でローダーによる相互作用が行われないことに注意してください。プライマリーは、すべての変更を Loader を介してプッシュする必要があります。 レプリカは変更をプッシュしません。このアルゴリズムの副次作用は、レプリカに常にトランザクションがあるが、次のプライマリー・トランザクションによってこれらのトランザクションのコミット状況が送信されるまで、コミットされないことです。その場合には、トランザクションはレプリカ上でコミットまたはロールバックされます。このようになるまでは、トランザクションはコミットされません。短い時間 (数秒) 後にトランザクションの結果が送信されるようなタイマーをプライマリーに追加することができます。このタイマーは、その時刻ウィンドウに対する失効性を制限しますが、除去はしません。 こうした失効性は、レプリカ読み取りモードを使用する場合のみの問題です。それ以外の点では、失効性は、アプリケーションに影響を与えません。

プライマリーが失敗した場合、プライマリーでコミットまたはロールバックされたトランザクションがいくつかある可能性がありますが、これらの結果が含まれるメッセージがレプリカに到達しませんでした。レプリカが新規プライマリーにプロモートされる際の最初のアクションの 1 つは、この状態に対処することです。保留中の各トランザクションは、新規プライマリーのマップ・セットに対して再処理されます。ローダーがある場合は、そのローダーに各トランザクションが送られます。これらのトランザクションには、厳密な先入れ先出し法 (FIFO) 順序が適用されます。失敗したトランザクションは無視されます。例えば、3 つのトランザクション A、B、および C が保留中の場合、A はコミットし、B はロールバックし、C もコミットする可能性があります。1 つのトランザクション が他のトランザクションに影響を与えることはありません。これらのトランザクションは独立したものと見なされます。

ローダーで使用されるロジックは、フェイルオーバー・リカバリー・モードと通常モードの場合では若干異なることがあります。ローダーがフェイルオーバー・リカバリー・モードであるときは、ReplicaPreloadController インターフェースを実装することで容易に識別できます。checkPreloadStatus メソッドは、フェイルオーバー・リカバリーが完了した場合にのみ呼び出されます。 このため、Loader インターフェースの apply メソッドが checkPreloadStatus メソッドより前に呼び出される場合は、リカバリー・トランザクションになります。checkPreloadStatus メソッドが呼び出されると、フェイルオーバー・リカバリーが完了します。

レプリカ間のロード・バランシング

特に構成されていない限り、eXtreme Scale は、すべての読み取り要求と書き込み要求を 指定されたレプリカ生成グループのプライマリー・サーバーに送信します。プライマリーは、クライアントからのすべての要求にサービスを提供する必要があります。読み取り要求をプライマリーのレプリカに送信できるようにするとよいでしょう。読み取り要求をレプリカに送信することにより、読み取り要求の負荷を複数の Java 仮想マシン (JVM) で共有できるようになります。ただし、読み取り要求のためにレプリカを使用すると、応答が不整合になる可能性があります。

レプリカ間のロード・バランシングは、通常、クライアントが常時変更されるデータをキャッシュしているか、またはクライアントがペシミスティック・ロックを使用している場合にのみ使用されます。

データが絶えず変更され、そのためクライアントのニア・キャッシュで無効化された場合は、結果としてクライアントからプライマリーへの get 要求率が比較的高くなります。同様に、ペシミスティック・ロック・モードでは、ローカル・キャッシュが存在しないため、すべての要求がプライマリーに送信されます。

データが比較的静的であるか、またはペシミスティック・モードが使用されていない場合には、読み取り要求をレプリカへ送信しても、パフォーマンスにそれほど大きな影響を与えません。データで満杯のキャッシュを持つクライアントからの get 要求の頻度は、高くありません。

クライアントが始動されたばかりのときには、ニア・キャッシュは空です。空のキャッシュに対するキャッシュ要求は、プライマリーに転送されます。時間が経過してクライアント・キャッシュにデータが入れられると、要求ロードは除去されます。多数のクライアントが同時に始動する場合は、ロード (負荷) が重大な影響を及ぼす可能性があります。そのため、レプリカ読み取りがパフォーマンス上適切な選択になることがあります。

クライアント・サイドのレプリカ生成

eXtreme Scale により、非同期レプリカ生成を使用して、サーバー・マップを 1 つ以上のクライアントに複製することができます。 クライアントは ClientReplicableMap.enableClientReplication メソッドを使用して、サーバー・サイド・マップのローカルの読み取り専用コピーを要求できます。

void enableClientReplication(Mode mode, int[] partitions, 
ReplicationMapListener listener) throws ObjectGridException;

最初のパラメーターはレプリカ生成モードです。このモードには、連続レプリカ生成またはスナップショット・レプリカ生成を指定できます。2 番目のパラメーターは、データの複製元の区画を表す区画 ID の配列です。この値がヌルの場合、または空の配列の場合、データはすべての区画から複製されます。最後のパラメーターは、クライアント・レプリカ生成イベントを受信するためのリスナーです。詳しくは、API 資料の ClientReplicableMap および ReplicationMapListener を参照してください。

レプリカ生成が有効になると、サーバーはクライアントへのマップの複製を開始します。結局のところ、クライアントは、どの時点においてもわずか数トランザクションでサーバーに到達します。