Coherenceでは、データベース、Webサービス、パッケージ化されたアプリケーション、ファイル・システムなど、任意のデータソースの透過的な読取りまたは書込みのキャッシングがサポートされていますが、データベースが最も一般的に使用されています。ここでは、任意のバックエンド・データソースのことを、簡単にデータベースと呼びます。効率的なキャッシュでは、集中的な読取り専用操作および読取り/書込み操作をサポートし、読取り/書込み操作の際には、キャッシュとデータベースは完全に同期化されている必要があります。これを実現するために、Coherenceではリードスルー、ライトスルー、リフレッシュアヘッドおよびライトビハインド・キャッシュをサポートします。
注意: パーティション(分散)およびニア・キャッシュ・トポロジでの使用: リードスルー/ライトスルー・キャッシング(および他の類似するキャッシング形態)は、パーティション(分散)キャッシュ(ニア・キャッシュも含む)トポロジで使用することのみを目的としています。ローカル・キャッシュでは、この機能のサブセットがサポートされます。レプリケーション・キャッシュとオプティミスティック・キャッシュは使用しないでください。 |
CacheStore
は、キャッシュと基礎となるデータソースの接続に使用されるアプリケーション固有のアダプタです。CacheStore
実装では、データ・アクセス・メカニズムを使用してデータソースにアクセスします(たとえば、Hibernate、Toplink Essentials、JPA、アプリケーション固有のJDBCコール、他のアプリケーション、メインフレーム、他のキャッシュなど)。CacheStore
では、データソースからの取得データを使用したJavaオブジェクトの作成方法、データソースへのオブジェクトのマッピング方法や書込み方法、およびデータソースからのオブジェクトの削除方法が認識されます。
データソース接続方針およびデータソース-アプリケーション・オブジェクトのマッピング情報は、データソース・スキーマ、アプリケーション・クラス・レイアウト、および操作環境に固有です。そのため、このマッピング情報は、アプリケーション開発者がCacheStore
実装の形式で用意する必要があります。詳細は、「CacheStore実装の作成」を参照してください。
アプリケーションが、たとえばkey
X
のエントリをキャッシュに要求したときに、キャッシュにX
がなかった場合、CoherenceではCacheStoreへの委任が自動的に実行され、基礎となるデータソースからのX
のロードが要求されます。データソースにX
が存在している場合、CacheStore
はそれをロードしてCoherenceに返し、Coherenceは将来の使用に備えてそれをキャッシュに置き、最終的にX
を要求したアプリケーションに返します。これをリードスルー・キャッシングと呼びます。リフレッシュアヘッド・キャッシュ機能を使用すると、(認識されている待機時間を削減することにより)読取りパフォーマンスがさらに向上します。詳細は、「リフレッシュアヘッド・キャッシング」を参照してください。
Coherenceでは、2つの異なる方法でデータソースの更新を処理できます。1つ目の方法は、ライトスルーです。ライトスルーでは、アプリケーションでキャッシュのデータが更新されると(つまり、put
(...)をコールしてキャッシュ・エントリを変更すると)、CacheStore
を介して基礎となるデータソースにデータが正常に保存されるまで操作は完了しません(つまり、put
は返されません)。このままでは、データソースへの書込みにおいて待機時間が生じるため、書込みパフォーマンスはまったく向上しません。書込みパフォーマンスの向上は、ライトビハインド・キャッシュ機能が実現します。詳細は、「ライトビハインド・キャッシュ」を参照してください。
ライトビハインドのシナリオでは、構成済の遅延時間(10秒、20分、1日、1週間以上など)が経過と、変更されたキャッシュ・エントリは非同期にデータソースに書き込まれます。ライトビハインド・キャッシュでは、データソースで更新する必要のあるデータのライトビハインド・キューが保持されます。アプリケーションでキャッシュのX
が更新されると、X
はライトビハインド・キューに追加され(X
がない場合。ある場合は置き換えられる)、指定されたライトビハインド遅延の経過後、CoherenceはCacheStoreをコールして基礎となるデータソースのX
を最新の状態に更新します。ライトビハインド遅延は、一連の変更が始まる時点を基準としています。つまり、データソースのデータは、キャッシュのライトビハインド遅延を超えて遅れることはありません。
そのため、一度読み取って構成済の間隔で書き込む(つまり、更新回数がより少ない)シナリオになります。このアーキテクチャ・タイプには、主に4つの利点があります。
基礎となるデータソースへのデータ書込みを待つ必要がないため、アプリケーションのパフォーマンスが向上します(データは後で、別の実行スレッドによって書き込まれる)。
アプリケーションにおけるデータベース負荷が大幅に削減されます。読取り操作と書込み操作の量が両方とも減るため、データベース負荷も削減されます。他のキャッシング・アプローチと同様、キャッシングによって読取りが削減されます。一般的に、書込みはより負荷の高い操作ですが、多くの場合、その回数が減ります。ライトビハインド間隔の間における同一オブジェクトへの複数の変更操作が結合され、基礎となるデータソースに一度だけ書き込まれるからです(書込み結合)。また、複数のキャッシュ・エントリへの書込みは、CacheStore.storeAll()
メソッドを使用して単一のデータベース・トランザクションにまとめることができます(書込み組合せ)。
アプリケーションは、データベース障害からある程度保護されます。書込みの失敗時にオブジェクトが書込みキューに再度配置されるように、ライトビハインド機能を構成できるからです。アプリケーションの使用データがCoherenceキャッシュにある場合、データベースを稼働せずに操作を継続できます。これは、Coherenceパーティション・キャッシュを使用すると、容易に実現できます。Coherenceパーティション・キャッシュでは、(ローカル記憶域が有効な)すべての参加クラスタ・ノードでキャッシュ全体をパーティション化できるため、巨大なキャッシュ・メモリーを使用できます。
線形スケーラビリティ: アプリケーションでより多くの同時ユーザー数を処理するには、クラスタ内のノード数を増加するだけで済みます。データベース負荷への影響は、ライトビハインド間隔を長くすることで調整できます。
ライトビハインド・キャッシュは、1つの構成設定を調整するだけで有効化できますが、ライトビハインドを期待どおりに動作させるのはより困難です。特に、アプリケーション設計では、いくつかの設計上の問題に前もって対処しておく必要があります。
データベースの更新がキャッシュ・トランザクションの外側で発生するため、そこにライトビハインド・キャッシュが最も作用します。つまり、キャッシュ・トランザクションはほとんどの場合、データベース・トランザクションが開始される前に完了します。これは、データベース・トランザクションが失敗してはならないことを意味します。これが保証できない場合、ロールバック機能を採用する必要があります。
ライトビハインドはデータベース更新の順序を並べ替えることができるため、参照整合性制約で順不同の更新をできるようにする必要があります。概念的には、これはデータベースをISAMスタイル(更新競合がないことが保証された主キー・ベースのアクセス)の記憶域として使用することを意味します。他のアプリケーションとデータベースを共有すると、新たな困難に直面します。つまり、ライトビハインド・トランザクションが外部の更新と競合しないことを保証する手段がなくなります。これは、ライトビハインドの競合がヒューリスティックに処理されるか、オペレータが手動で調整する必要があることを意味します。
通常は、論理データベース・トランザクションに各キャッシュ・エントリの更新をマッピングすると、最も単純なデータベース・トランザクションが保証されるため理想的です。
ライトビハインドは、(ライトビハインド・キューがディスクに書き込まれるまで)キャッシュを効率的に記録システムで保持するため、ビジネス規則ではクラスタに永続可能な(ディスクへの永続可能ではなく)データおよびトランザクションの記憶域を許可する必要があります。
Coherenceの従来のリリースでは、(フェイルオーバー/フェイルバックによって)再バランシングすると、影響を受けたキャッシュ・パーティションのすべてのキャッシュ・エントリがキューに再配置されていました(通常、1/N。Nはクラスタ内のサーバー数)。ライトビハインドの性質上(非同期のキューイングおよび負荷の平均化)、直接的な影響は最小化されますが、ある程度のワークロードでは問題が発生する場合があります。影響を受けるアプリケーションのベスト・プラクティスは、com.tangosol.net.cache.VersionedBackingMap
を使用することでした。Coherence 3.2以降は、変更されたエントリがデータソースに正常に書き込まれるとバックアップが通知されるため、この方針は不要です。VersionedBackingMap
が書込みキューイングの動作にのみ使用されていた場合、可能であればアプリケーションでそれを使用しないことをお薦めします。
リフレッシュアヘッドのシナリオでは、キャッシュ・ローダーから最近アクセスされた任意のキャッシュ・エントリを、失効する前に自動的かつ非同期的にリロード(リフレッシュ)するようにキャッシュを構成できます。その結果、アクセス頻度の高いエントリがキャッシュに入ると、失効後にエントリがリロードされるときに、アプリケーションは潜在的に低速なキャッシュ・ストアに対する読取りの影響を受けません。非同期リフレッシュは、有効期限が近いオブジェクトがアクセスされたときにのみトリガーされます。失効後にオブジェクトがアクセスされた場合、キャッシュ・ストアから同期の読取りが行われ、その値がリフレッシュされます。
リフレッシュアヘッド期限は、エントリの有効期間の割合として表されます。たとえば、キャッシュ・エントリの有効期間が60秒に設定されており、リフレッシュアヘッド係数が0.5に設定されているとします。キャッシュされたオブジェクトを60秒後にアクセスすると、キャッシュ・ストアから同期読取りが実行され、その値がリフレッシュされます。ただし、30秒を超える60秒未満のエントリに対するリクエストを実行すると、キャッシュの現在の値が返され、キャッシュ・ストアから非同期のリロードがスケジュールされます。
リフレッシュアヘッドは、多数のユーザーがオブジェクトにアクセスする場合に有用です。値はキャッシュで最新の状態に維持され、キャッシュ・ストアの大量のリロードに起因する待機時間が回避されます。
リフレッシュアヘッド係数の値は、coherence-cache-config.xmlファイルの<read-write-backing-map-scheme
>要素の<refresh-ahead-factor>
サブ要素で指定します。リフレッシュアヘッドでは、キャッシュのエントリの有効期間(<expiry-delay>
)も設定されていることを前提としています。
例12-1のXMLコード部分は、ローカル・キャッシュのエントリのリフレッシュアヘッド係数を0.5、有効期間を20秒で構成しています。つまり、エントリが有効期間の10秒以内にアクセスされると、キャッシュ・ストアから非同期にリロードされるようにスケジュールされます。
例12-1 リフレッシュアヘッド係数を指定するキャッシュ構成
<cache-config> ... <distributed-scheme> <scheme-name>categories-cache-all-scheme</scheme-name> <service-name>DistributedCache</service-name> <backing-map-scheme> <!-- Read-write-backing-map caching scheme. --> <read-write-backing-map-scheme> <scheme-name>categoriesLoaderScheme</scheme-name> <internal-cache-scheme> <local-scheme> <scheme-ref>categories-eviction</scheme-ref> </local-scheme> </internal-cache-scheme> <cachestore-scheme> <class-scheme> <class-name>com.demo.cache.coherence.categories.CategoryCacheLoader</class-name> </class-scheme> </cachestore-scheme> <refresh-ahead-factor>0.5</refresh-ahead-factor> </read-write-backing-map-scheme> </backing-map-scheme> <autostart>true</autostart> </distributed-scheme> ... <!-- Backing map scheme definition used by all the caches that require size limitation and/or expiry eviction policies. --> <local-scheme> <scheme-name>categories-eviction</scheme-name> <expiry-delay>20s</expiry-delay> </local-scheme> ... </cache-config>
この項では、いくつかのキャッシング方針の利点を比較します。
クラスタ環境のキャッシュアサイド・パターンには、一般的に2つのアプローチがあります。1つ目のアプローチでは、キャッシュ・ミスをチェックし、データベースに問い合せ、キャッシュに移入して、アプリケーション処理を継続します。この場合、様々なアプリケーション・スレッドがこの処理を同時に実行すると、多数のデータベース・アクセスが発生することになります。もう1つのアプローチでは、アプリケーションでロックを再確認します(これは、チェックがキャッシュ・エントリに対する原子性を持っているため機能する)。ただし、キャッシュ・ミスまたはデータベース更新(クラスタ化されたロック、追加の読取り、およびクラスタ化されたロック解除 - 最大10の追加ネットワーク・ホップ、または一般的なギガビット・イーサネット接続で6〜8ミリ秒、さらに追加処理のオーバーヘッドおよびキャッシュ・エントリのロック継続期間の増加)の際に大量のオーバーヘッドが生じます。
インライン・キャッシングを使用すると、(フォルト・トレランスを確保するためにデータがバックアップ・サーバーにコピーされている間)エントリは2つのネットワーク・ホップでのみロックされます。また、ロックはパーティション所有者によりローカルで維持されます。さらに、アプリケーション・コードは完全にキャッシュ・サーバーで管理されます。つまり、ノードの管理下にあるサブセットのみが直接データベースにアクセスします(そのため、負荷とセキュリティが予測可能になる)。加えて、これによりデータベース・ロジックからキャッシュ・クライアントが切り離されます。
リフレッシュアヘッドでは、リードスルーと比べて待機時間が減りますが、それは将来必要とされるキャッシュ・アイテムをキャッシュが正確に予測できる場合のみです。これらの予測が完全に正確であれば、リフレッシュアヘッドにより待機時間が削減され、余分なオーバーヘッドはありません。予測ミスの比率が高いと、データベースに送信される不要なリクエストが多くなるため、スループットへの影響も大きくなります。データベースのリクエスト処理が遅れ始めると、待機時間が増加する可能性もあります。
ライトビハインド・キャッシュの要件が満たされると、ライトスルー・キャッシュと比較して非常に高いスループットが得られ、待機時間が削減されます。また、ライトビハインド・キャッシュによりデータベース(書込みの減少)およびキャッシュ・サーバー(キャッシュ値のデシリアライズの削減)の負荷が低減されます。
すべてのCacheStore
操作は、冪等になるように(つまり、不要な副作用がなくても繰り返し処理できるように)設計する必要があります。ライトスルーおよびライトビハインド・キャッシュの場合、フェイルオーバー処理時にキャッシュ更新のデータベース部分を再試行することにより、低コストのフォルト・トレランスを提供できます。ライトビハインド・キャッシュの場合、冪等性によって、データ整合性に影響を与えずに、複数のキャッシュ更新を単一のCacheStore
の起動にまとめることができます。
アプリケーションにライトビハインド・キャッシュの要件があっても、書込み組合せを回避する必要がある場合(たとえば、監査のため)、バージョニングされたキャッシュ・キーを作成する必要があります(たとえば、シーケンスIDを持つ通常の主キーを組み合せて)。
Coherenceでは、複数のCacheStore
インスタンス間での2フェーズのCacheStore
操作はサポートされません。つまり、2つのキャッシュ・エントリが更新され、別々のキャッシュ・サーバーにあるCacheStore
モジュールへのコールがトリガーされる場合、片方のデータベース更新は成功し、もう一方は失敗する可能性があります。この場合、アプリケーション・サーバー・トランザクション・マネージャで(キャッシュとデータベースを単一のトランザクションの中にある2つの別のコンポーネントとして更新する)、キャッシュアサイド・アーキテクチャを使用することをお薦めします。多くの場合、論理コミットの失敗がないように(当然、サーバー障害が発生しないようにもする)データベース・スキーマを設計することは可能です。ライトビハインド・キャッシュでは、データベースの動作によりputが影響を受けないため、この問題は回避されます(根本的な問題は設計プロセスの初期段階で解決しておく必要がある)。この制限は、将来のCoherenceのリリースで解決される予定です。
キャッシュ問合せは、キャッシュに格納されているデータのみを操作し、欠落データまたは欠落する可能性のあるデータをロードするためにはCacheStore
をトリガーしません。そのため、CacheStore
でバックアップされたキャッシュに問い合せるアプリケーションは、その問合せに必要なすべてのデータがあらかじめロードされていることを確認する必要があります。効率を向上させるために、ほとんどのバルク・ロード操作は、アプリケーションの起動時にデータセットをデータベースからキャッシュに直接送信して完了しておきます(NamedCache.putAll()
を使用して、データのブロックをキャッシュにまとめる)。ローダー・プロセスは、制御可能なCachestoreパターンを使用して、データベースにバックアップされた循環更新を無効にする必要があります。CacheStore
は、起動サービス(クラスタ全体にエージェントを送信して、各JVMのローカル・フラグを変更する)を使用するか、レプリケーション・キャッシュ(別のキャッシュ・サービス)に値を設定して、CacheStore
メソッドの起動ごとにそれを読み取ることによって管理できます(一般的なデータベース操作と比較すると、オーバーヘッドが最低限に保たれる)。Coherenceのクラスタ化されたJMX機能により、カスタムMBeanも簡単に実装できます。
CacheStore
実装はプラッガブルで、データソースのキャッシュの使用状況に応じて、次の2つのインタフェースのいずれかを実装する必要があります。
CacheLoader
: 読取り専用キャッシュ
CacheStore
: CacheLoader
を拡張して、読取り/書込みキャッシュをサポートします。
これらのインタフェースは、com.tangosol.net.cache
パッケージにあります。CacheLoader
インタフェースには、主に2つのメソッドがあります。load(Object key)
とloadAll(Collection keys)
です。CacheStore
インタフェースには、store(Object key, Object value)
、storeAll(Map mapEntries)
、erase(Object key)
およびeraseAll(Collection colKeys)
メソッドが追加されました。
サンプルの実装は、『Oracle Coherence開発者ガイド』の「CacheStoreのサンプル」を参照してください。
CacheStore
モジュールをプラグインするには、distributed-scheme
、backing-map-scheme
、cachestore-scheme
、またはread-write-backing-map-scheme
キャッシュ構成要素の中のCacheStore
実装クラス名を指定します。
read-write-backing-map-scheme
では、com.tangosol.net.cache.ReadWriteBackingMap
が構成されます。このバッキング・マップは2つの主要な要素で構成されます。実際にデータをキャッシュする内部マップ(internal-cache-scheme
を参照)とデータベースと相互作用するCacheStore
モジュール(cachestore-scheme
を参照)です。
例12-2は、CacheStore
モジュールを指定するキャッシュ構成を示しています。<init-params>
要素には、CacheStore
コンストラクタに渡されるパラメータの順序付けられたリストが含まれます。{cache-name}
構成マクロを使用してキャッシュ名をCacheStore
実装に渡すことにより、これがデータベース表にマップされます。使用可能なマクロの一覧は、「キャッシュ構成のパラメータ・マクロ」を参照してください。
ライトビハインドおよびリフレッシュアヘッドの構成方法の詳細は、read-write-backing-map-scheme
を参照してください。その際には、write-batch-factor
、refresh-ahead-factor
、write-requeue-threshold
およびrollback-cachestore-failures
要素に注意してください。
例12-2 Cachestoreモジュールによるキャッシュ構成
<?xml version="1.0"?> <!DOCTYPE cache-config SYSTEM "cache-config.dtd"> <cache-config> <caching-scheme-mapping> <cache-mapping> <cache-name>com.company.dto.*</cache-name> <scheme-name>distributed-rwbm</scheme-name> </cache-mapping> </caching-scheme-mapping> <caching-schemes> <distributed-scheme> <scheme-name>distributed-rwbm</scheme-name> <backing-map-scheme> <read-write-backing-map-scheme> <internal-cache-scheme> <local-scheme/> </internal-cache-scheme> <cachestore-scheme> <class-scheme> <class-name>com.company.MyCacheStore</class-name> <init-params> <init-param> <param-type>java.lang.String</param-type> <param-value>{cache-name}</param-value> </init-param> </init-params> </class-scheme> </cachestore-scheme> </read-write-backing-map-scheme> </backing-map-scheme> </distributed-scheme> </caching-schemes> </cache-config>
注意: スレッド・カウント:CacheStore モジュールを使用すると、キャッシュ・サービス・スレッドの消費が著しく増加します(最速のデータベースを選択しても、インメモリー構造より何桁倍も処理が遅くなる)。そのため、キャッシュ・サービスのスレッド・カウントを増加する必要があります(通常は、10〜100の範囲)。スレッド・プールが不十分な場合の最も顕著な症状は、(背後にあるデータベースの処理が増加せずに)キャッシュ・リクエストの待機時間が増加することです。 |
CacheStore
を実装する場合は、次のことに注意してください。
CacheStore
実装では、ホスティングしているキャッシュ・サービスをコールバックしないでください。これには、内部的にCoherenceキャッシュ・サービスを参照するOR/Mソリューションも含まれます。別のキャッシュ・サービス・インスタンスをコールすることも可能ですが、ネストされるコールが深くなりすぎないように注意してください。各コールでは、キャッシュ・サービス・スレッドが消費されるため、キャッシュ・サービス・スレッドプールを使い果たすと、デッドロックに陥る可能性があります。
キャッシュ・エントリのクラス(値オブジェクト、データ転送オブジェクトとも呼ばれる)は、キャッシュ・サーバー・クラスパスに置く必要があります(キャッシュ・サーバーは、キャッシュ・エントリをシリアライズ/デシリアライズしてCacheStore
モジュールと相互作用する必要があるため)。
CacheStore.storeAll
メソッドは、キャッシュがライトビハインドとして構成され、<write-batch-factor>
が構成されている場合に、最もよく使用されます。CacheLoader.loadAll
メソッドも、Coherenceで使用されています。同様の理由で、それを最初に使用する際にはリフレッシュアヘッドを有効にする必要があると考えられます。