第10章 ロックと同時実行の設定
Data Grid は、マルチバージョン同時実行制御 (MVCC) を使用して、共有データへのアクセスを改善します。
- 同時リーダーとライターの許可
- リーダーとライターが互いにブロックしない
- 書き込みスキューを検出して処理できる
- 内部ロックのストライピングが可能
10.1. ロックおよび同時実行
マルチバージョン同時実行制御 (MVCC) は、リレーショナルデータベースやその他のデータストアで一般的な同時実行スキームです。MVCC には、粗粒度の Java 同期や、共有データにアクセスするための JDK ロックに比べて、多くの利点があります。
Data Grid の MVCC 実装では、ロックと同期が最小限に抑えられており、可能な限り compare-and-swap などのロックフリー技術やロックフリーのデータ構造などに重点を置いています。これにより、マルチ CPU 環境とマルチコア環境の最適化に役立ちます。
特に、Data Grid の MVCC 実装はリーダーに対して高度に最適化されています。リーダースレッドは、エントリーの明示的なロックを取得せず、代わりに問題のエントリーを直接読み込みます。
一方、ライターは、書き込みロックを取得する必要があります。これにより、エントリーごとに 1 つの同時書き込みのみが保証されるため、同時ライターはキューイングしてエントリーを変更することになります。
同時読み取りを可能にするため、ライターはエントリーを MVCCEntry
でラップして、変更する予定のエントリーのコピーを作成します。このコピーは、同時リーダーが部分的に変更された状態を認識できないようにします。書き込みが完了したら、MVCCEntry.commit()
はデータコンテナーへの変更をフラッシュし、後続のリーダーに変更内容が反映されます。
10.1.1. クラスター化されたキャッシュおよびロック
Data Grid クラスターでは、プライマリー所有者ノードがキーをロックします。
非トランザクションキャッシュの場合、Data Grid は書き込み操作をキーのプライマリー所有者に転送して、ロックを試行できるようにします。次に、Data Grid は、他の所有者に書き込み操作を転送するか、キーをロックできない場合は例外を出力します。
操作が条件付きで、プライマリー所有者で失敗した場合、Data Grid はこれを他の所有者には転送しません。
トランザクションキャッシュの場合、プライマリーの所有者は楽観的および悲観的ロックモードでキーをロックできます。Data Grid は、トランザクション間の同時読み取りを制御する異なる分離レベルもサポートします。
10.1.2. LockManager
LockManager
は、書き込み用にエントリーをロックするコンポーネントです。LockManager
は、LockContainer
を使用して、ロックを検索、保持、作成します。LockContainers
には、ロックストライピングをサポートするものと、エントリーごとに 1 つのロックをサポートするものの 2 つの大まかな特徴があります。
10.1.3. ロックストライピング
ロックストライピングでは、固定サイズの共有ロックコレクションをキャッシュ全体に使用する必要があり、ロックはエントリーのキーのハッシュコードに基づいてエントリーに割り当てられます。JDK の ConcurrentHashMap
がロックを割り当てる方法と同様に、これにより、関連性のない可能性のあるエントリーが同じロックによってブロックされる代わりに、拡張性の高い固定オーバーヘッドのロックメカニズムが可能になります。
別の方法は、ロックストライピングを無効にすることです。これは、エントリーごとに 新しい ロックが作成されることを意味します。このアプローチでは、スループットが高くなる 可能性 がありますが、追加のメモリー使用量やガベージコレクションのチャーンなどのコストがかかります。
異なるキーのロックが同じロックストライプになってしまうとデッドロックが発生する可能性があるため、ロックストライピングはデフォルトで無効になっています。
ロックストライピングで使用される共有ロックコレクションのサイズは、<locking />
設定要素の concurrencyLevel
属性を使用して調整できます。
設定例:
<locking striping="false|true"/>
または、以下を実行します。
new ConfigurationBuilder().locking().useLockStriping(false|true);
10.1.4. 同時実行レベル
この同時実行レベルは、ストライプロックコンテナーのサイズを決定する他に、DataContainer
の内部など、関連する JDK ConcurrentHashMap
ベースのコレクションを調整するためにも使用されます。このパラメーターは、Data Grid でもまったく同じ方法で使用されているため、同時実行レベルの詳細は、JDK ConcurrentHashMap
Javadocs を参照してください。
設定例:
<locking concurrency-level="32"/>
または、以下を実行します。
new ConfigurationBuilder().locking().concurrencyLevel(32);
10.1.5. ロックタイムアウト
ロックタイムアウトは、競合するロックを待つ時間 (ミリ秒単位) を指定します。
設定例:
<locking acquire-timeout="10000"/>
または、以下を実行します。
new ConfigurationBuilder().locking().lockAcquisitionTimeout(10000); //alternatively new ConfigurationBuilder().locking().lockAcquisitionTimeout(10, TimeUnit.SECONDS);
10.1.6. 一貫性
(すべての所有者がロックされているのとは対照的に) 単一の所有者がロックされるという事実により、次の一貫性の保証が失われることはありません。キー K
がノード {A, B}
に対してハッシュ化され、トランザクション TX1
が、たとえば、A
上の K
のロックを取得したとします。別のトランザクション TX2
が B
(またはその他のノード) 上で開始され、TX2
が K
のロックを試みる場合、ロックがすでに TX1
によって保持されているため、タイムアウトでロックに失敗します。理由は、キー K
のロックがトランザクションの発生場所に関係なく、常に、確定的に、クラスターの同じノードで取得されるからです。
10.1.7. データのバージョン管理
Data Grid は、simple と external の 2 つの形式のデータバージョン管理をサポートします。simple バージョン管理は、書き込みスキューチェックのトランザクションキャッシュで使用されます。
external バージョン管理は、Data Grid を Hibernate で使用する場合など、Data Grid 内のデータバージョン管理の外部ソースをカプセル化するために使用され、そのデータバージョン情報をデータベースから直接取得します。
このスキームでは、バージョンに渡すメカニズムが必要になり、オーバーロードされたバージョン put()
および putForExternalRead()
が、AdvancedCache
で提供され、外部データバージョンを取り込みます。その後、これは InvocationContext
に保管され、コミット時にエントリーに適用されます。
external バージョン管理の場合、書き込みスキューチェックは実行できず、実行されません。