第8章 パーティション処理の設定

8.1. パーティション処理

Data Grid クラスターは、データが保存される複数のノードから構築されます。ノード障害がある状態でデータを失わないようにするために、Data Grid は複数のノードにわたり、Data Grid parlance の同じデータ -- キャッシュエントリーをコピーします。このレベルのデータ冗長性は、numOwners 設定属性を介して設定され、同時にクラッシュするノードが numOwners 未満である限り、DataGrid に使用可能なデータのコピーがあることを保証します。

ただし、numOwners を超えるノードがクラスターから消えるという壊滅的な状況が発生する可能性があります。

スプリットブレイン
たとえば、ルーターのクラッシュにより、クラスターは 2 つ以上のパーティションに分割されるか、個別に動作するサブクラスターに分割されます。このような場合、異なるパーティションから読み取り/書き込みを行う複数のクライアントが、同じキャッシュエントリーの異なるバージョンを参照し、多くのアプリケーションで問題があります。冗長ネットワークや IP ボンディング など、スプリットブレインが発生する可能性を軽減する方法があることに注意してください。これらは、問題の発生期間のみを縮小します。
numOwners ノードを順番にクラッシュ
numOwners ノードは迅速な成功でクラッシュし、Data Grid にクラッシュ間の状態を適切にリバランスする時間がない場合、結果は部分的なデータの損失になります。

このセクションで説明されているパーティション処理機能により、スプリットブレインが発生したときに、キャッシュで実行できる操作を設定できます。Data Grid は、複数のパーティション処理ストラテジーを提供します。これは、Brewer の CAP 定理 の観点から、パーティションが存在する場合に可用性または一貫性が犠牲になるかどうかを決定します。以下は、指定されるストラテジーの一覧です。

ストラテジー詳細CAP

DENY_READ_WRITES

パーティションに特定のセグメントのすべての所有者がない場合、そのセグメント内のすべてのキーの読み取りと書き込みの両方が拒否されます。

一貫性

ALLOW_READS

このパーティションに存在する場合は特定のキーの読み取りを許可しますが、このパーティションにセグメントのすべての所有者が含まれている場合にのみ書き込みを許可します。これは、このパーティションで利用可能なものの、クライアントアプリケーションの観点から見えても決定論的ではない場合に一部のエントリーが読み取り可能であるため、一貫したアプローチです。

一貫性

ALLOW_READ_WRITES

各パーティションのエントリーが分岐することを許可し、パーティションのマージ時に競合解決を試みます。

可用性

アプリケーションの要件によって、適切なストラテジーを決定する必要があります。たとえば、DENY_READ_WRITES は、高い整合性要件を持つアプリケーションにより適しています。つまり、システムからデータを読み取ったデータを正確にする必要があります。一方、Data Grid がベストエフォートキャッシュとして使用されている場合、パーティションは完全に許容できる可能性があり、一貫性よりも可用性を優先するため、ALLOW_READ_WRITES の方が適切な場合があります。

以下のセクションでは、各パーティション処理ストラテジーに対して Data Grid が スプリットブレイン連続した障害 を処理する方法を説明します。この後に、Data Grid が マージポリシー を介したパーティションマージ時に自動競合解決を可能にする方法を説明するセクションが続きます。最後に、パーティション処理ストラテジーおよびマージポリシーの設定方法 を説明するセクションを説明します。

8.1.1. スプリットブレイン

スプリットブレインの状況では、各ネットワークパーティションは独自の JGroups ビューをインストールし、他のパーティションからノードを削除します。パーティションが互いに認識していないため、2 つ以上のパーティションに分割されているかどうかを直接判断する方法はありません。代わりに、明示的な脱退メッセージを送信せずに 1 つ以上のノードが JGroups クラスターから消えたときに、クラスターが分割されたと想定します。

8.1.1.1. 分割ストラテジー

このセクションでは、スプリットブレインが発生したときに各パーティション処理ストラテジーがどのように動作するかを詳細に説明します。

8.1.1.1.1. ALLOW_READ_WRITES

各パーティションは、AVAILABLE モードで残りのすべてのパーティションで、独立したクラスターとして機能します。つまり、各パーティションはデータの一部のみを確認でき、各パーティションはキャッシュに競合する更新を書き込む可能性があることを意味します。これらの競合をマージすると、ConflictManager と設定された EntryMergePolicy を利用することで自動的に解決されます。

8.1.1.1.2. DENY_READ_WRITES

分割が検出されると、各パーティションは即座にリバランスを開始しませんが、まずは代わりに DEGRADED モードになるかどうかを確認します。

  • 少なくとも 1 つのセグメントがすべての所有者を失った場合 (最後のリバランスが終了してから少なくともnumOwnersノードが残っていることを意味します)、パーティションは DEGRADED モードに入ります。
  • 最新の安定したトポロジー に、パーティションに単純なノード (floor(numNodes/2) + 1) が含まれていない場合は、パーティションも DEGRADED モードになります。
  • それ以外の場合、パーティションは正常に機能し、リバランスを開始します。

安定したトポロジーは、リバランス操作が終了するたびに更新され、コーディネーターによって別のリバランスが不要であると判断されます。

これらのルールは、複数のパーティションが AVAILABLE モードになるようにし、他のパーティションが DEGRADED モードになるようにします。

パーティションが DEGRADED モードの場合、完全に所有されているキーへのアクセスのみを許可します。

  • このパーティション内のノード上のコピーをすべて持つエントリーのリクエスト (読み取りおよび書き込み) は異常な状態です。
  • 消えたノードによって部分的または完全に所有されているエントリーの要求は、AvailabilityException で拒否されます。

これにより、パーティションが同じキーに異なる値を書き込めないようにし (キャッシュの一貫性を保つ)、また、1 つのパーティションが他のパーティションで更新されたキーを読み取れないようにすることができます (古いデータはありません)。

強調を行うには、numOwners = 2 を使用した分散モードに設定されている初期クラスター M = {A, B, C, D} を検討してください。さらに、owners(k1) = {A,B}owners(k2) = {B,C}、および owners(k3) = {C,D} となる 3 つのキー k1k2、および k3(キャッシュに存在するかどうかは不明) について考えてみます。次に、ネットワークは N1 = {A, B}N2 = {C, D} の 2 つのパーティションに分割され、DEGRADED モードに入り、以下のように動作します。

  • N1 では、k1 は読み取り/書き込みに使用でき、k2(部分的に所有) と k3(所有されていない) は使用できず、それらにアクセスすると、AvailabilityException が発生します。
  • N2 では k1 および k2 は読み取り/書き込みには使用できません。k3 が利用できます。

パーティション処理プロセスの関連する要素として、スプリットブレインが発生すると、作成されるパーティションは、キーの所有権を算出するために元のセグメントマッピング (スプリットブレインの前に存在したもの) に依存します。k1k2、または k3 がすでに存在しているかどうかは重要で、それらの可用性は同じです。

さらに別の時点でネットワークが回復し、N1 パーティションと N2 パーティションがマージして最初のクラスター M に戻ると、M は劣化モードを終了し、再び完全に使用可能になります。このマージ操作中、M は再利用可能になったため、ConflictManager と設定された EntryMergePolicy を使用して、スプリットブレインの発生と検出の間に発生した可能性のある競合をチェックします。

別の例として、クラスターは O1 = {A, B, C} および O2 = {D} の 2 つのパーティションに分割して、パーティション O1 が完全に使用可能になるようにすることができます (残りのメンバーのキャッシュエントリーのバランスを取り直します)。ただし、パーティション O2 は分割を検出し、パフォーマンスが低下します。キーは完全に所有されていないため、AvailabilityException による読み取り操作または書き込み操作は拒否されます。

その後、O1 および O2 のパーティションが M にマージし直すと、ConflictManager は競合を解決しようとし、もう一度 D が完全に利用可能になります。

8.1.1.1.3. ALLOW_READS

パーティションは DENY_READ_WRITES と同じ方法で処理されます。ただし、パーティションが DEGRADED モードの場合に、部分的に所有されたキー上の読み取り操作は、AvailabilityException を出力しません。

8.1.1.2. 現在の制限

2 つのパーティションは、分離して開始できます。マージしない限り、一貫性のないデータの読み込み、書き込みを行うことができます。今後は、カスタムの可用性ストラテジーを許可します (例: 特定のノードがクラスターの一部であるか、または外部のマシンにアクセスできるかどうかを確認など)。

8.1.2. 連続するノードが停止

前項で説明したように、Data Grid は、プロセス/マシンがクラッシュするため、またはネットワーク障害が原因でノードが JGroups ビューを残すかどうかを検出できません。ノードが JGroups クラスターを突然とすると、ネットワークの問題が原因でノードが JGroups クラスターを終了すると想定されます。

設定したコピー数 (numOwners) が 1 よりも大きい場合、クラスターは利用可能なままになり、クラッシュしたノードでデータの新しいレプリカを作成しようとします。ただし、リバランスプロセス中にその他のノードがクラッシュする可能性があります。短期間に numOwners ノードがクラッシュすると、一部のキャッシュエントリーがクラスターから完全に消える可能性があります。この場合、DENY_READ_WRITES または ALLOW_READS ストラテジーを有効にすると、(正しい)Data Grid はスプリットブレインのセクションに記載されているように、DEGRADED モードに入ります。

管理者は、numOwners を超えるノードを急速にシャットダウンして、それらのノードにのみ保存したデータが失われないようにすることもできます。管理者がノードを正常にシャットダウンすると、Data Grid はノードに戻ることができないことを認識します。ただし、クラスターは各ノードの残状況を追跡せず、それらのノードがクラッシュしたかのように、キャッシュが DEGRADED モードに入ります。

この段階では、クラスターが停止し、外部ソースからのデータを使用して再起動時にその状態を回復する方法はありません。クラスターは、numOwners の連続するノードの失敗を回避するために、適切な numOwners で設定されることが予想されます。したがって、この状況は非常に稀なはずです。アプリケーションがキャッシュ内の一部のデータを失うことが可能な場合、管理者は JMX 経由で可用性モードを AVAILABLE に戻すことができます。

8.1.3. 競合マネージャー

競合マネージャーは、ユーザーが特定のキーに保存されているすべてのレプリカ値を取得できるツールです。保存したレプリカの値が競合しているキャッシュエントリーのストリームをユーザーが処理できるようにする他にもあります。さらに、EntryMergePolicy インターフェイスの実装を利用することで、そのような競合を自動的に解決できます。

8.1.3.1. 競合の検出

競合は、指定のキーの保存された各値を取得することによって検出されます。競合マネージャーは、現在の整合性ハッシュによって定義された各キーの書き込み所有者から保存された値を取得します。保存した値の .equals メソッドは、すべての値が等しいかどうかを判断するために使用されます。すべての値が等しい場合は、キーに競合が発生しません。それ以外の場合は競合が発生しています。指定のノードにエントリーが存在しない場合は、null 値が返されることに注意してください。そのため、指定のキーに null 値と非 null 値が存在する場合に競合が発生している点に注意してください。

8.1.3.2. マージポリシー

特定の CacheEntry のレプリカ間で競合が発生した場合は、競合解決アルゴリズムを定義する必要があるため、EntryMergePolicy インターフェイスを提供します。このインターフェイスは、単一のメソッド"merge"で設定され、その返された CacheEntry は、指定されたキーの"resolved"エントリーとして使用されます。null 以外の CacheEntry が返されると、このエントリー値はキャッシュのすべてのレプリカに "put" されます。ただし、マージの実装が null 値を返すと、競合するキーに関連付けられたすべてのレプリカがキャッシュから削除されます。

merge メソッドは、"preferredEntry" および "otherEntries" という 2 つのパラメーターを取ります。パーティションのマージのコンテキストでは、希望の Entry がパーティションに保存されている CacheEntry のプライマリーレプリカで、パーティションには最も大きな topologyId を持つものと等しくなります。パーティションが重複している場合、つまりノード A は両方のパーティション {A}、{A,B,C} のトポロジーにあります。このパーティションでは、他のパーティションのトポロジーの背後に topologId が高いため、{A} を選択します。パーティションのマージが発生しないと、"preferredEntry" は CacheEntry のプライマリーレプリカです。2 番目のパラメーター "otherEntries" は、競合が検出されたキーに関連付けられた他のすべてのエントリーのリストです。

注記

EntryMergePolicy::merge は、競合が検出されたときにのみ呼び出され、すべての CacheEntrys が同じ場合は呼び出されません。

現在、Data Grid は EntryMergePolicy の以下の実装を提供します。

ポリシー詳細

MergePolicy.NONE(デフォルト)

競合を解決するための試行はありません。マイノリティパーティションでホストされているエントリーは削除され、このパーティションのノードはリバランスが開始されるまでデータを保持しません。この動作は、競合解決をサポートしない Infinispan の以前のバージョンと同等であることに注意してください。この場合、マイナーパーティションでホストされるエントリーに対する変更はすべて失われますが、リバランスが終了するとすべてのエントリーの一貫性が保たれます。

MergePolicy.PREFERRED_ALWAYS

常に "preferredEntry" を使用します。MergePolicy.NONE は、PREFERRED_ALWAYS とほぼ同等で、競合解決の実行によるパフォーマンスへの影響はありません。そのため、以下のシナリオが懸念されない限り、MergePolicy.NONE を選択する必要があります。DENY_READ_WRITES または DENY_READ ストラテジーを使用する場合は、パーティションが DEGRADED モードに入る場合にのみ、書き込み操作が部分的に完了するため、一貫性のない値が含まれるレプリカが作成されます。MergePolicy.PREFERRED_ALWAYS は対象の不整合を検出し、これを解決します。ただし、MergePolicy.NONE の場合、CacheEntry レプリカはクラスターのリバランス後に一貫性がありません。

MergePolicy.PREFERRED_NON_NULL

null 以外の場合は "preferredEntry" を使用します。それ以外の場合は、"otherEntries" の最初のエントリーを利用します。

MergePolicy.REMOVE_ALL

競合が検出されると、必ずキャッシュからキーを削除します。

完全修飾クラス名

マージのカスタム実装は、カスタムマージポリシー を使用します。

8.1.4. 使用法

パーティションが ConflictManager をマージすると、設定された EntryMergePolicy の競合を自動的に解決しようとしますが、アプリケーションが必要とする競合の有無を手動で検索または解決することもできます。

以下のコードは、EmbedCacheManager の ConflictManager を取得する方法、指定されたキーの全バージョンを取得する方法、および特定のキャッシュ全体で競合をチェックする方法を示しています。

EmbeddedCacheManager manager = new DefaultCacheManager("example-config.xml");
Cache<Integer, String> cache = manager.getCache("testCache");
ConflictManager<Integer, String> crm = ConflictManagerFactory.get(cache.getAdvancedCache());

// Get All Versions of Key
Map<Address, InternalCacheValue<String>> versions = crm.getAllVersions(1);

// Process conflicts stream and perform some operation on the cache
Stream<Map<Address, CacheEntry<Integer, String>>> conflicts = crm.getConflicts();
conflicts.forEach(map -> {
   CacheEntry<Integer, String> entry = map.values().iterator().next();
   Object conflictKey = entry.getKey();
   cache.remove(conflictKey);
});

// Detect and then resolve conflicts using the configured EntryMergePolicy
crm.resolveConflicts();

// Detect and then resolve conflicts using the passed EntryMergePolicy instance
crm.resolveConflicts((preferredEntry, otherEntries) -> preferredEntry);
注記

ConflictManager::getConflicts ストリームはエントリーごとに処理されますが、基礎となるスプリットは、セグメントごとにキャッシュエントリーを遅延読み込みしています。

8.1.5. パーティション処理の設定

キャッシュが分散またはレプリケートされない限り、パーティション処理の設定は無視されます。デフォルトのパーティション処理ストラテジーは ALLOW_READ_WRITES で、デフォルトの EntryMergePolicy は MergePolicies::PREFERRED_ALWAYS です。

<distributed-cache name="the-default-cache">
   <partition-handling when-split="ALLOW_READ_WRITES" merge-policy="PREFERRED_NON_NULL"/>
</distributed-cache>

プログラムを使用しても同じことができます。

ConfigurationBuilder dcc = new ConfigurationBuilder();
dcc.clustering().partitionHandling()
                    .whenSplit(PartitionHandling.ALLOW_READ_WRITES)
                    .mergePolicy(MergePolicy.PREFERRED_ALWAYS);

8.1.5.1. カスタムマージポリシーの実装

EntryMergePolicy のカスタム実装を提供することもできます。

<distributed-cache name="mycache">
   <partition-handling when-split="ALLOW_READ_WRITES"
                       merge-policy="org.example.CustomMergePolicy"/>
</distributed-cache>
ConfigurationBuilder dcc = new ConfigurationBuilder();
dcc.clustering().partitionHandling()
                    .whenSplit(PartitionHandling.ALLOW_READ_WRITES)
                    .mergePolicy(new CustomMergePolicy());
public class CustomMergePolicy implements EntryMergePolicy<String, String> {

   @Override
   public CacheEntry<String, String> merge(CacheEntry<String, String> preferredEntry, List<CacheEntry<String, String>> otherEntries) {
      // decide which entry should be used

      return the_solved_CacheEntry;
   }

8.1.5.2. Infinispan サーバーインスタンスへのカスタムマージポリシーのデプロイ

サーバーでカスタム EntryMergePolicy 実装を使用するには、実装クラスをサーバーにデプロイする必要があります。これは、java service-provider の規則を使用し、EntryMergePolicy 実装の完全修飾クラス名を含む META-INF/services/org.infinispan.conflict.EntryMergePolicy ファイルを持つ jar のクラスファイルをパッケージ化することで実現できます。

# list all necessary implementations of EntryMergePolicy with the full qualified name
org.example.CustomMergePolicy

カスタムマージポリシーをサーバー上で利用できるようにするには、ポリシーセマンティックが保存された Key/Value オブジェクトにアクセスする必要がある場合は、オブジェクトストレージを有効にする必要があります。これは、サーバーのキャッシュエントリーはマーシャル形式に格納され、ポリシーに返された Key/Value オブジェクトは WrappedByteArray のインスタンスとなる可能性があるためです。ただし、カスタムポリシーがキャッシュエントリーに関連付けられたメタデータのみに依存する場合、オブジェクトストレージは必要ではなく、リクエストごとのマーシャリングデータの追加のパフォーマンスコストが原因で (他の理由で必要としなければ) 回避する必要があります。最後に、提供されるマージポリシーのいずれかが使用されている場合は、オブジェクトストレージは必要ありません。

8.1.6. 監視および管理

キャッシュの可用性モードは、JMX で Cache MBean の属性として公開されます。属性は書き込み可能で、管理者はキャッシュを DEGRADED モードから AVAILABLE(一貫性のコスト) に強制的に移行することができます。

可用性モードは、AdvancedCache インターフェイスからもアクセスできます。

AdvancedCache ac = cache.getAdvancedCache();

// Read the availability
boolean available = ac.getAvailability() == AvailabilityMode.AVAILABLE;

// Change the availability
if (!available) {
   ac.setAvailability(AvailabilityMode.AVAILABLE);
}