この章の内容は次のとおりです。
distributed-scheme
、backing-map-scheme
、cachestore-scheme
またはread-write-backing-map-scheme
を指定します。com.tangosol.net.cache.CacheStore
インタフェースのきわめて基本的なサンプル実装を確認します。親トピック: キャッシュの使用
ノート:
リードスルー/ライトスルー・キャッシング(および他の類似するキャッシング形態)は、パーティション(分散)キャッシュ(ニア・キャッシュも含む)トポロジで使用することのみを目的としています。ローカル・キャッシュでは、この機能のサブセットがサポートされます。レプリケート・キャッシュとオプティミスティック・キャッシュは使用しないでください。
この項には次のトピックが含まれます:
親トピック: データ・ソースのキャッシュ
キャッシュ・ストアは、キャッシュと基礎となるデータ・ソースの接続に使用されるアプリケーション固有のアダプタです。キャッシュ・ストア実装では、データ・アクセス・メカニズム(たとえば、Toplink/EclipseLink、JPA、Hibernate、アプリケーション固有のJDBCコール、他のアプリケーション、メインフレーム、他のキャッシュなど)を使用してデータ・ソースにアクセスします。キャッシュ・ストアでは、データ・ソースからの取得データを使用したJavaオブジェクトの作成方法、データ・ソースへのオブジェクトのマッピング方法や書込み方法、およびデータ・ソースからのオブジェクトの削除方法が認識されます。
データ・ソース接続戦略およびデータ・ソース-アプリケーション・オブジェクトのマッピング情報は、データ・ソース・スキーマ、アプリケーション・クラス・レイアウト、および操作環境に固有です。そのため、このマッピング情報は、アプリケーション開発者がキャッシュ・ストアの実装で用意する必要があります。キャッシュ・ストアの実装の作成を参照してください。
事前定義済のキャッシュ・ストア実装
Coherenceには、JPAキャッシュ・ストアの実装がいくつか含まれています。
Generic JPA - 任意のJPAプロバイダで使用できるキャッシュ・ストア実装。JPAとCoherenceの併用を参照してください。
EclipseLink - EclipseLink JPAプロバイダ用に最適化されたキャッシュ・ストア実装。JPAとCoherenceの併用を参照してください。
Hibernate - Hibernate JPAプロバイダ用のキャッシュ・ストア実装。HibernateとCoherenceの統合を参照してください。
親トピック: データ・ソースのキャッシュの概要
アプリケーションから、たとえばkey
X
のキャッシュ・エントリが要求されたときに、キャッシュにX
がなかった場合、CoherenceはCacheStoreへの委任を自動的に実行し、基礎となるデータソースからX
をロードするようにCacheStoreに要求します。データ・ソースにX
が存在している場合、CacheStore
はそれをロードしてCoherenceに返し、Coherenceは将来の使用に備えてそれをキャッシュに置き、最終的にX
を要求したアプリケーションに返します。これをリードスルー・キャッシングと呼びます。リフレッシュアヘッド・キャッシュ機能を使用すると、(認識されている待機時間を削減することにより)読取りパフォーマンスがさらに向上します。リフレッシュアヘッド・キャッシングを参照してください。
親トピック: データ・ソースのキャッシュの概要
Coherenceでは、2つの異なる方法でデータ・ソースの更新を処理できます。1つ目の方法は、ライトスルーです。この場合、アプリケーションでキャッシュのデータが更新されると(つまり、put
(...)をコールしてキャッシュ・エントリを変更すると)、キャッシュ・ストアを介して基礎となるデータ・ソースにデータが正常に保存されるまで操作は完了しません(つまり、put
は返されません)。このままでは、データ・ソースへの書込みにおいて待機時間が生じるため、書込みパフォーマンスはまったく向上しません。書き込みパフォーマンスを向上させるには、ライトビハインド・キャッシュ機能を使用します。ライトビハインド・キャッシュを参照してください。
親トピック: データ・ソースのキャッシュの概要
ライトビハインドのシナリオでは、構成された遅延(10秒、20分、1日、1週間、またはそれ以上)の経過後に、変更されたキャッシュ・エントリが非同期でデータ・ソースに書き込まれます。これは、キャッシュの挿入と更新にのみ適用されることに注意する必要があります。キャッシュ・エントリの削除は、データ・ソースと同期して実行されます。ライトビハインド・キャッシングでは、データ・ソースで更新する必要のあるデータのライトビハインド・キューが保持されます。アプリケーションでキャッシュのX
が更新されると、X
はライトビハインド・キューに追加され(Xがない場合。ある場合は置き換えられる)、指定されたライトビハインド遅延の経過後、CoherenceはCacheStoreをコールして基礎となるデータ・ソースのX
を最新の状態に更新します。ライトビハインド遅延は、一連の変更が始まる時点を基準としています。つまり、データ・ソースのデータは、キャッシュのライトビハインド遅延を超えて遅れることはありません。
このため、一度読み取って構成された間隔で書き込む(つまり、更新頻度が少ない)シナリオになります。このアーキテクチャ・タイプには、主に4つの利点があります。
基礎となるデータ・ソースへのデータ書込みを待つ必要がないため、アプリケーションのパフォーマンスが向上します。(データは後で、別の実行スレッドによって書き込まれます。)
アプリケーションにおけるデータベース負荷が大幅に削減されます。読取り操作と書込み操作の量が両方とも減るため、データベース負荷も削減されます。他のキャッシング・アプローチと同様、キャッシングによって読取りが削減されます。一般的に、書込みはより負荷の高い操作ですが、多くの場合、その回数が減ります。ライトビハインド間隔の間における同一オブジェクトへの複数の変更操作が結合され、基礎となるデータ・ソースに一度だけ書き込まれるからです(書込み結合)。また、複数のキャッシュ・エントリへの書込みは、CacheStore.storeAll()
メソッドを使用して単一のデータベース・トランザクションにまとめることができます(書込み組合せ)。
アプリケーションは、データベース障害からある程度保護されます。書込みの失敗時にオブジェクトが書込みキューに再度配置されるように、ライトビハインド機能を構成できるからです。アプリケーションの使用データがCoherenceキャッシュにある場合、データベースを稼働せずに操作を継続できます。これは、Coherenceパーティション・キャッシュを使用すると、容易に実現できます。Coherenceパーティション・キャッシュでは、(ローカル記憶域が有効な)すべての参加クラスタ・ノードでキャッシュ全体をパーティション化できるため、巨大なキャッシュ・メモリーを使用できます。
線形スケーラビリティ: アプリケーションでより多くの同時ユーザー数を処理するには、クラスタ内のノード数を増加するだけで済みます。データベース負荷への影響は、ライトビハインド間隔を長くすることで調整できます。
ライトビハインドの要件
ライトビハインド・キャッシングは、1つの構成設定を調整するだけで有効化できますが、ライトビハインドを期待どおりに動作させるのはより困難です。特に、アプリケーション設計では、いくつかの設計上の問題に前もって対処しておく必要があります。
データベースの更新がキャッシュ・トランザクションの外側で発生するため、そこにライトビハインド・キャッシングが最も作用します。つまり、キャッシュ・トランザクションはほとんどの場合、データベース・トランザクションが開始される前に完了します。これは、データベース・トランザクションが失敗してはならないことを意味します。これが保証できない場合、ロールバック機能を採用する必要があります。
ライトビハインドはデータベース更新の順序を並べ替えることができるため、参照整合性制約で順不同の更新をできるようにする必要があります。概念的には、これはデータベースをISAMスタイル(更新競合がないことが保証された主キー・ベースのアクセス)の記憶域として使用するようなものです。他のアプリケーションとデータベースを共有すると、新たな困難に直面します。つまり、ライトビハインド・トランザクションが外部の更新と競合しないことを保証する手段がなくなります。これは、ライトビハインドの競合がヒューリスティックに処理されるか、オペレータが手動で調整する必要があることを意味します。
通常は、論理データベース・トランザクションに各キャッシュ・エントリの更新をマッピングすると、最も単純なデータベース・トランザクションが保証されるため理想的です。
ライトビハインドは、(ライトビハインド・キューがディスクに書き込まれるまで)キャッシュを効率的に記録システムで保持するため、ビジネス規則ではクラスタに永続可能な(ディスクへの永続可能ではなく)データおよびトランザクションの記憶域を許可する必要があります。
親トピック: データ・ソースのキャッシュの概要
リフレッシュアヘッドのシナリオでは、キャッシュ・ローダーから最近アクセスされた任意のキャッシュ・エントリを、失効する前に自動的かつ非同期的にリロード(リフレッシュ)するようにキャッシュを構成できます。その結果、アクセス頻度の高いエントリがキャッシュに入ると、失効後にエントリがリロードされるときに、アプリケーションは潜在的に低速なキャッシュ・ストアに対する読取りの影響を受けません。非同期リフレッシュは、有効期限が近いオブジェクトへのアクセスが発生したときにのみトリガーされます。オブジェクトへのアクセスが期限切れ後の場合、キャッシュ・ストアからの読取りが同期的に行われ、その値がリフレッシュされます。
リフレッシュアヘッド期限は、エントリの有効期間の割合として表されます。たとえば、キャッシュ・エントリの有効期間が60秒に設定されており、リフレッシュアヘッド係数が0.5に設定されているとします。キャッシュされたオブジェクトを60秒後にアクセスすると、キャッシュ・ストアから同期読取りが実行され、その値がリフレッシュされます。ただし、30秒を超える60秒未満のエントリに対するリクエストを実行すると、キャッシュの現在の値が返され、キャッシュ・ストアから非同期のリロードがスケジュールされます。ただし、これにより、レスポンスが返される前にキャッシュ全体がリフレッシュされることはありません。リフレッシュはバックグラウンドで実行されます。
リフレッシュアヘッドは、多数のユーザーがオブジェクトにアクセスする場合に有用です。値はキャッシュで最新の状態に維持され、キャッシュ・ストアの大量のリロードに起因する待機時間が回避されます。
リフレッシュアヘッド係数の値は<refresh-ahead-factor>
サブ要素により指定されます。read-write-backing-map-schemeを参照してください。リフレッシュアヘッドでは、キャッシュのエントリの有効期間(<expiry-delay>
)も設定されていることを前提としています。
例14-1では、ローカル・キャッシュのエントリのリフレッシュアヘッド係数を0.5で、有効期間を20秒で構成しています。エントリが有効期間の10秒以内にアクセスされると、キャッシュ・ストアから非同期にリロードされるようにスケジュールされます。
例14-1 リフレッシュアヘッド係数の指定
<distributed-scheme> <scheme-name>categories-cache-all-scheme</scheme-name> <service-name>DistributedCache</service-name> <backing-map-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> <local-scheme> <scheme-name>categories-eviction</scheme-name> <expiry-delay>20s</expiry-delay> </local-scheme>
親トピック: データ・ソースのキャッシュの概要
Oracle Coherence GoldenGate HotCache (HotCache)の統合により、アプリケーション外部のソースからデータベースへの変更をCoherenceキャッシュ内のオブジェクトに伝播できます。HotCacheの統合により、失効している可能性があったり、期限切れのキャッシュ・データをアプリケーションが使用しないことが保証されます。HotCacheには、失効したデータのみを処理する効率的なプッシュ・モデルが採用されています。データベースに変更が行われる際にデータがプッシュされるため、待機時間の短さが保証されます。HotCacheの統合には、CoherenceとGoldenGateの両方をインストールする必要があります。Oracle Coherence GoldenGate HotCacheとの統合を参照してください。
親トピック: データ・ソースのキャッシュの概要
この項には次のトピックが含まれます:
クラスタ化環境のキャッシュアサイド・パターンには、一般的に2つのアプローチがあります。1つ目のアプローチでは、キャッシュ・ミスをチェックし、データベースに問い合せ、キャッシュに移入して、アプリケーション処理を継続します。この場合、様々なアプリケーション・スレッドがこの処理を同時に実行すると、多数のデータベース・アクセスが発生することになります。もう1つのアプローチでは、アプリケーションでロックを再確認します(これは、チェックがキャッシュ・エントリに対する原子性を持っているため機能します)。ただし、キャッシュ・ミスまたはデータベース更新の際に大量のオーバーヘッドが生じます(クラスタ化されたロック、追加の読取り、およびクラスタ化されたロック解除 - 最大10の追加ネットワーク・ホップに加え、追加処理のオーバーヘッドおよびキャッシュ・エントリのロック継続期間の増加)。
インライン・キャッシングを使用すると、(フォルト・トレランスを確保するためにデータがバックアップ・サーバーにコピーされている間)エントリは2つのネットワーク・ホップでのみロックされます。また、ロックはパーティション所有者によりローカルで維持されます。さらに、アプリケーション・コードは完全にキャッシュ・サーバーで管理されます。つまり、ノードの管理下にあるサブセットのみが直接データベースにアクセスします(そのため、負荷とセキュリティの予測可能性が向上します)。加えて、これによりデータベース・ロジックからキャッシュ・クライアントが切り離されます。
親トピック: キャッシュ方針の選択
リフレッシュアヘッドでは、リードスルーと比べて待機時間が減りますが、それは将来必要とされるキャッシュ・アイテムをキャッシュが正確に予測できる場合のみです。これらの予測が完全に正確であれば、リフレッシュアヘッドにより待機時間が削減され、余分なオーバーヘッドはありません。予測の正確性が低下すると、データベースに送信される不要なリクエストが増加するため、スループットへの影響も大きくなります。データベースのリクエスト処理が遅れ始めると、待機時間が長くなる可能性もあります。
親トピック: キャッシュ方針の選択
ライトビハインド・キャッシングの要件が満たされると、ライトスルー・キャッシュと比較して非常に高いスループットが得られ、待機時間が削減されます。また、ライトビハインド・キャッシングによりデータベース(書込みの減少)およびキャッシュ・サーバー(キャッシュ値のデシリアライズの削減)の負荷が低減されます。
親トピック: キャッシュ方針の選択
CacheLoader
- 読取り専用キャッシュ
CacheStore
- 読取り/書込みキャッシュ
BinaryEntryStore
- バイナリ・エントリ・オブジェクトの読取り/書込み。
これらのインタフェースは、com.tangosol.net.cache
パッケージにあります。CacheLoader
インタフェースには、load(Object key)
およびloadAll(Collection keys)
という2つの主要なメソッドがあります。CacheStore
インタフェースでは、store(Object key, Object value)
、storeAll(Map mapEntries)
、erase(Object key)
およびeraseAll(Collection colKeys)
メソッドが追加されます。BinaryEntryStore
インタフェースでは他のインタフェースと同じメソッドが提供されますが、このインタフェースは直接バイナリ・オブジェクトに対して機能します。
キャッシュ・ストアの実装のサンプルおよび制御可能なキャッシュ・ストアの実装のサンプルを参照してください。
親トピック: データ・ソースのキャッシュ
distributed-scheme
、backing-map-scheme
、cachestore-scheme
またはread-write-backing-map-scheme
を指定します。
read-write-backing-map-scheme
では、ReadWriteBackingMap
の実装を構成します。このバッキング・マップは、実際にデータをキャッシュする内部マップ(internal-cache-scheme
)と、データベースと相互作用するキャッシュ・ストア実装(cachestore-scheme
)という2つの主要な要素で構成されます。read-write-backing-map-schemeを参照し、write-batch-factor
、refresh-ahead-factor
、write-requeue-threshold
およびrollback-cachestore-failures
要素に注目してください。
例14-2は、キャッシュ・ストアの実装を指定するキャッシュ構成を示しています。<init-params>
要素には、コンストラクタに渡されるパラメータの順序付けられたリストが含まれます。{cache-name}
構成マクロを使用してキャッシュ名を実装に渡すことにより、これがデータベース表にマップされます。パラメータ・マクロの使用を参照してください。
例14-2 Cachestoreモジュールの例
<?xml version="1.0"?> <cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config" xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd"> <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.example.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>
キャッシュ・ストアでのフェデレート・キャッシュの使用
デフォルトでは、キャッシュ・ストアにロードされるデータはフェデレーション参加者に統合されません。フェデレーテッド・キャッシュを有効にする場合は、ローカル参加者のすべてのリードスルー・リクエストがリモート参加者にレプリケートされ、リモート・サイトのデータ・ソースが更新されるため、注意が必要です。これは、タイミングにセンシティブなアプリケーションで特に問題になる可能性があります。さらに、読取り量の多いアプリケーションでは、リモート・サイトのデータ・ソースへの不要な書込みを引き起こす可能性があります。
リードスルー・キャッシングの使用時にキャッシュにロードされるエントリを統合するには、キャッシュ・ストアの定義で<federated-loading>
要素をtrue
に設定します。たとえば:
<cachestore-scheme> <class-scheme> <class-name>com.example.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> <federated-loading>true</federated-loading> </cachestore-scheme>
親トピック: データ・ソースのキャッシュ
com.tangosol.net.cache.CacheStore
インタフェースのきわめて基本的なサンプル実装を確認します。CacheStore.storeAll()
を実装してJDBCのバルク挿入およびバルク更新を行います。データベースのキャッシュ構成のサンプルについては、データベースのキャッシュを参照してください。ヒント:
キャッシュをバルク・ロードすることによって、処理作業を軽減できます。次の例では、put
メソッドを使用して、キャッシュ・ストアに値を書き込みます。多くの場合、putAll
メソッドを使用してバルク・ロードを実行すると、処理作業およびネットワーク・トラフィックが軽減されます。キャッシュの事前ロードを参照してください。
例14-3 CacheStoreインタフェースの実装
package com.tangosol.examples.coherence; import com.tangosol.net.cache.CacheStore; import com.tangosol.util.Base; import java.sql.DriverManager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * An example implementation of CacheStore * interface. * * @author erm 2003.05.01 */ public class DBCacheStore extends Base implements CacheStore { // ----- constructors --------------------------------------------------- /** * Constructs DBCacheStore for a given database table. * * @param sTableName the db table name */ public DBCacheStore(String sTableName) { m_sTableName = sTableName; configureConnection(); } /** * Set up the DB connection. */ protected void configureConnection() { try { Class.forName("org.gjt.mm.mysql.Driver"); m_con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD); m_con.setAutoCommit(true); } catch (Exception e) { throw ensureRuntimeException(e, "Connection failed"); } } // ---- accessors ------------------------------------------------------- /** * Obtain the name of the table this CacheStore is persisting to. * * @return the name of the table this CacheStore is persisting to */ public String getTableName() { return m_sTableName; } /** * Obtain the connection being used to connect to the database. * * @return the connection used to connect to the database */ public Connection getConnection() { return m_con; } // ----- CacheStore Interface -------------------------------------------- /** * Return the value associated with the specified key, or null if the * key does not have an associated value in the underlying store. * * @param oKey key whose associated value is to be returned * * @return the value associated with the specified key, or * <tt>null</tt> if no value is available for that key */ public Object load(Object oKey) { Object oValue = null; Connection con = getConnection(); String sSQL = "SELECT id, value FROM " + getTableName() + " WHERE id = ?"; try { PreparedStatement stmt = con.prepareStatement(sSQL); stmt.setString(1, String.valueOf(oKey)); ResultSet rslt = stmt.executeQuery(); if (rslt.next()) { oValue = rslt.getString(2); if (rslt.next()) { throw new SQLException("Not a unique key: " + oKey); } } stmt.close(); } catch (SQLException e) { throw ensureRuntimeException(e, "Load failed: key=" + oKey); } return oValue; } /** * Store the specified value under the specific key in the underlying * store. This method is intended to support both key/value creation * and value update for a specific key. * * @param oKey key to store the value under * @param oValue value to be stored * * @throws UnsupportedOperationException if this implementation or the * underlying store is read-only */ public void store(Object oKey, Object oValue) { Connection con = getConnection(); String sTable = getTableName(); String sSQL; // the following is very inefficient; it is recommended to use DB // specific functionality that is, REPLACE for MySQL or MERGE for Oracle if (load(oKey) != null) { // key exists - update sSQL = "UPDATE " + sTable + " SET value = ? where id = ?"; } else { // new key - insert sSQL = "INSERT INTO " + sTable + " (value, id) VALUES (?,?)"; } try { PreparedStatement stmt = con.prepareStatement(sSQL); int i = 0; stmt.setString(++i, String.valueOf(oValue)); stmt.setString(++i, String.valueOf(oKey)); stmt.executeUpdate(); stmt.close(); } catch (SQLException e) { throw ensureRuntimeException(e, "Store failed: key=" + oKey); } } /** * Remove the specified key from the underlying store if present. * * @param oKey key whose mapping is to be removed from the map * * @throws UnsupportedOperationException if this implementation or the * underlying store is read-only */ public void erase(Object oKey) { Connection con = getConnection(); String sSQL = "DELETE FROM " + getTableName() + " WHERE id=?"; try { PreparedStatement stmt = con.prepareStatement(sSQL); stmt.setString(1, String.valueOf(oKey)); stmt.executeUpdate(); stmt.close(); } catch (SQLException e) { throw ensureRuntimeException(e, "Erase failed: key=" + oKey); } } /** * Remove the specified keys from the underlying store if present. * * @param colKeys keys whose mappings are being removed from the cache * * @throws UnsupportedOperationException if this implementation or the * underlying store is read-only */ public void eraseAll(Collection colKeys) { throw new UnsupportedOperationException(); } /** * Return the values associated with each the specified keys in the * passed collection. If a key does not have an associated value in * the underlying store, then the return map does not have an entry * for that key. * * @param colKeys a collection of keys to load * * @return a Map of keys to associated values for the specified keys */ public Map loadAll(Collection colKeys) { throw new UnsupportedOperationException(); } /** * Store the specified values under the specified keys in the underlying * store. This method is intended to support both key/value creation * and value update for the specified keys. * * @param mapEntries a Map of any number of keys and values to store * * @throws UnsupportedOperationException if this implementation or the * underlying store is read-only */ public void storeAll(Map mapEntries) { throw new UnsupportedOperationException(); } /** * Iterate all keys in the underlying store. * * @return a read-only iterator of the keys in the underlying store */ public Iterator keys() { Connection con = getConnection(); String sSQL = "SELECT id FROM " + getTableName(); List list = new LinkedList(); try { PreparedStatement stmt = con.prepareStatement(sSQL); ResultSet rslt = stmt.executeQuery(); while (rslt.next()) { Object oKey = rslt.getString(1); list.add(oKey); } stmt.close(); } catch (SQLException e) { throw ensureRuntimeException(e, "Iterator failed"); } return list.iterator(); } // ----- data members --------------------------------------------------- /** * The connection. */ protected Connection m_con; /** * The db table name. */ protected String m_sTableName; /** * Driver class name. */ private static final String DB_DRIVER = "org.gjt.mm.mysql.Driver"; /** * Connection URL. */ private static final String DB_URL = "jdbc:mysql://localhost:3306/CacheStore"; /** * User name. */ private static final String DB_USERNAME = "root"; /** * Password. */ private static final String DB_PASSWORD = null; }
親トピック: データ・ソースのキャッシュ
例14-4のMain.java
ファイルは、次のような制御可能なキャッシュ・ストアの2種類の操作方法を示しています。
制御可能なキャッシュ(別のサービスに配置されている必要があります)を使用して、キャッシュ・ストアの有効化または無効化を行います。これは、ControllableCacheStore1
クラスで示されます。
CacheStoreAware
インタフェースを使用して、キャッシュに追加されたオブジェクトで記憶域が不要なことを示します。これは、ControllableCacheStore2
クラスで示されます。
ControllableCacheStore1
とControllableCacheStore2
は両方とも、com.tangosol.net.cache.AbstractCacheStore
クラスを拡張します。このヘルパー・クラスによって、storeAll
操作およびeraseAll
操作の非最適実装が行われます。
CacheStoreAware
インタフェースを使用して、キャッシュに追加されるオブジェクトをデータベースに格納する必要がないことを示すことができます。データベースのキャッシュを参照してください。
例14-4は、Main.java
インタフェースの一覧を示しています。
例14-4 Main.java - 制御可能なCacheStoreとの相互作用
import com.tangosol.net.CacheFactory; import com.tangosol.net.NamedCache; import com.tangosol.net.cache.AbstractCacheStore; import com.tangosol.util.Base; import java.io.Serializable; import java.util.Date; public class Main extends Base { /** * A cache controlled CacheStore implementation */ public static class ControllableCacheStore1 extends AbstractCacheStore { public static final String CONTROL_CACHE = "cachestorecontrol"; String m_sName; public static void enable(String sName) { CacheFactory.getCache(CONTROL_CACHE).put(sName, Boolean.TRUE); } public static void disable(String sName) { CacheFactory.getCache(CONTROL_CACHE).put(sName, Boolean.FALSE); } public void store(Object oKey, Object oValue) { Boolean isEnabled = (Boolean) CacheFactory.getCache(CONTROL_CACHE).get(m_sName); if (isEnabled != null && isEnabled.booleanValue()) { log("controllablecachestore1: enabled " + oKey + " = " + oValue); } else { log("controllablecachestore1: disabled " + oKey + " = " + oValue); } } public Object load(Object oKey) { log("controllablecachestore1: load:" + oKey); return new MyValue1(oKey); } public ControllableCacheStore1(String sName) { m_sName = sName; } } /** * a valued controlled CacheStore implementation that * implements the CacheStoreAware interface */ public static class ControllableCacheStore2 extends AbstractCacheStore { public void store(Object oKey, Object oValue) { boolean isEnabled = oValue instanceof CacheStoreAware ? !((CacheStoreAware) oValue).isSkipStore() : true; if (isEnabled) { log("controllablecachestore2: enabled " + oKey + " = " + oValue); } else { log("controllablecachestore2: disabled " + oKey + " = " + oValue); } } public Object load(Object oKey) { log("controllablecachestore2: load:" + oKey); return new MyValue2(oKey); } } public static class MyValue1 implements Serializable { String m_sValue; public String getValue() { return m_sValue; } public String toString() { return "MyValue1[" + getValue() + "]"; } public MyValue1(Object obj) { m_sValue = "value:" + obj; } } public static class MyValue2 extends MyValue1 implements CacheStoreAware { boolean m_isSkipStore = false; public boolean isSkipStore() { return m_isSkipStore; } public void skipStore() { m_isSkipStore = true; } public String toString() { return "MyValue2[" + getValue() + "]"; } public MyValue2(Object obj) { super(obj); } } public static void main(String[] args) { try { // example 1 NamedCache cache1 = CacheFactory.getCache("cache1"); // disable cachestore ControllableCacheStore1.disable("cache1"); for(int i = 0; i < 5; i++) { cache1.put(new Integer(i), new MyValue1(new Date())); } // enable cachestore ControllableCacheStore1.enable("cache1"); for(int i = 0; i < 5; i++) { cache1.put(new Integer(i), new MyValue1(new Date())); } // example 2 NamedCache cache2 = CacheFactory.getCache("cache2"); // add some values with cachestore disabled for(int i = 0; i < 5; i++) { MyValue2 value = new MyValue2(new Date()); value.skipStore(); cache2.put(new Integer(i), value); } // add some values with cachestore enabled for(int i = 0; i < 5; i++) { cache2.put(new Integer(i), new MyValue2(new Date())); } } catch(Throwable oops) { err(oops); } finally { CacheFactory.shutdown(); } } }
親トピック: データ・ソースのキャッシュ
この項には次のトピックが含まれます:
すべての操作は、冪等になるように(つまり、望ましくない副作用なしに繰り返し処理できるように)設計する必要があります。ライトスルーおよびライトビハインド・キャッシュの場合、フェイルオーバー処理時にキャッシュ更新のデータベース部分を再試行することにより、低コストのフォルト・トレランスを提供できます。ライトビハインド・キャッシングの場合、冪等性によって、データ整合性に影響を与えずに、複数のキャッシュ更新を単一の起動にまとめることができます。
アプリケーションにライトビハインド・キャッシングの要件があっても、書込み組合せを回避する必要がある場合(たとえば、監査のため)、バージョニングされたキャッシュ・キーを作成する必要があります(たとえば、シーケンスIDを持つ通常の主キーを組み合せて)。
親トピック: 実装に関する考慮事項
Coherenceでは、複数のキャッシュ・ストア・インスタンス間での2フェーズの操作はサポートされません。つまり、2つのキャッシュ・エントリが更新され、別々のキャッシュ・サーバーにあるキャッシュ・ストア実装へのコールがトリガーされる場合、片方のデータベース更新は成功し、もう一方は失敗する可能性があります。この場合、アプリケーション・サーバーのトランザクション・マネージャで、キャッシュアサイド・アーキテクチャ(キャッシュとデータベースを、単一トランザクション内の2つの別個のコンポーネントとして更新する)を使用することをお薦めします。多くの場合、論理コミットの失敗がないように(当然、サーバー障害が発生しないようにもする)データベース・スキーマを設計することは可能です。ライトビハインド・キャッシングでは、データベースの動作によりputが影響を受けないため、この問題は回避されます(根本的な問題は設計プロセスの初期段階で解決しておく必要があります)。
親トピック: 実装に関する考慮事項
キャッシュ問合せでは、キャッシュに格納されているデータのみを操作し、欠落(または欠落する可能性のある)データをロードするためにキャッシュ・ストアの実装をトリガーすることはありません。そのため、キャッシュ・ストアでバックアップされたキャッシュに問い合せるアプリケーションは、その問合せに必要なすべてのデータがあらかじめロードされていることを確認する必要があります。キャッシュ内のデータの問合せを参照してください。効率性を考慮して、アプリケーションの起動時にデータ・セットをデータベースから直接キャッシュに送信して、ほとんどのバルク・ロード操作を実行しておく必要があります(NamedCache.putAll()
を使用して、データのブロックをキャッシュにまとめる)。ローダー処理は、制御可能なキャッシュ・ストア・パターンを使用して、データベースにバックアップされた循環更新を無効にする必要があります。制御可能なキャッシュ・ストアの実装のサンプルを参照してください。キャッシュ・ストアを制御するには、起動サービスを使用するか(クラスタ全体にエージェントを送信して各JVMのローカル・フラグを変更します)、レプリケート・キャッシュ(別のキャッシュ・サービス)に値を設定して、すべてのキャッシュ・ストア実装のメソッド呼出しでその値を読み取ります(一般的なデータベース操作に比べて最小限のオーバーヘッドで済みます)。Coherenceのクラスタ化されたJMX機能により、カスタムMBeanも簡単に実装できます。
親トピック: 実装に関する考慮事項
キャッシュ・ストアの実装では、ホスティングしているキャッシュ・サービスをコールバックしないでください。これには、内部的にCoherenceキャッシュ・サービスを参照するORMソリューションも含まれます。別のキャッシュ・サービス・インスタンスをコールすることも可能ですが、深くネストされたコールは避けるように注意する必要があります。コールのそれぞれがキャッシュ・サービス・スレッドを消費するため、キャッシュ・サービス・スレッド・プールを使用し尽くすと、デッドロックが発生する可能性があります。
親トピック: 実装に関する考慮事項
キャッシュ・エントリのクラス(値オブジェクト、データ転送オブジェクトとも呼ばれる)は、キャッシュ・サーバー・クラスパスに置く必要があります(キャッシュ・サーバーは、キャッシュ・エントリをシリアライズ/デシリアライズしてキャッシュ・ストアと相互作用する必要があるため)。
親トピック: 実装に関する考慮事項
キャッシュがライトビハインドとして構成され、<write-batch-factor>
が構成されている場合に使用される可能性が最も高いのは、CacheStore.storeAll
メソッドです。Coherenceでは、CacheLoader.loadAll
メソッドも使用されます。同様の理由により、その最初の使用にはリフレッシュアヘッドを有効にする必要がある場合があります。
親トピック: 実装に関する考慮事項
データベース接続は、コンテナ接続プール(またはサード・パーティの接続プール)から取得するか、スレッドローカルの遅延初期化パターンを使用して取得する必要があります。専用キャッシュ・サーバーは、多くの場合、管理コンテナなしでデプロイされるため、後者が最も魅力的な選択になります。ただし、過度な同時データベース接続を回避するように、キャッシュ・サービスのスレッド・プール・サイズは制限する必要があります。
親トピック: 実装に関する考慮事項