28 キャッシュ内のデータの問合せ
この章の内容は次のとおりです。
- 問合せの概要
Coherenceでは、指定された一連の基準を満たすキャッシュ・エントリを検索できます。 - 問合せの概念
問合せ処理の概念は、ValueExtractorインタフェースに基づいています。値エクストラクタは、問合せで指定のオブジェクトから属性を抽出するために使用されます(索引付けの場合も同様)。 - 問合せの実行
Coherenceでは、com.tangosol.util.Filtersクラスで多数の事前作成済フィルタが提供されます。 - フィルタ結果の効率的な処理
大量のデータ・セットをバッチで問い合せても、ヒープ領域不足を防ぐことができます。 - 問合せ索引の使用
問合せ索引によって、値(またはそれらの値の属性)と対応するキーをQueryMap内に関連付けて問合せのパフォーマンスを向上させることができます。 - バッチ問合せの実行
問合せを発行するクライアント上にメモリーを保持するために、問合せ結果をバッチで取得できる様々なテクニックがあります。 - ソート済ビューの使用
- 複数値属性での問合せの実行
Coherenceでは、コレクションや配列を含めた複数値属性の索引付けおよび問合せがサポートされています。 - 連鎖エクストラクタの使用
ChainedExtractor実装を使用すると、ゼロ引数(アクセッサ)メソッドの連鎖起動が可能となります。 - 問合せ結果の一貫性チェックをスキップするオプション
Coherenceではデフォルトで、指定したフィルタに一致するエントリが問合せ結果に含まれます。ただし、ターゲットとなるパーティションが同時に変更された場合、一貫性チェックを実行すると、問合せの再評価が繰り返されることがあります。 - 問合せのコストと効果の評価
問合せ内の各フィルタの見積もりコストと実際の効果をそれぞれ表示するために、問合せの実行計画レコードおよび問合せトレース・レコードを作成できます。
親トピック: データ・グリッド操作の実行
問合せの概要
現在、問合せはキャッシュされたデータにのみ適用されます(CacheLoaderインタフェースを使用して、問合せに一致する可能性がある追加データが取得されることはありません)。このため、問合せが実行される前にデータセット全体をキャッシュにロードする必要があります。データセットが大きすぎて使用可能なメモリーに収まらない場合は、特定のディメンション(日付など)に従ってキャッシュの内容を制限し、問合せの構造に基づいてキャッシュ問合せとデータベース問合せを手動で切り替えることができます。保守性の面から、通常はキャッシュ対応のデータ・アクセス・オブジェクト(DAO)の内部にこの処理を実装するのが最適です。
索引を付けるには、各パーティション・キャッシュ・ノードで属性を抽出できることが必要です。専用のキャッシュ・サーバー・インスタンスの場合は、これを実現するために(通常は)アプリケーション・クラスをキャッシュ・サーバーのクラスパスにインストールする必要があります。
ローカル・キャッシュおよびレプリケート・キャッシュの場合は、索引なしのデータまたは索引付きのデータに対して問合せをローカルで評価できます。パーティション・キャッシュの場合、問合せは通常、クラスタ全体でパラレルに実行され、索引が使用されます。索引のない属性にアクセスするには、オブジェクトをデシリアライズする必要があります(ただし、他の属性に索引を付けると、評価が必要なオブジェクトを減らすことができます)。最後に、Coherenceにはコストベース・オプティマイザ(CBO)が用意されており、問合せの効率化を支援するトレースおよび実行レポートもサポートされます。
ノート:
フィルタ・インタフェースを実装するすべてのクラスは、オブジェクトのシリアル化可能な状態のみに基づくようにhashCode()およびequals()メソッドを明示的に実装する必要があります。これは、フィルタがシリアル化され、ネットワーク経由で転送され、最終的にマップ・キーとして使用される、ObservableMap.addMapListenerおよびその他の類似の場所でフィルタを使用する場合に特に重要です。ValueExtractor実装でも同様に有効です。
親トピック: キャッシュ内のデータの問合せ
問合せの概念
ValueExtractorインタフェースに基づいています。値エクストラクタは、問合せで指定のオブジェクトから属性を抽出するために使用されます(索引付けの場合も同様)。以前、ほとんどの開発者に求められたのは、このインタフェースのReferenceExtractor実装を使用して、メソッド名を文字列として渡すことだけでした。ReflectionExtractorの代替は、プロパティおよびメソッド名の両方に対して機能するUniversalExtractorです。クラスUniversalExtractorは、POJO、JavaレコードおよびJSONオブジェクトに対して機能します。クラスcom.tangosol.util.Extractorsは、様々なValueExtractorクラスに対して単純なファクトリ・メソッドを提供し、サポートされているすべての値に対して機能するため、UniversalExtractorを使用するようになりました。Java 8の登場に伴い、コンパイル時の型の安全性を確保するためにメソッド参照を使用することを強くお薦めします。たとえば:
ValueExtractor extractor = ValueExtractor.of(Customer::getName);Coherenceは、com.tangosol.util.Filtersクラスを介して単純なフィルタDSLを提供します。このクラスには、フィルタを簡単に作成するための多くのファクトリ・メソッドが含まれています。これらのフィルタを使用すると、静的にインポートされた場合は特に、コードが読みやすくなります。このため、Filterクラスを直接作成するかわりに、フィルタを使用することを強くお薦めします。
getName属性を持つオブジェクトが含まれているキャッシュに問合せを実行するには、Filterを使用する必要があります。次のフィルタは、Filters.equalsファクトリ・メソッドを使用し、比較するメソッド参照と値を指定します。
Filter filter = Filters.equal(Customer::getName, "Bob Smith");次の例は、特定のフィルタ基準を満たすキャッシュ・エントリを選択するルーチンを示しています。
for (Map.Entry<Integer, Person> entry : cache.entrySet(filter)) {
Integer key = entry.getKey();
Person person = entry.getValue();
System.out.println("key=" + key + " person=" + person);
}次の例では、キャッシュ・エントリを選択してソートするフィルタを使用します。
// entrySet(Filter filter, Comparator comparator) Iterator iter = cache.entrySet(filter, null).iterator();
追加したnull引数は、キャッシュ内のComparableオブジェクトの自然な順序を使用して結果セットをソートする必要があることを指定します。クライアントでは、Comparatorの実装を用意することによって、結果セットの順序を明示的に指定する場合があります。ソート処理を行う場合は、ソートの前に結果セット全体を取得しておく必要があるために、Coherenceで適用できる最適化が著しく制限されるので注意してください。
親トピック: キャッシュ内のデータの問合せ
問合せの実行
com.tangosol.util.Filtersクラスで多数の事前作成済フィルタが提供されます。例28-1に、問合せを作成し、Filters.greaterEqualファクトリ・メソッドを使用する方法を示します。
例28-1 フィルタを指定したキャッシュの問合せ
Filter filter = Filters.greaterEqual(Person::getId, 18);
for (Map.Entry<Integer, Person> entry : cache.entrySet(filter)) {
Integer key = entry.getKey();
Person person = entry.getValue();
System.out.println("key=" + key + " person=" + person);
}ノート:
ニア・キャッシュを通じて問合せを実行することは可能ですが、問合せではニア・キャッシュのフロント部分が使用されません。問合せでニア・キャッシュを使用する場合は、次のシーケンスを使用するのが最適のアプローチです。Set setKeys = cache.key set(filter);
Map mapResult = cache.getAll(setKeys);親トピック: キャッシュ内のデータの問合せ
フィルタ結果の効率的な処理
BUFFER_SIZE(この例では100)のみです。
ノート:
LimitFilter APIは、次の例と同様に、結果を別々に処理できます。ただし、LimitFilterは、ユーザー・インタフェースのように結果がページングされるシナリオに適しています。問合せ結果のデータをすべて処理する場合には効率的な方法ではありません。
例28-2 問合せ結果のバッチ処理
public static void performQuery()
{
NamedCache c = CacheFactory.getCache("test");
// Search for entries that start with 'c'
Filter query = Filters.like(Extractors.identity(), "c%", '\\', true);
// Perform query, return keys of entries that match
Set keys = c.keySet(query);
// The amount of objects to process at a time
final int BUFFER_SIZE = 100;
// Object buffer
Set buffer = new HashSet(BUFFER_SIZE);
for (Iterator i = keys.iterator(); i.hasNext();)
{
buffer.add(i.next());
if (buffer.size() >= BUFFER_SIZE)
{
// Bulk load BUFFER_SIZE number of objects from cache
Map entries = c.getAll(buffer);
// Process each entry
process(entries);
// Done processing these keys, clear buffer
buffer.clear();
}
}
// Handle the last partial chunk (if any)
if (!buffer.isEmpty())
{
process(c.getAll(buffer));
}
}
public static void process(Map map)
{
for (Iterator ie = map.entrySet().iterator(); ie.hasNext();)
{
Map.Entry e = (Map.Entry) ie.next();
out("key: "+e.getKey() + ", value: "+e.getValue());
}
}親トピック: キャッシュ内のデータの問合せ
問合せ索引の使用方法
QueryMap内に関連付けて問合せのパフォーマンスを向上させることができます。この項には次のトピックが含まれます:
親トピック: キャッシュ内のデータの問合せ
索引の作成
QueryMapクラスのaddIndexメソッドを使用して、索引を作成します。問合せ可能な属性であれば、このメソッドを使用して索引を付けることができます。このメソッドには、次の3つのパラメータが含まれています。
addIndex(ValueExtractor extractor, boolean fOrdered, Comparator comparator)例28-3は、索引の作成方法を示しています。
例28-3 索引を作成するサンプル・コード
NamedCache<Integer, Customer> myCache = CacheFactory.getCache("MyCache");
myCache.addIndex(Customer::getOutstandingBalance, true, null);fOrdered引数では、索引構造をソートするかどうかを指定します。ソートされた索引は、「2つの日付の間に収まるエントリをすべて選択」や「姓がSで始まる従業員をすべて選択」などの範囲問合せに役立ちます。等価問合せの場合は、順序付けられていない索引を使用できます。この索引を使用すると、領域と時間の面で効率が向上することがあります。
comparator引数では、索引の順序付け用にカスタムのjava.util.Comparatorを指定できます。
addIndexメソッドは、単にキャッシュ実装のヒントとして示したものであり、索引がサポートされていない場合や目的の索引(またはそれに類似した索引)が存在する場合は、キャッシュによって無視されることがあります。アプリケーションでは、確実に索引が提示されるようにするために、索引が存在する場合でもこのメソッドをコールして索引を提示することが求められます。たとえば、分散環境では、各サーバーが起動時に同じ索引セットを推奨する可能性が高いため、別のサーバーが同じ索引をリクエストしたかどうかにかかわらず、これらの索引を無条件にリクエストしたとしても、アプリケーションにとってマイナス面はありません。
Coherenceでは必要に応じて問合せを結合することができます。またCoherenceには、索引の使用優先順位を決めるコストベース・オプティマイザ(CBO)が搭載されています。索引を利用するには、問合せで使用されているものと等価のエクストラクタ((Object.equals())を使用する必要があります。
適用された索引のリストはStorageManagerMBeanから取得できます。Oracle Coherenceの管理のStorageManagerMBeanを参照してください。
親トピック: 問合せ索引の使用
ユーザー定義索引の作成
アプリケーションを選択してユーザー定義索引を作成し、索引に追加されるエントリを管理できます。ユーザー定義索引は、通常、索引の保守に必要なメモリーや処理のオーバーヘッドの削減に使用されます。ユーザー定義索引を作成するには、アプリケーションでMapIndexインタフェースとIndexAwareExtractorインタフェースを実装する必要があります。この項では、条件付き索引を作成するためのインタフェースの実装を提供するConditionalIndexクラスとConditionalExtractorクラスについても説明します。条件付き索引は関連付けられたフィルタを使用して、エントリに索引を付ける必要があるかどうかを評価します。
この項には次のトピックが含まれます:
MapIndexインタフェースの実装
MapIndexインタフェースは、索引付けされたMap(またはその値の属性)に格納されている値を、索引付けされたMapの対応キーに関連付けるために使用されます。アプリケーションは、このインタフェースを実装してカスタム索引を提供します。
次の例の実装では、null以外の値を持つエントリを追加するのみの索引が定義されています。これは、大量のエントリを持つキャッシュがあり、小さなサブセットにのみ意味のあるnull以外の値が格納されている場合に特に便利です。
public class CustomMapIndex implements MapIndex
{
public void insert(Map.Entry entry)
{
if (entry.getValue()!= null)
{
...
}
}
...
}
この例では、エントリの値を抽出する前にその値がnullかどうかが確認されますが、これは後でも実行できます。エントリの値がnullの場合、索引には何も挿入されません。nullに関する類似の確認は、MapIndex updateメソッドでも必要です。残りのMapIndexメソッドも、適切に実装される必要があります。
親トピック: ユーザー定義索引の作成
IndexAwareExtractorインタフェースの実装
IndexAwareExtractorインタフェースは、MapIndex索引の作成と破棄をサポートするValueExtractorインタフェースの拡張機能です。このインタフェースのインスタンスは、QueryMap APIとともに使用してカスタム索引の作成をサポートすることを目的としたものです。次の例は、このインタフェースの実装方法を示しており、先程作成したCustomMapIndexクラスの例に対するものです。
public class CustomIndexAwareExtractor
implements IndexAwareExtractor, ExternalizableLite, PortableObject
{
public CustomIndexAwareExtractor(ValueExtractor extractor)
{
m_extractor = extractor;
}
public MapIndex createIndex(boolean fOrdered, Comparator comparator,
Map mapIndex)
{
ValueExtractor extractor = m_extractor;
MapIndex index = (MapIndex) mapIndex.get(extractor);
if (index != null)
{
throw new IllegalArgumentException(
"Repetitive addIndex call for " + this);
}
index = new CustomMapIndex(extractor, fOrdered, comparator);
mapIndex.put(extractor, index);
return index;
}
public MapIndex destroyIndex(Map mapIndex)
{
return (MapIndex) mapIndex.remove(m_extractor);
}
...
}
この例では、基礎となるエクストラクタを実際に使用して索引を作成し、最終的にキャッシュ・エントリから値を抽出しています。IndexAwareExtractor実装は、既存のQueryMapインタフェースを保持しながら、カスタムのMapIndex実装の作成と破棄を管理するために使用されます。
IndexAwareExtractorは、QueryMap.addIndexコールとQueryMap.removeIndexコールに渡されます。これに対して、CoherenceはIndexAwareExtractorのcreateIndexとdestroyIndexをコールします。IndexAwareExtractorは、createIndexとdestroyIndexに渡される、エクストラクタと索引を関連付けるMapを保持します。
親トピック: ユーザー定義索引の作成
条件付き索引の使用
条件付き索引はカスタム索引であり、上述のとおりMapIndexインタフェースとIndexAwareExtractorインタフェースの両方を実装し、関連付けられたフィルタを使用してエントリに索引を付ける必要があるかどうかを評価します。エントリの抽出された値は、そのフィルタがtrueに評価されている場合、索引に追加されます。実装クラスは、それぞれConditionalIndexとConditionalExtractorです。
ConditionalIndexはConditionalExtractorによって作成されます。ConditionalIndexで使用されるフィルタとエクストラクタは、ConditionalExtractorで設定され、QueryMap.addIndexコールの間にConditionalIndexコンストラクタに渡されます。
ConditionalExtractorは、ConditionalIndexの作成のみに使用されるIndexAwareExtractor実装です。基礎となるValueExtractorは、索引の作成時の値の抽出に使用され、指定された索引マップで作成されたConditionalIndexに関連付けられているエクストラクタです。値を抽出するConditionalExtractorの使用はサポートされていません。たとえば:
ValueExtractor extractor = Extractors.extract(“lastName”); Filter filter = new NotEqualsFilter(Person::getId, null); ValueExtractor condExtractor = new ConditionalExtractor(filter, extractor, true); // add the conditional index which should only contain the last name values for the // entries with non-null Ids cache.addIndex(condExtractor, true, null);
親トピック: ユーザー定義索引の作成
バッチ問合せの実行
key set形式の問合せをgetAll()と組み合せると、エントリ・セット全体がクライアント上で同時にデシリアライズされないため、メモリーの消費が削減されます。また、ニア・キャッシュも利用されます。たとえば:
// key set(Filter filter)
Set setKeys = cache.keySet(filter);
Set setPageKeys = new HashSet();
int PAGE_SIZE = 100;
for (Iterator iter = setKeys.iterator(); iter.hasNext();)
{
setPageKeys.add(iter.next());
if (setPageKeys.size() == PAGE_SIZE || !iter.hasNext())
{
// get a block of values
Map mapResult = cache.getAll(setPageKeys);
// process the block
// ...
setPageKeys.clear();
}
}
LimitFilterを使用すると、クライアントに送信するデータ量が制限されると同時に、ページングが使用可能になります。それが正しく機能するには、LimitFilterの使用に2つの前提条件があります。
-
問題のデータ・セットを同時に変更することはできません。
-
データはクラスタのすべての記憶域ノード間で均等に配分され、データ・セット全体の公平なサンプルが各データに含まれます。
ノート:
再配分の場合、データはすべての記憶域ノード間で均等に配分されないため、受信問合せに対して間違った結果セットになります。
int pageSize = 25;
Filter filter = Filters.greaterEqual(Person::getAge, 18);
// get entries 1-25
Filter limitFilter = new LimitFilter(filter, pageSize);
Set entries = cache.entrySet(limitFilter);
// get entries 26-50
limitFilter.nextPage();
entries = cache.entrySet(limitFilter);分散/パーティション・キャッシュを使用するときには、問合せでPartitionedFilterを使用してパーティションとキャッシュ・サーバーをターゲットにすることができます。各問合せのリクエストは単一のキャッシュ・サーバーをターゲットとしているため、これは、問合せ結果をバッチ処理するための最も効率的な方法であり、リクエストに応答する必要のあるサーバーの数を減らし、ネットワークを最も効率的に使用できるようにします。
ノート:
PartitionedFilterの使用はクラスタ・メンバーに制限されています。Coherence*Extendのクライアントは使用できません。Coherence*Extendクライアントは、上述の2つのテクニックを使用できます。また、これらの問合せをInvocableとして実装し、リモートでCoherence*Extendクライアントが実行することも可能です。
パーティションで問合せパーティションを実行するには:
DistributedCacheService service =
(DistributedCacheService) cache.getCacheService();
int cPartitions = service.getPartitionCount();
PartitionSet parts = new PartitionSet(cPartitions);
for (int iPartition = 0; iPartition < cPartitions; iPartition++)
{
parts.add(iPartition);
Filter filterPart = new PartitionedFilter(filter, parts);
Set setEntriesPart = cache.entrySet(filterPart);
// process the entries ...
parts.remove(iPartition);
}問合せは、サーバー・ベースでサーバー上で実行することもできます。
DistributedCacheService service =
(DistributedCacheService) cache.getCacheService();
int cPartitions = service.getPartitionCount();
PartitionSet partsProcessed = new PartitionSet(cPartitions);
for (Iterator iter = service.getStorageEnabledMembers().iterator();
iter.hasNext();)
{
Member member = (Member) iter.next();
PartitionSet partsMember = service.getOwnedPartitions(member);
// due to a redistribution some partitions may have been processed
partsMember.remove(partsProcessed);
Filter filterPart = new PartitionedFilter(filter, partsMember);
Set setEntriesPart = cache.entrySet(filterPart);
// process the entries ...
partsProcessed.add(partsMember);
}
// due to a possible redistribution, some partitions may have been skipped
if (!partsProcessed.isFull())
{
partsProcessed.invert();
Filter filter = new PartitionedFilter(filter, partsProcessed);
// process the remaining entries ...
}親トピック: キャッシュ内のデータの問合せ
ソート済ビューの使用
ソート済ビューにより、Coherenceで管理されるデータのクライアント側ビューを作成できます。このビューは、エントリ値の自然なソート順または指定されたComparatorに基づいてソートされます。
親トピック: キャッシュ内のデータの問合せ
ソート済ビューの作成
ソート済ビューは、次のViewBuilder APIを使用して作成できます:
NamedMap<String, String> states = session.getMap("states");
NamedMap<String, String> sortedStates = states.view().sorted().build();- 状態名のマスター・コピーが2文字の状態コードをキーとして格納されている分散キャッシュへの参照を取得します。
- 状態のクライアント側ビューを作成します。これはマップ値の自然な順序でソートされています。たとえば、状態名です。
ビューの内容は、他のビューと同じくCoherenceによって自動的に同期されます。つまり、状態のマスター・リストに加えられた変更は、自動的にクライアント側の各ビューに反映されます。
カスタムのComparatorを次のsortedメソッドに渡すことで、より複雑なデータ型のビューを作成することもできます:
NamedMap<Long, Person> people = session.getMap("people");
NamedMap<Long, Person> sortedPeople = people.view()
.sorted(Comparator.comparing(Person::getAge).reversed())
.build();上記の例では、年齢でソートされた最年長から最年少までの全員のビューが提供されます。
ViewBuilder APIでサポートされている他のすべての操作(ソート前のビューのエントリのフィルタリングなど)を実行できます。たとえば、年齢順にソートされた最年少から最年長までのすべての女性のビューを作成するには、次のようにビューを定義します:
NamedMap<Long, Person> people = session.getMap("people");
NamedMap<Long, Person> sortedPeople = people.view()
.filter(Filters.equal(Person::getGender, Gender.FEMALE))
.sorted(Comparator.comparing(Person::getAge))
.build();ノート:
ソートは、常に、データがサーバーから取得された後にクライアントで実行されます。ほとんどの場合、これは操作に指定した順序に関係なく発生します。たとえば、filter操作とsorted操作の順序を逆にして、前の例について次のようにビューを作成した場合、動作は同じです:
NamedMap<Long, Person> people = session.getMap("people");
NamedMap<Long, Person> sortedPeople = people.view()
.sorted(Comparator.comparing(Person::getAge))
.filter(Filters.equal(Person::getGender, Gender.FEMALE))
.build();唯一の例外は、map操作です。この操作では、ネットワーク経由で転送されるデータの量を減らすためにサーバー上で値の型を変換することにより、クライアントに格納される値の型が変わるためです。保持しているものしかソートできないため、正しい値型を使用できるように、必ずsort操作をmap操作の後に指定する必要があります:
NamedMap<Long, Person> people = session.getMap("people");
NamedMap<Long, Name> sortedNames = people.view()
.filter(Filters.equal(Person::getGender, Gender.MALE))
.map(Person::getName)
.sorted(Comparator.comparing(Name::getLast)
.thenComparing(Name::getFirst))
.build();前の例では、サーバー上のすべての男性のNamesを抽出してから、クライアント上で名ではなく姓でソートし、次に名でソートします。
操作を実行順に指定することをお薦めします。たとえば、操作が実行される順序であるfilter、map、sortedのようにします。
親トピック: ソート済ビューの使用
複数値属性での問合せの実行
ContainsAllFilter、ContainsAnyFilterおよびContainsFilterが使用されます。
Set searchTerms = new HashSet();
searchTerms.add("java");
searchTerms.add("clustering");
searchTerms.add("books");
// The cache contains instances of a class "Document" which has a method
// "getWords" which returns a Collection<String> containing the set of
// words that appear in the document.
Filter filter = Filters.containsAll(Document::getWords, searchTerms);
Set entrySet = cache.entrySet(filter);
// iterate through the search results
// ...親トピック: キャッシュ内のデータの問合せ
連鎖エクストラクタの使用
ChainedExtractorを実装すると、ゼロ引数(アクセッサ)メソッドの連鎖起動が可能になります。次の例では、まずエクストラタがプロパティのリファレンスを使用して、キャッシュされているPersonオブジェクトごとにnameプロパティにアクセスします。その後、リフレクションを使用して、返されるStringでlengthメソッドをコールします。
ValueExtractor extractor = Extractors.chained("name", "length()");このエクストラクタを問合せに渡し、(たとえば)名前が10文字以内の人をすべて、問合せで選択することができます。メソッド呼出しは、無限に連鎖できます。たとえば、name.trim().length()。
POFエクストラクタおよびPOFアップデータは、SimplePofPathクラスを使用してChainedExtractorsと同じ機能を提供します。POFエクストラクタとPOFアップデータの使用を参照してください。
親トピック: キャッシュ内のデータの問合せ
問合せ結果の一貫性チェックをスキップするオプション
Coherenceではデフォルトで、指定したフィルタに一致するエントリが問合せ結果に含まれます。ただし、ターゲットとなるパーティションが同時に変更された場合、一貫性チェックを実行すると、問合せの再評価が繰り返されることがあります。
次のオプションを使用すると、レスポンスを速くするためにCoherenceで一貫性チェックを緩和する必要があるかどうかを判断できます:
- 個々のアグリゲータのcharacteristics()をオーバーライドすることにより、アグリゲータ・レベルで実行。
たとえば:
@Override public int characteristics() { return super.characteristics() | ALLOW_INCONSISTENCIES; } - JVM引数を介してJVMレベルで実行:
システム・プロパティ
-Dcoherence.query.retry = 0を渡します。
集約メソッドを使用した問合せの場合、この両方のオプションを使用するとレスポンスが速くなります。フィルタ(entrySetなど)を使用した問合せの場合は、JVMオプションを使用して問合せ結果の再評価をスキップできます。
親トピック: キャッシュ内のデータの問合せ
問合せのコストと効果の評価
この項には次のトピックが含まれます:
問合せレコードの作成
com.tangosol.util.aggregator.QueryRecorderクラスは、特定のフィルタの実行レコードまたはトレース・レコードを生成します。このクラスは、クラスタ内のすべてのノードを問い合せ、結果を集約する機能を持つ並列アグリゲータの実装です。このクラスは、問合せ内のフィルタに関する見積もりコストを示すEXPLAINレコードと、問合せ内の各フィルタの実際の効果を示すTRACEレコードの2つのレコード・タイプをサポートしています。
問合せレコードを作成するには、RecordTypeパラメータを指定する新しいQueryRecorderインスタンスを作成します。aggregateメソッドのパラメータとしてテストされるインスタンスとフィルタを含めます。次の例では、説明レコードを作成しています。
NamedCache<String, Person> cache = CacheFactory.getCache("people");
cache.addIndex(Person::getAge, true, null);
AllFilter filter = new AllFilter(new Filter[]
{
Filters.equal(Person::getAge, 16).or(Filters.equal(Person::getAge, 19)),
Filters.equal(Person::getLastName, "Smith"),
Filters.equal(Person::getFirstName, "Bob"),
});
QueryRecorder<String, Person> agent = new QueryRecorder<>(RecordType.EXPLAIN);
Object resultsExplain = cache.aggregate(filter, agent);
System.out.println("\nExplain Plan=\n" + resultsExplain + "\n");トレース・レコードを作成するには、RecordTypeパラメータをTRACEに変更します。
QueryRecorder<String, Person> agent = new QueryRecorder<>(RecordType.TRACE);親トピック: 問合せのコストと効果の評価
問合せレコードの解析
問合せレコードは、問合せを構成するフィルタと索引の評価に使用されます。実行計画レコードは、フィルタの適用に関連する見積もりコストの評価に使用されます。トレース・レコードは、キー・セットの削減に対するフィルタの効果の評価に使用されます。
この項では、サンプルの実行計画レコードおよびサンプルのトレース・レコードを示し、レコードの読取りおよび解析方法について説明します。レコードは、記憶域が有効な4つのノードのクラスタにある1500エントリのサンプル問合せに基づいています。問合せは、16歳または19歳で、姓がSmith、名がBobである人を検索するフィルタで構成されています。最後に、getAgeに対する索引が追加されます。問合せレコードの実行の例を参照してください。
NamedCache<String, Person> cache = CacheFactory.getCache("people");
cache.addIndex(Person::getAge, true, null);
AllFilter filter = new AllFilter(new Filter[]
{
Filters.equal(Person::getAge, 16).or(Filters.equal(Person::getAge, 19)),
Filters.equal(Person::getLastName, "Smith"),
Filters.equal(Person::getFirstName, "Bob"),
});この項には次のトピックが含まれます:
問合せの実行計画レコード
問合せの実行レコードは、問合せ操作の一部としてフィルタの評価の見積もりコストを提供します。コストでは、フィルタで索引を使用できるかどうかが考慮されます。コスト評価は、問合せの実行時にフィルタが適用される順序の決定に使用されます。索引を使用するフィルタは、コストが最も低く、最初に適用されます。
例28-4では、一般的な問合せ実行計画レコードを示します。レコードには、問合せ内の各フィルタを評価するための実行計画表、およびフィルタで使用できる各索引が記載されている索引ルックアップ表が含まれています。これらの列は、次のように示されます。
-
名前 – この列は、問合せ内の各フィルタの名前を示します。複合フィルタの場合は、複合フィルタ内の各フィルタの情報が示されます。
-
索引 – この列は、指定のフィルタで索引を使用できるかどうかを示します。索引が見つかった場合、表示された番号が索引ルックアップ表の索引番号に対応します。例では、
getAge()に対して、順序付けられた単純なマップ索引(0)が見つかりました。 -
コスト – この列は、フィルタの適用に関する見積もりコストを示します。索引が使用可能な場合、コストは1となります。索引の適用操作では、索引コンテンツに1回だけアクセスすれば済むので、値1が使用されます。例では、記憶域が有効な4つのクラスタ・メンバーがあり、したがって、コストには4つのメンバーすべての索引へのアクセスが反映されます。索引が存在しない場合、コストは
EVAL_COST * number of keysとして計算されます。EVAL_COST値は、定数値(1000)です。これは、フィルタを使用してキー・セットを削減するために全体スキャンを実行する相対的なコストを示します。例では、評価が必要な1500のキャッシュ・エントリがあります。索引付きのエントリへの問合せは常に、索引付きでないエントリと比べると安価ですが、効果が保証されているわけではありません。
例28-4のレコードは、getAge()の等価フィルタはコストが低いことを示しています。これは、そのフィルタが、関連付けられた索引を持ち、getLastName()およびgetFirstName()の前に適用されるためです。ただし、getAge()フィルタは、安価ですが、すべてのエントリが16か19のいずれかであり、BobおよびSmithに一致するエントリがほとんどない場合、あまり効果的ではないこともあります。この場合、getLastName()およびgetFirstName()に索引を追加するほうがより効果的です。さらに、索引でキー・セットの削減がうまく行われていない場合、索引の作成に関連するコスト(主にメモリー消費)は無駄になります。
例28-4 サンプルの問合せ実行計画レコード
Explain Plan
Name Index Cost
==================================================================================
com.tangosol.util.filter.AllFilter | ---- | 0
com.tangosol.util.filter.OrFilter | ---- | 0
EqualsFilter(.getAge(), 16) | 0 | 4
EqualsFilter(.getAge(), 19) | 0 | 4
EqualsFilter(.getLastName(), Smit | 1 | 1500000
EqualsFilter(.getFirstName(), Bob | 2 | 1500000
Index Lookups
Index Description Extractor Ordered
==================================================================================
0 SimpleMapIndex: Extractor=.getAge(), Ord .getAge() true
1 No index found .getLastName() false
2 No index found .getFirstName() false
親トピック: 問合せレコードの解析
問合せのトレース・レコード
問合せのトレース・レコードは、問合せ操作の一部としてフィルタの評価の実コストを提供します。コストでは、フィルタで索引を使用できるかどうかが考慮されます。問合せが実際に実行され、キー・セットの削減に関する各フィルタの効果が示されます。
例28-5では、一般的な問合せトレース・レコードを示します。レコードには、問合せ内の各フィルタの効果を示すトレース表、およびフィルタで使用できる各索引が記載されている索引ルックアップ表が含まれています。これらの列は、次のように示されます。
-
名前 – この列は、問合せ内の各フィルタの名前を示します。複合フィルタの場合は、複合フィルタ内の各フィルタの情報が示されます。
-
索引 – この列は、指定のフィルタで索引を使用できるかどうかを示します。索引が見つかった場合、表示された番号が索引ルックアップ表の索引番号に対応します。例では、
getAge()に対して、順序付けられた単純なマップ索引(0)が見つかりました。 -
有効性 – この列は、各フィルタの結果として実際に削減されたキー・セットの量を示します。値は、
prefilter_key_set_size|postfilter_key_set_sizeとして示され、パーセンテージでも表されます。prefilter_key_set_size値は、フィルタの評価または索引の適用前のキー・セット・サイズを表します。postfilter_key_set_size値は、フィルタの評価または索引の適用後に残っているキー・セット・サイズを表します。複合フィルタ・エントリの場合、この値は、含まれているフィルタ全体の結果です。索引に基づいてキー・セット・サイズを削減することができなくなると、結果のキー・セットはデシリアライズされ、索引フィルタ以外のフィルタが適用されます。 -
期間 – この列は、フィルタの評価または索引の適用にかかった時間をミリ秒で示します。値0は、記録された時間がレポートのしきい値を下回ったことを示します。次の例で、
63ミリ秒は、最初のフィルタgetLastName()で発生したキー・セットのデシリアライズのみが必要な結果です。
例28-5のレコードは、1500エントリを、姓がSmithで名がBob、年齢が16歳か19歳の100件のエントリに絞り込むのに約63ミリ秒かかったことを示しています。1500エントリのキー・セットは、まず、getAge()の索引を使用して300に削減されました。その結果としての300エントリ(索引を使用してこれ以上削減することはできません)は、デシリアライズされ、getLastName()に基づいて150エントリに削減され、getFirstName()に基づいて100に削減されました。この例では、getAge()の索引は、実質1200エントリのキー・セットを削減できたので、消費したリソースに値する価値があります。getLastNameおよびgetFirstNameの索引は、問合せ全体のパフォーマンスを高めますが、索引の作成に必要な追加のリソースに値する価値がない場合もあります。
例28-5 サンプルの問合せトレース・レコード
Trace
Name Index Effectiveness Duration
==================================================================================
com.tangosol.util.filter.AllFilter | ---- | 1500|300(80%) | 0
com.tangosol.util.filter.OrFilter | ---- | 1500|300(80%) | 0
EqualsFilter(.getAge(), 16) | 0 | 1500|150(90%) | 0
EqualsFilter(.getAge(), 19) | 0 | 1350|150(88%) | 0
EqualsFilter(.getLastName(), Smit | 1 | 300|300(0%) | 0
EqualsFilter(.getFirstName(), Bob | 2 | 300|300(0%) | 0
com.tangosol.util.filter.AllFilter | ---- | 300|100(66%) | 63
EqualsFilter(.getLastName(), Smit | ---- | 300|150(50%) | 63
EqualsFilter(.getFirstName(), Bob | ---- | 150|100(33%) | 0
Index Lookups
Index Description Extractor Ordered
==================================================================================
0 SimpleMapIndex: Extractor=.getAge(), Ord .getAge() true
1 No index found .getLastName() false
2 No index found .getFirstName() false
親トピック: 問合せレコードの解析
問合せレコードの実行の例
次の例は、問合せレコードの作成を示す単純なクラスです。このクラスは、分散キャッシュ(mycache)を1500 Personオブジェクトとともにロードし、属性の索引を作成し、問合せを実行し、クラスが終了する前にコンソールに出力される問合せ実行計画レコードと問合せトレース・レコードの両方を作成します。
例28-6 問合せレコードの例
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.util.Filter;
import com.tangosol.util.aggregator.QueryRecorder;
import com.tangosol.util.filter.AllFilter;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import static com.tangosol.util.Filters.equal;
import static com.tangosol.util.aggregator.QueryRecorder.RecordType;
public class QueryRecordExample
{
public static void main(String[] args)
{
System.setProperty("coherence.wka", "127.0.0.1");
System.setProperty("coherence.distributed.localstorage", "true");
// ((age == 16 OR age == 19) AND lastName == 'Smith' AND firstName == 'Bob')
AllFilter filter = new AllFilter(new Filter[]
{
equal(Person::getAge, 16).or(equal(Person::getAge, 19)),
equal(Person::getLastName, "Smith"),
equal(Person::getFirstName, "Bob"),
});
NamedCache<String, Person> cache = CacheFactory.getCache("people");
cache.addIndex(Person::getAge, true, null);
populateCache(cache);
testExplain(cache, filter);
testTrace(cache, filter);
}
public static void testExplain(NamedCache<String, Person> cache, Filter filter)
{
QueryRecorder<String, Person> agent = new QueryRecorder<>(RecordType.EXPLAIN);
Object resultsExplain = cache.aggregate(filter, agent);
System.out.println("\nExplain Plan=\n" + resultsExplain + "\n");
}
public static void testTrace(NamedCache<String, Person> cache, Filter filter)
{
QueryRecorder<String, Person> agent = new QueryRecorder<>(RecordType.TRACE);
Object resultsExplain = cache.aggregate(filter, agent);
System.out.println("\nTrace =\n" + resultsExplain + "\n");
}
private static void populateCache(NamedCache<String, Person> cache)
{
Map<String, Person> buffer = new HashMap<>();
for (int i = 0; i < 1500; ++i)
{
Person person = new Person(i % 3 == 0 ? "Joe" : "Bob",
i % 2 == 0 ? "Smith" : "Jones", 15 + i % 10);
buffer.put("key" + i, person);
}
cache.putAll(buffer);
}
public static class Person implements Serializable
{
public Person(String sFirstName, String sLastName, int nAge)
{
m_sFirstName = sFirstName;
m_sLastName = sLastName;
m_nAge = nAge;
}
public String getFirstName()
{
return m_sFirstName;
}
public String getLastName()
{
return m_sLastName;
}
public int getAge()
{
return m_nAge;
}
public String toString()
{
return "Person( " +m_sFirstName + " " + m_sLastName + " : " + m_nAge + ")";
}
private final String m_sFirstName;
private final String m_sLastName;
private final int m_nAge;
}
}
親トピック: 問合せのコストと効果の評価