第9章 トランザクションの設定
分散システム上に存在するデータは、一時的なネットワークの停止やシステム障害、あるいは単純なヒューマンエラーによって生じるエラーなどの影響を受けやすくなっています。このような外部要素については制御できませんが、データの品質に深刻な影響を与える可能性があります。データ破損の影響は、顧客満足度の低下、サービスが利用できなくなるほどのコストのかかるシステムの再構築など多岐にわたります。
Data Grid は、ACID (atomicity, consistency, isolation, durability) トランザクションを実行して、キャッシュの状態が一貫していることを確認できます。
9.1. トランザクション
Data Grid は、JTA 準拠のトランザクションを使用し、参加するように設定できます。
または、トランザクションのサポートが無効になっている場合は、JDBC 呼び出しで自動コミットを使用する場合と同等になります。ここでは、すべての変更後に変更がレプリケートされる可能性があります (レプリケーションが有効な場合)。
すべてのキャッシュ操作で Data Grid は以下を行います。
- スレッドに関連する現在の トランザクション を取得します。
- トランザクションのコミットまたはロールバック時に通知されるように、XAResource をトランザクションマネージャーに登録します (登録されていない場合)。
これを実行するには、キャッシュに環境の TransactionManager への参照を提供する必要があります。これは通常、TransactionManagerLookup インターフェイスの実装のクラス名を使用してキャッシュを設定することで行います。キャッシュが起動すると、このクラスのインスタンスを作成し、TransactionManager
への参照を返す getTransactionManager()
メソッドを呼び出します。
Data Grid には複数のトランザクションマネージャールックアップクラスが同梱されます。
トランザクションマネージャールックアップの実装
- EmbeddedTransactionManagerLookup: これは、他の実装が利用できない場合に、組み込みモードのみに使用する必要がある基本的なトランザクションマネージャーを提供します。この実装は、同時トランザクションおよびリカバリーでは、重大な制限があります。
-
JBossStandaloneJTAManagerLookup: スタンドアロン環境、または JBoss AS 7 以前、および WildFly 8、9、10 で Data Grid を実行している場合、トランザクションマネージャーのデフォルトとしてこれを選択します。このトランザクションは、
EmbeddedTransactionManager
の不足をすべて解消する JBoss Transactions をベースとした本格的なトランザクションマネージャーです。 - WildflyTransactionManagerLookup: WildFly 11 以降で Data Grid を実行している場合は、トランザクションマネージャーのデフォルトとしてこれを選択します。
-
GenericTransactionManagerLookup: これは、最も一般的な Java EE アプリケーションサーバーでトランザクションマネージャーを見つけるルックアップクラスです。トランザクションマネージャーが見つからない場合は、
EmbeddedTransactionManager
がデフォルトの設定になります。
初期化すると、TransactionManager
は Cache
自体から取得することもできます。
//the cache must have a transactionManagerLookupClass defined Cache cache = cacheManager.getCache(); //equivalent with calling TransactionManagerLookup.getTransactionManager(); TransactionManager tm = cache.getAdvancedCache().getTransactionManager();
9.1.1. トランザクションの設定
トランザクションはキャッシュレベルで設定されます。以下はトランザクションの動作に影響する設定と、各設定属性の簡単な説明になります。
<locking isolation="READ_COMMITTED"/> <transaction locking="OPTIMISTIC" auto-commit="true" complete-timeout="60000" mode="NONE" notifications="true" reaper-interval="30000" recovery-cache="__recoveryInfoCacheName__" stop-timeout="30000" transaction-manager-lookup="org.infinispan.transaction.lookup.GenericTransactionManagerLookup"/>
プログラムを使用する場合
ConfigurationBuilder builder = new ConfigurationBuilder(); builder.locking() .isolationLevel(IsolationLevel.READ_COMMITTED); builder.transaction() .lockingMode(LockingMode.OPTIMISTIC) .autoCommit(true) .completedTxTimeout(60000) .transactionMode(TransactionMode.NON_TRANSACTIONAL) .useSynchronization(false) .notifications(true) .reaperWakeUpInterval(30000) .cacheStopTimeout(30000) .transactionManagerLookup(new GenericTransactionManagerLookup()) .recovery() .enabled(false) .recoveryInfoCacheName("__recoveryInfoCacheName__");
-
isolation
- 分離レベルを設定します。詳細は、分離レベル を参照してください。デフォルトはREPEATABLE_READ
です。 -
locking
- キャッシュが楽観的または悲観的ロックを使用するかどうかを設定します。詳細は、トランザクションのロック を参照してください。デフォルトはOPTIMISTIC
です。 -
auto-commit
: 有効にすると、ユーザーは 1 回の操作でトランザクションを手動で開始する必要はありません。トランザクションは自動的に起動およびコミットされます。デフォルトはtrue
です。 -
complete-timeout
- 完了したトランザクションに関する情報を保持する期間 (ミリ秒単位)。デフォルトは60000
です。 mode
: キャッシュがトランザクションかどうかを設定します。デフォルトはNONE
です。利用可能なオプションは以下のとおりです。-
NONE
- 非トランザクションキャッシュ -
FULL_XA
- リカバリーが有効になっている XA トランザクションキャッシュリカバリーの詳細は、トランザクションリカバリー を参照してください。 -
NON_DURABLE_XA
- リカバリーが無効になっている XA トランザクションキャッシュ。 -
NON_XA
- XA の代わりに 同期化 を介して統合されたトランザクションキャッシュ。詳細は、同期の登録 のセクションを参照してください。 -
BATCH
- バッチを使用して操作をグループ化するトランザクションキャッシュ。詳細は バッチ処理 のセクションを参照してください。
-
-
notifications
- キャッシュリスナーのトランザクションイベントを有効/無効にします。デフォルトはtrue
です。 -
reaper-interval
- トランザクション完了情報をクリーンアップするスレッドが開始する間隔 (ミリ秒単位)。デフォルトは30000
です。 -
recovery-cache
- リカバリー情報を保存するキャッシュ名を設定します。リカバリーの詳細は、トランザクションリカバリー を参照してください。デフォルトはrecoveryInfoCacheName
です。 -
stop-timeout
- キャッシュの停止時に進行中のトランザクションを待機する時間 (ミリ秒単位)。デフォルトは30000
です。 -
transaction-manager-lookup
-jakarta.transaction.TransactionManager
への参照を検索するクラスの完全修飾クラス名を設定します。デフォルトはorg.infinispan.transaction.lookup.GenericTransactionManagerLookup
です。
2 フェーズコミット (2PC) が Data Grid に実装される方法、およびロックが取得される方法の詳細は、以下のセクションを参照してください。設定の詳細は、設定リファレンス を参照してください。
9.1.2. 分離レベル
Data Grid は、READ_COMMITTED および REPEATABLE_READ の 2 つの分離レベルを提供します。
これらの分離レベルは、リーダーが同時書き込みを確認するタイミングを決定し、MVCCEntry
の異なるサブクラスを使用して内部的に実装されます。MVCCEntry では、状態がデータコンテナーにコミットされる方法が異なります。
以下は、Data Grid のコンテキストの READ_COMMITTED
および REPEATABLE_READ
の違いを理解する上で役立つ詳細な例です。READ_COMMITTED
の場合、同じキーで連続して 2 つの読み取り呼び出しを行うと、キーが別のトランザクションによって更新され、2 つ目の読み取りによって新しい更新値が返されることがあります。
Thread1: tx1.begin() Thread1: cache.get(k) // returns v Thread2: tx2.begin() Thread2: cache.get(k) // returns v Thread2: cache.put(k, v2) Thread2: tx2.commit() Thread1: cache.get(k) // returns v2! Thread1: tx1.commit()
REPEATABLE_READ
では、最終 get は引き続き v
を返します。そのため、トランザクション内で同じキーを複数回取得する場合は、REPEATABLE_READ
を使用する必要があります。
ただし、読み取りロックが REPEATABLE_READ
に対しても取得されないため、この現象が発生する可能性があります。
cache.get("A") // returns 1 cache.get("B") // returns 1 Thread1: tx1.begin() Thread1: cache.put("A", 2) Thread1: cache.put("B", 2) Thread2: tx2.begin() Thread2: cache.get("A") // returns 1 Thread1: tx1.commit() Thread2: cache.get("B") // returns 2 Thread2: tx2.commit()
9.1.3. トランザクションのロック
9.1.3.1. 悲観的なトランザクションキャッシュ
ロック取得の観点では、悲観的トランザクションはキーの書き込み時にキーのロックを取得します。
- ロック要求がプライマリー所有者に送信されます (明示的なロック要求または操作のいずれか)。
プライマリーの所有者はロックの取得を試みます。
- 成功した場合は、正の応答が返されます。
- そうでない場合は、負の応答が送信され、トランザクションはロールバックされます。
たとえば、以下のようになります。
transactionManager.begin(); cache.put(k1,v1); //k1 is locked. cache.remove(k2); //k2 is locked when this returns transactionManager.commit();
cache.put(k1,v1)
が返されると、k1
はロックされ、クラスター内のどこかで実行中の他のトランザクションは、これに書き込むことができません。k1
の読み取りは引き続き可能です。トランザクションの完了時に k1
のロックが解放されます (コミットまたはロールバック)。
条件付き操作の場合、検証はオリジネーターで実行されます。
9.1.3.2. 楽観的トランザクションキャッシュ
楽観的トランザクションロックはトランザクションの準備時に取得され、トランザクションのコミット (またはロールバック) まで保持されます。これは、書き込みでローカルロックを取得し、準備中にクラスターのロックが取得される 5.0 デフォルトロックモデルとは異なります。
- 準備はすべての所有者に送信されます。
プライマリーの所有者は、必要なロックの取得を試みます。
- ロックに成功すると、書き込みのスキューチェックが実行されます。
- 書き込みスキューチェックが成功した場合 (または無効化された場合) は、正の応答を送信します。
- それ以外の場合は、負の応答が送信され、トランザクションはロールバックされます。
たとえば、以下のようになります。
transactionManager.begin(); cache.put(k1,v1); cache.remove(k2); transactionManager.commit(); //at prepare time, K1 and K2 is locked until committed/rolled back.
条件付きコマンドの場合、検証は引き続きオリジネーターで実行されます。
9.1.3.3. 悲観的または楽観的トランザクションのどちらが必要か
ユースケースの観点からは、同時に実行されている複数のトランザクション間で多くの競合が ない 場合は、楽観的トランザクションを使用する必要があります。これは、読み取り時と、コミット時 (書き込みスキューチェックが有効) の間でデータが変更された場合に、楽観的トランザクションがロールバックするためです。
一方、キーでの競合が多く、トランザクションのロールバックがあまり望ましくない場合は、悲観的トランザクションの方が適している可能性があります。悲観的トランザクションは、その性質上、よりコストがかかります。各書き込み操作ではロックの取得に RPC が関係する可能性があります。
9.1.4. スキューの書き込み
書き込みスキューは、2 つのトランザクションが独立して同時に同じキーの読み取りと書き込みを行うときに発生します。書き込みスキューの結果、両方のトランザクションは同じキーに対して更新を正常にコミットしますが、値は異なります。
Data Grid は、書き込みスキューチェックを自動的に実行し、楽観的トランザクションで REPEATABLE_READ
分離レベルのデータの一貫性を確保します。これにより、Data Grid はトランザクションの 1 つを検出し、ロールバックできます。
LOCAL
モードで動作する場合、書き込みスキューの確認は Java オブジェクト参照に依存して違いを比較します。これにより、書き込みスキューをチェックするための信頼性の高い技術が提供されます。
9.1.4.1. 悲観的トランザクションでのキーへの書き込みロックの強制
悲観的トランザクションでの書き込みスキューを回避するには、Flag.FORCE_WRITE_LOCK
で読み取り時にキーをロックします。
-
トランザクション以外のキャッシュでは、
Flag.FORCE_WRITE_LOCK
は動作しません。get()
呼び出しは、キーの値を読み取りますが、ロックをリモートで取得しません。 -
Flag.FORCE_WRITE_LOCK
は、同じトランザクションでエンティティーが後で更新されるトランザクションと併用する必要があります。
Flag.FORCE_WRITE_LOCK
の例は、以下のコードスニペットを比較してください。
// begin the transaction if (!cache.getAdvancedCache().lock(key)) { // abort the transaction because the key was not locked } else { cache.get(key); cache.put(key, value); // commit the transaction }
// begin the transaction try { // throws an exception if the key is not locked. cache.getAdvancedCache().withFlags(Flag.FORCE_WRITE_LOCK).get(key); cache.put(key, value); } catch (CacheException e) { // mark the transaction rollback-only } // commit or rollback the transaction
9.1.5. 例外への対処
CacheException (またはそのサブクラス) が JTA トランザクションの範囲内のキャッシュメソッドによってスローされる場合、トランザクションは自動的にロールバック対象としてマークされます。
9.1.6. 同期の登録
デフォルトでは、Data Grid は XAResource を介して、分散トランザクションの最初のクラス参加者として登録します。トランザクションの参加者として Data Grid が必要ではなく、ライフサイクル (準備、完了) によってのみ通知される状況があります (例: Data Grid が Hibernate で 2 次レベルキャッシュとして使用される場合など)。
Data Grid は、同期 を介したトランザクションのエンリストを許可します。これを有効にするには、NON_XA
トランザクションモードを使用します。
Synchronization
には、TransactionManager
が 1PC で 2PC を最適化できるという利点があります。この場合、他の 1 つのリソースのみがそのトランザクションにエンリストされます (last resource commit optimization)。たとえば、Hibernate 2 次キャッシュ: Data Grid がコミット時よりも XAResource
として TransactionManager
に登録する場合、TransactionManager
は 2 つの XAResource
(キャッシュとデータベース) を認識し、この最適化を行いません。2 つのリソース間で調整する必要があるため、tx ログをディスクに書き込む必要があります。一方、Data Grid を Synchronization
として登録すると、TransactionManager
はディスクへのログの書き込みを省略します (パフォーマンスが向上)。
9.1.7. バッチ処理
バッチ処理は、トランザクションの原子性といくつかの特性を許可しますが、本格的な JTA または XA 機能は許可しません。多くの場合、バッチ処理は本格的なトランザクションよりもはるかに軽量で安価です。
一般的には、トランザクションの参加者のみが Data Grid クラスターである場合に、バッチ処理 API を使用する必要があります。反対に、トランザクションに複数のシステムが必要な場合に、(TransactionManager
に関連する)JTA トランザクションを使用する必要があります。たとえば、トランザクションの "Hello world!" を考慮すると、ある銀行口座から別の銀行口座にお金を転送します。両方の口座が Data Grid 内に保存されている場合は、バッチ処理を使用できます。ある口座がデータベースにあり、もう 1 つの口座が Data Grid の場合は、分散トランザクションが必要になります。
バッチ処理を使用するためにトランザクションマネージャーを定義する必要はありません。
9.1.7.1. API
バッチ処理を使用するようにキャッシュを設定したら、Cache
で startBatch()
と endBatch()
を呼び出して使用します。例:
Cache cache = cacheManager.getCache(); // not using a batch cache.put("key", "value"); // will replicate immediately // using a batch cache.startBatch(); cache.put("k1", "value"); cache.put("k2", "value"); cache.put("k2", "value"); cache.endBatch(true); // This will now replicate the modifications since the batch was started. // a new batch cache.startBatch(); cache.put("k1", "value"); cache.put("k2", "value"); cache.put("k3", "value"); cache.endBatch(false); // This will "discard" changes made in the batch
9.1.7.2. バッチ処理と JTA
裏では、バッチ機能が JTA トランザクションを開始し、そのスコープ内のすべての呼び出しがそれに関連付けられます。これには、内部 TransactionManager
実装が非常に簡単な (例: リカバリーなし) を使用します。バッチ処理では、以下を取得します。
- 呼び出し中に取得したロックはバッチが完了するまで保持されます。
- 変更はすべて、バッチ完了プロセスの一部として、クラスター内でバッチ内に複製されます。バッチの各更新のレプリケーションチャット数を減らします。
- 同期のレプリケーションまたは無効化が使用された場合は、レプリケーション/無効化の失敗により、バッチがロールバックされます。
- すべてのトランザクション関連の設定は、バッチ処理にも適用されます。
9.1.8. トランザクションリカバリー
リカバリーは XA トランザクションの機能であり、リソースの不測の事態、場合によってはトランザクションマネージャーの障害を対処し、それに応じてそのような状況から回復します。
9.1.8.1. リカバリーを使用するタイミング
外部データベースに保存されたアカウントから Data Grid に保管されたアカウントに転送される分散トランザクションについて考えてみましょう。TransactionManager.commit()
が呼び出されると、両方のリソースが正常に完了します (第 1 フェーズ)。コミット (第 2) フェーズでは、データベースは、トランザクションマネージャーからコミットリクエストを受け取る前に、Data Grid の変更を問題なく適用します。この時点では、システムが一貫性のない状態です。お金は外部データベースのア口座から取得されますが、まだ Data Grid には表示されません (ロックは 2 フェーズコミットプロトコルの 2 番目のフェーズでのみリリースされます)。リカバリーはこの状況に対応することで、データベースと Data Grid の両方のデータが一貫した状態で終了します。
9.1.8.2. 仕組み
リカバリーはトランザクションマネージャーによって調整されます。トランザクションマネージャーは Data Grid と連携して、手動による介入が必要な未確定のトランザクションのリストを決定し、システム管理者に (電子メール、ログアラートなどを介して) 通知します。このプロセスはトランザクションマネージャーに固有のものですが、通常トランザクションマネージャーで設定が必要になります。
未確定のトランザクション ID を把握すると、システム管理者は Data Grid クラスターに接続し、トランザクションのコミットを再生したり、ロールバックを強制できるようになりました。Data Grid は、この JMX ツールを提供します。これは、トランザクションのリカバリーおよび調整セクション で広範囲に説明されています。
9.1.8.3. リカバリーの設定
Data Grid では、リカバリーはデフォルトでは有効になっていません。無効にすると、TransactionManager
は Data Grid と動作しないため、インダウト状態のトランザクションを決定できません。トランザクションの設定 セクションでは、その設定を有効にする方法を示しています。
注記: recovery-cache
属性は必須ではなく、キャッシュごとに設定されます。
リカバリーが機能するには、完全な XA トランザクションが必要であるため、mode
を FULL_XA
に設定する必要があります。
9.1.8.3.1. JMX サポートの有効化
リカバリー JMX サポートの管理に JMX を使用できるようにするには、明示的に有効にする必要があります。
9.1.8.4. リカバリーキャッシュ
未確定のトランザクションを追跡し、それらに応答できるようにするために、Data Grid は将来の使用のためにすべてのトランザクション状態をキャッシュします。この状態は、未確定のトランザクションに対してのみ保持され、コミット/ロールバックフェーズが完了した後、正常に完了したトランザクションに対しては削除されます。
この未確定のトランザクションデータはローカルキャッシュ内に保持されます。これにより、データが大きくなりすぎた場合に、キャッシュローダーを介してこの情報をディスクにスワップするように設定できます。このキャッシュは、recovery-cache
設定属性を介して指定できます。指定のない場合は、Data Grid がローカルキャッシュを設定します。
リカバリーが有効になっているすべての Data Grid キャッシュ間で同じリカバリーキャッシュを共有することは可能です (必須ではありません)。デフォルトのリカバリーキャッシュがオーバーライドされた場合、指定のリカバリーキャッシュは、キャッシュ自体が使用するものとは異なるトランザクションマネージャーを返す TransactionManagerLookup を使用する必要があります。
9.1.8.5. トランザクションマネージャーとの統合
これはトランザクションマネージャーに固有のものですが、通常トランザクションマネージャーは XAResource.recover()
を呼び出すために XAResource
実装への参照が必要になります。Data Grid XAResource
の以下の API への参照を取得するには、以下を行います。
XAResource xar = cache.getAdvancedCache().getXAResource();
トランザクションを実行するプロセスとは異なるプロセスで復元を実行することが一般的です。
9.1.8.6. 調整
トランザクションマネージャーは、システム管理者に未確定のトランザクションについて独自の方法で通知します。この段階では、システム管理者がトランザクションの XID(バイトアレイ) を把握していることを前提としています。
通常のリカバリーフローは以下のとおりです。
- ステップ 1: システム管理者は、JMX を介して Data Grid サーバーに接続し、未確定のトランザクションをリスト表示します。以下のイメージは、未確定のトランザクションを持つ Data Grid ノードに接続する JConsole を示しています。
図9.1 未確定のトランザクションの表示
未確定の各トランザクションのステータスが表示されます (この例では "PREPARED" です)。status フィールドに複数の要素が存在する可能性があります。たとえば、トランザクションが特定ノードでコミットされていても、それらのノードでコミットされない場合は "PREPARED" および "COMMITTED" です。
- ステップ 2: システム管理者は、トランザクションマネージャーから受け取った XID を数字で表した Data Grid 内部 ID に視覚的にマッピングします。XID(バイトアレイ) は、JMX ツール (JConsole など) に渡して Data Grid 側で再アセンブルされるため、このステップが必要です。
- ステップ 3: システム管理者は、内部 ID に基づいて、対応する jmx 操作を介してトランザクションのコミット/ロールバックを強制的に実行します。以下のイメージは、内部 ID に基づいてトランザクションのコミットを強制することで取得します。
図9.2 コミットの強制
上記のすべての JMX 操作は、トランザクションの発信場所に関係なく、任意のノードで実行できます。
9.1.8.6.1. XID に基づくコミット/ロールバックの強制
疑わしいトランザクションのコミット/ロールバックを強制するための XID ベースの JMX 操作も利用できます。これらのメソッドは、トランザクションに関連付けられた番号 (前述の手順 2 で説明したように) ではなく、XID を記述する byte[] 配列を受け取ります。これらは、たとえば、特定の未確定トランザクションの自動完了ジョブを設定する場合に役立ちます。このプロセスはトランザクションマネージャーのリカバリーにプラグインされ、トランザクションマネージャーの XID オブジェクトにアクセスできます。