22 キャッシュ内のデータの問合せ

問合せを実行し、索引を使用して特定の条件に一致するキャッシュ内のデータを取得できます。問合せと索引は、Coherence付属のフィルタを採用した単純なものにするか、またはコレクションや配列などの複数値属性に対して実行できます。

この章の内容は次のとおりです。

問合せの概要

Coherenceでは、指定された一連の基準を満たすキャッシュ・エントリを検索できます。結果セットは必要に応じてソートできます。問合せは、コミット読取り分離を使用して評価されます。

現在、問合せはキャッシュされたデータにのみ適用されます(CacheLoaderインタフェースを使用して、問合せに一致する可能性がある追加データが取得されることはありません)。このため、問合せが実行される前にデータセット全体をキャッシュにロードする必要があります。データセットが大きすぎて使用可能なメモリーに収まらない場合は、特定のディメンション(日付など)に従ってキャッシュの内容を制限し、問合せの構造に基づいてキャッシュ問合せとデータベース問合せを手動で切り替えることができます。保守性の面から、通常はキャッシュ対応のデータ・アクセス・オブジェクト(DAO)の内部にこの処理を実装するのが最適です。

索引を付けるには、各パーティション・キャッシュ・ノードで属性を抽出できることが必要です。専用のキャッシュ・サーバー・インスタンスの場合は、これを実現するために(通常は)アプリケーション・クラスをキャッシュ・サーバーのクラスパスにインストールする必要があります。

ローカル・キャッシュおよびレプリケート・キャッシュの場合は、索引なしのデータまたは索引付きのデータに対して問合せをローカルで評価できます。パーティション・キャッシュの場合、問合せは通常、クラスタ全体でパラレルに実行され、索引が使用されます。索引のない属性にアクセスするには、オブジェクトをデシリアライズする必要があります(ただし、他の属性に索引を付けると、評価が必要なオブジェクトを減らすことができます)。最後に、Coherenceにはコストベース・オプティマイザ(CBO)が用意されており、問合せの効率化を支援するトレースおよび実行レポートもサポートされます。

ノート:

フィルタ・インタフェースを実装するすべてのクラスは、オブジェクトのシリアル化可能な状態のみに基づくようにhashCode()およびequals()メソッドを明示的に実装する必要があります。これは、フィルタがシリアル化され、ネットワーク経由で転送され、最終的にマップ・キーとして使用される、ObservableMap.addMapListenerおよびその他の類似の場所でフィルタを使用する場合に特に重要です。ValueExtractor実装でも同様に有効です。

問合せの概念

問合せ処理の概念は、ValueExtractorインタフェースに基づいています。値エクストラクタは、指定されたオブジェクトから属性を抽出し、問合せ(および同様に索引付け)するために使用されます。ほとんどの開発者に必要なのは、このインタフェースのReflectionExtractor実装のみです。実装は、リフレクションを使用してメソッド名(通常はgetterメソッド)を参照することにより、値オブジェクトから属性を抽出します。たとえば:
ValueExtractor extractor = new ReflectionExtractor("getName");

toString()のようなObjectメソッドをはじめとする任意のvoid引数メソッドを使用できます(プロトタイプ/デバッグに有用)。索引には、従来のフィールド索引(オブジェクトのフィールドに付けられた索引)または機能ベースの索引(仮想のオブジェクト属性に付けられた索引)を使用できます。たとえば、クラスにフィールド・アクセッサgetFirstNameおよびgetLastNameがある場合、そのクラスではこれらの名前を連結する関数getFullNameを定義し、その関数に索引を付けることができます。問合せ索引の使用を参照してください。

getName属性を持つオブジェクトが含まれているキャッシュに問合せを実行するには、Filterを使用する必要があります。フィルタには、指定したオブジェクトが基準を満たしているかどうかを判別するメソッドが1つあります。

Filter filter = new EqualsFilter(extractor, "Bob Smith");

フィルタには、メソッド名を受け入れてReflectionExtractorを内部で構成する、便利なコンストラクタも含まれています。

Filter filter = new EqualsFilter("getName", "Bob Smith");

次の例は、特定のフィルタ基準を満たすキャッシュ・エントリを選択するルーチンを示しています。

for (Iterator iter = cache.entrySet(filter).iterator(); iter.hasNext(); )
    {
    Map.Entry entry = (Map.Entry)iter.next();
    Integer key = (Integer)entry.getKey();
    Person 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で適用できる最適化が著しく制限されるので注意してください。

問合せの実行

Coherenceでは、com.tangosol.util.filterパッケージに多数の事前作成フィルタが含まれています。例22-1は、問合せを作成してGreaterEqualsFilterフィルタを使用する方法を示しています。

例22-1 フィルタを指定したキャッシュの問合せ

Filter filter = new GreaterEqualsFilter("getAge", 18);

for (Iterator iter = cache.entrySet(filter).iterator(); iter.hasNext(); )
    {
    Map.Entry entry = (Map.Entry) iter.next();
    Integer key = (Integer) entry.getKey();
    Person person = (Person) entry.getValue();
    System.out.println("key=" + key + " person=" + person);
    }

ノート:

ニア・キャッシュを通じて問合せを実行することは可能ですが、問合せではニア・キャッシュのフロント部分が使用されません。問合せでニア・キャッシュを使用する場合は、次のシーケンスを使用するのが最適のアプローチです。

Set setKeys = cache.key set(filter);
Map mapResult = cache.getAll(setKeys);

フィルタ結果の効率的な処理

大量のデータ・セットをバッチで問い合せても、ヒープ領域不足を防ぐことができます。
例22-2は、大きなデータ・セットを使用する場合に問合せ結果を処理するパターンを示しています。この例では、フィルタに一致するエントリのすべてのキーが返されますが、一度にキャッシュから取得されるエントリ数はBUFFER_SIZE(この例では100)のみです。

ノート:

LimitFilter APIは、次の例と同様に、結果を別々に処理できます。ただし、LimitFilterは、ユーザー・インタフェースのように結果がページングされるシナリオに適しています。問合せ結果のデータをすべて処理する場合には効率的な方法ではありません。

例22-2 問合せ結果のバッチ処理

public static void performQuery()
    {
    NamedCache c = CacheFactory.getCache("test");

    // Search for entries that start with 'c'
    Filter query = new LikeFilter(IdentityExtractor.INSTANCE, "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)

例22-3は、索引の作成方法を示しています。

例22-3 索引を作成するサンプル・コード

NamedCache cache = CacheFactory.getCache("MyCache");
ValueExtractor extractor = new ReflectionExtractor("getAttribute");
cache.addIndex(extractor, 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はIndexAwareExtractorcreateIndexdestroyIndexをコールします。IndexAwareExtractorは、createIndexdestroyIndexに渡される、エクストラクタと索引を関連付けるMapを保持します。

条件付き索引の使用

条件付き索引はカスタム索引であり、上述のとおりMapIndexインタフェースとIndexAwareExtractorインタフェースの両方を実装し、関連付けられたフィルタを使用してエントリに索引を付ける必要があるかどうかを評価します。エントリの抽出された値は、そのフィルタがtrueに評価されている場合、索引に追加されます。実装クラスは、それぞれConditionalIndexConditionalExtractorです。

ConditionalIndexConditionalExtractorによって作成されます。ConditionalIndexで使用されるフィルタとエクストラクタは、ConditionalExtractorで設定され、QueryMap.addIndexコールの間にConditionalIndexコンストラクタに渡されます。

ConditionalExtractorは、ConditionalIndexの作成のみに使用されるIndexAwareExtractor実装です。基礎となるValueExtractorは、索引の作成時の値の抽出に使用され、指定された索引マップで作成されたConditionalIndexに関連付けられているエクストラクタです。値を抽出するConditionalExtractorの使用はサポートされていません。たとえば:

ValueExtractor extractor = new ReflectionExtractor("getLastName");
Filter filter = new NotEqualsFilter("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 = new GreaterEqualsFilter("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では、コレクションや配列を含めた複数値属性の索引付けおよび問合せがサポートされています。オブジェクトに索引を付けるときには、そのオブジェクトが複数値タイプかどうかがチェックされてから、シングルトンでなくコレクションとして索引が付けられます。このようなコレクションに対する問合せには、ContainsAllFilterContainsAnyFilterおよび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 = new ContainsAllFilter("getWords", searchTerms);

Set entrySet = cache.entrySet(filter);

// iterate through the search results
// ...

連鎖エクストラクタの使用

ChainedExtractor実装を使用すると、ゼロ引数(アクセッサ)メソッドの連鎖起動が可能となります。次の例では、まずエクストラクタがリフレクションを使用して、キャッシュされているPersonオブジェクトごとにgetName()をコールします。その後、リフレクションを使用して、返されるStringlengthメソッドをコールします。
ValueExtractor extractor = new ChainedExtractor("getName.length");

このエクストラクタを問合せに渡し、(たとえば)名前が10文字以内の人をすべて、問合せで選択することができます。メソッドの起動は、getName.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オプションを使用して問合せ結果の再評価をスキップできます。

問合せのコストと効果の評価

問合せ内の各フィルタの見積もりコストと実際の効果をそれぞれ表示するために、問合せの実行計画レコードおよび問合せトレース・レコードを作成します。レコードは、Coherenceがどのように問合せを実行しているかを評価し、なぜ問合せが適切に実行されていないのか、またはどう修正すれば問合せをより適切に実行できるかを判断するために使用されます。問合せベースの統計の表示の詳細は、Oracle Coherenceの管理StorageManagerMBeanも参照してください。

この項には次のトピックが含まれます:

問合せレコードの作成

com.tangosol.util.aggregator.QueryRecorderクラスは、特定のフィルタの実行レコードまたはトレース・レコードを生成します。このクラスは、クラスタ内のすべてのノードを問い合せ、結果を集約する機能を持つ並列アグリゲータの実装です。このクラスは、問合せ内のフィルタに関する見積もりコストを示すEXPLAINレコードと、問合せ内の各フィルタの実際の効果を示すTRACEレコードの2つのレコード・タイプをサポートしています。

問合せレコードを作成するには、RecordTypeパラメータを指定する新しいQueryRecorderインスタンスを作成します。aggregateメソッドのパラメータとしてテストされるインスタンスとフィルタを含めます。次の例では、説明レコードを作成しています。

NamedCache cache = CacheFactory.getCache("mycache");
cache.addIndex(new ReflectionExtractor("getAge"), true, null);

AllFilter filter = new AllFilter(new Filter[]
   {
    new OrFilter(
       new EqualsFilter(new ReflectionExtractor("getAge"), 16),
       new EqualsFilter(new ReflectionExtractor("getAge"), 19)),
    new EqualsFilter(new ReflectionExtractor("getLastName"), "Smith"),
    new EqualsFilter(new ReflectionExtractor("getFirstName"), "Bob"),
    });
 
QueryRecorder agent = new QueryRecorder(RecordType.EXPLAIN);
Object resultsExplain = cache.aggregate(filter, agent);
 
System.out.println("\n" + resultsExplain + "\n");

トレース・レコードを作成するには、RecordTypeパラメータをTRACEに変更します。

QueryRecorder agent = new QueryRecorder(RecordType.TRACE);

問合せレコードの解析

問合せレコードは、問合せを構成するフィルタと索引の評価に使用されます。実行計画レコードは、フィルタの適用に関連する見積もりコストの評価に使用されます。トレース・レコードは、キー・セットの削減に対するフィルタの効果の評価に使用されます。

この項では、サンプルの実行計画レコードおよびサンプルのトレース・レコードを示し、レコードの読取りおよび解析方法について説明します。レコードは、記憶域が有効な4つのノードのクラスタにある1500エントリのサンプル問合せに基づいています。問合せは、16歳または19歳で、姓がSmith、名がBobである人を検索するフィルタで構成されています。最後に、getAgeに対する索引が追加されます。問合せレコードの実行の例を参照してください。

NamedCache cache = CacheFactory.getCache("mycache");
cache.addIndex(new ReflectionExtractor("getAge"), true, null);

AllFilter filter = new AllFilter(new Filter[]
   {
    new OrFilter(
       new EqualsFilter(new ReflectionExtractor("getAge"), 16),
       new EqualsFilter(new ReflectionExtractor("getAge"), 19)),
    new EqualsFilter(new ReflectionExtractor("getLastName"), "Smith"),
    new EqualsFilter(new ReflectionExtractor("getFirstName"), "Bob"),
    });

この項には次のトピックが含まれます:

問合せの実行計画レコード

問合せの実行レコードは、問合せ操作の一部としてフィルタの評価の見積もりコストを提供します。コストでは、フィルタで索引を使用できるかどうかが考慮されます。コスト評価は、問合せの実行時にフィルタが適用される順序の決定に使用されます。索引を使用するフィルタは、コストが最も低く、最初に適用されます。

例22-4では、一般的な問合せ実行計画レコードを示します。レコードには、問合せ内の各フィルタを評価するための実行計画表、およびフィルタで使用できる各索引が記載されている索引ルックアップ表が含まれています。これらの列は、次のように示されます。

  • 名前 – この列は、問合せ内の各フィルタの名前を示します。複合フィルタの場合は、複合フィルタ内の各フィルタの情報が示されます。

  • 索引 – この列は、指定のフィルタで索引を使用できるかどうかを示します。索引が見つかった場合、表示された番号が索引ルックアップ表の索引番号に対応します。例では、getAge()に対して、順序付けられた単純なマップ索引(0)が見つかりました。

  • コスト – この列は、フィルタの適用に関する見積もりコストを示します。索引が使用可能な場合、コストは1となります。索引の適用操作では、索引コンテンツに1回だけアクセスすれば済むので、値1が使用されます。例では、記憶域が有効な4つのクラスタ・メンバーがあり、したがって、コストには4つのメンバーすべての索引へのアクセスが反映されます。索引が存在しない場合、コストはEVAL_COST * number of keysとして計算されます。EVAL_COST値は、定数値(1000)です。これは、フィルタを使用してキー・セットを削減するために全体スキャンを実行する相対的なコストを示します。例では、評価が必要な1500のキャッシュ・エントリがあります。索引付きのエントリへの問合せは常に、索引付きでないエントリと比べると安価ですが、効果が保証されているわけではありません。

例22-4のレコードは、getAge()の等価フィルタはコストが低いことを示しています。これは、そのフィルタが、関連付けられた索引を持ち、getLastName()およびgetFirstName()の前に適用されるためです。ただし、getAge()フィルタは、安価ですが、すべてのエントリが1619のいずれかであり、BobおよびSmithに一致するエントリがほとんどない場合、あまり効果的ではないこともあります。この場合、getLastName()およびgetFirstName()に索引を追加するほうがより効果的です。さらに、索引でキー・セットの削減がうまく行われていない場合、索引の作成に関連するコスト(主にメモリー消費)は無駄になります。

例22-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
問合せのトレース・レコード

問合せのトレース・レコードは、問合せ操作の一部としてフィルタの評価の実コストを提供します。コストでは、フィルタで索引を使用できるかどうかが考慮されます。問合せが実際に実行され、キー・セットの削減に関する各フィルタの効果が示されます。

例22-5では、一般的な問合せトレース・レコードを示します。レコードには、問合せ内の各フィルタの効果を示すトレース表、およびフィルタで使用できる各索引が記載されている索引ルックアップ表が含まれています。これらの列は、次のように示されます。

  • 名前 – この列は、問合せ内の各フィルタの名前を示します。複合フィルタの場合は、複合フィルタ内の各フィルタの情報が示されます。

  • 索引 – この列は、指定のフィルタで索引を使用できるかどうかを示します。索引が見つかった場合、表示された番号が索引ルックアップ表の索引番号に対応します。例では、getAge()に対して、順序付けられた単純なマップ索引(0)が見つかりました。

  • 有効性 – この列は、各フィルタの結果として実際に削減されたキー・セットの量を示します。値は、prefilter_key_set_size | postfilter_key_set_sizeとして示され、パーセンテージでも表されます。prefilter_key_set_size値は、フィルタの評価または索引の適用前のキー・セット・サイズを表します。postfilter_key_set_size値は、フィルタの評価または索引の適用後に残っているキー・セット・サイズを表します。複合フィルタ・エントリの場合、この値は、含まれているフィルタ全体の結果です。索引に基づいてキー・セット・サイズを削減することができなくなると、結果のキー・セットはデシリアライズされ、索引フィルタ以外のフィルタが適用されます。

  • 期間 – この列は、フィルタの評価または索引の適用にかかった時間をミリ秒で示します。値0は、記録された時間がレポートのしきい値を下回ったことを示します。次の例で、63ミリ秒は、最初のフィルタgetLastName()で発生したキー・セットのデシリアライズのみが必要な結果です。

例22-5のレコードは、1500エントリを、姓がSmithで名がBob、年齢が16歳か19歳の100件のエントリに絞り込むのに約63ミリ秒かかったことを示しています。1500エントリのキー・セットは、まず、getAge()の索引を使用して300に削減されました。その結果としての300エントリ(索引を使用してこれ以上削減することはできません)は、デシリアライズされ、getLastName()に基づいて150エントリに削減され、getFirstName()に基づいて100に削減されました。この例では、getAge()の索引は、実質1200エントリのキー・セットを削減できたので、消費したリソースに値する価値があります。getLastNameおよびgetFirstNameの索引は、問合せ全体のパフォーマンスを高めますが、索引の作成に必要な追加のリソースに値する価値がない場合もあります。

例22-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オブジェクトとともにロードし、属性の索引を作成し、問合せを実行し、クラスが終了する前にコンソールに出力される問合せ実行計画レコードと問合せトレース・レコードの両方を作成します。

例22-6 問合せレコードの例

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.util.Filter;
import com.tangosol.util.aggregator.QueryRecorder;
import static com.tangosol.util.aggregator.QueryRecorder.RecordType;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.AllFilter;
import com.tangosol.util.filter.EqualsFilter;
import com.tangosol.util.filter.OrFilter;
import java.io.Serializable;
import java.util.Properties;
 
public class QueryRecordExanple
   {
   public static void main(String[] args) {
   
      testExplain();
      testTrace();
   }
   
   public static void testExplain()
   {
      NamedCache cache = CacheFactory.getCache("mycache");
      cache.addIndex(new ReflectionExtractor("getAge"), true, null);
      PopulateCache(cache);
        
      AllFilter filter = new AllFilter(new Filter[]
      {
         new OrFilter(
            new EqualsFilter(new ReflectionExtractor("getAge"), 16),
            new EqualsFilter(new ReflectionExtractor("getAge"), 19)),
         new EqualsFilter(new ReflectionExtractor("getLastName"), "Smith"),
         new EqualsFilter(new ReflectionExtractor("getFirstName"), "Bob"),
      });
 
      QueryRecorder agent = new QueryRecorder(RecordType.EXPLAIN);
      Object resultsExplain = cache.aggregate(filter, agent);
      System.out.println("\nExplain Plan=\n" + resultsExplain + "\n");
   } 

   public static void testTrace()
   {
      NamedCache cache = CacheFactory.getCache("hello-example");
      cache.addIndex(new ReflectionExtractor("getAge"), true, null);
      PopulateCache(cache);
        
      AllFilter filter = new AllFilter(new Filter[]
      {
         new OrFilter(
            new EqualsFilter(new ReflectionExtractor("getAge"), 16),
            new EqualsFilter(new ReflectionExtractor("getAge"), 19)),
         new EqualsFilter(new ReflectionExtractor("getLastName"), "Smith"),
         new EqualsFilter(new ReflectionExtractor("getFirstName"), "Bob"),
      });

      QueryRecorder agent = new QueryRecorder(RecordType.TRACE);
      Object resultsExplain = cache.aggregate(filter, agent);
      System.out.println("\nTrace =\n" + resultsExplain + "\n");
   }
    
   private static void PopulateCache(NamedCache cache)
      {
         for (int i = 0; i < 1500; ++i)
            {
               Person person = new Person(i % 3 == 0 ? "Joe" : "Bob",
                  i % 2 == 0 ? "Smith" : "Jones", 15 + i % 10);
               cache.put("key" + i, person);
            }
      } 
 
   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 String m_sFirstName;
      private String m_sLastName;
      private int m_nAge;
   } 
}