Cloud Datastore インデックス

App Engine は、エンティティの各プロパティに単純なインデックスを事前に定義します。さらに App Engine アプリケーションでは、datastore-indexes.xml という名前のインデックス構成ファイルでカスタム インデックスを定義できます。このファイルは、アプリケーションの /war/WEB-INF/appengine-generated ディレクトリ内に生成されます。開発用サーバーは、既存のインデックスでは実行できないクエリが出現したときに、このファイルに自動的に候補を追加します。アプリケーションをアップロードする前にこのファイルを編集して、インデックスを手動で調整できます。

インデックスとクエリの詳細については、インデックスの選択と高度な検索をご覧ください。

注: インデックス ベースのクエリ メカニズムはさまざまなクエリをサポートしているため、ほとんどのアプリケーションに適しています。ただし、他のデータベース テクノロジーでは一般にサポートされているいくつかの種類のクエリがサポートされていません。特に Cloud Datastore のクエリエンジンでは、結合と集計クエリがサポートされていない点に注意してください。Cloud Datastore クエリの制限については、データストア クエリのページをご覧ください。

インデックスの定義と構造

インデックスは、特定の種類のエンティティのプロパティを、各プロパティの対応する順序(昇順または降順)で並べたリストに対して定義されます。また、祖先クエリで使用できるように、オプションでエンティティの祖先を含めることもできます。

インデックス テーブルには、インデックス定義で指定したすべてのプロパティに対応する列が含まれています。テーブルの各行は Cloud Datastore 内のエンティティを表し、これがインデックスに基づくクエリの結果となります。インデックスには、インデックスで使用されているすべてのプロパティに対応するインデックス付けされた値セットを持つエンティティのみが含められます。あるエンティティが値を持たないプロパティがインデックス定義で参照されている場合、そのエンティティはインデックスに含まれないため、インデックスに基づくクエリで結果として返されることはありません。

注: Cloud Datastore では、プロパティを持たないエンティティと、プロパティに null 値(null)が指定されているエンティティが区別されます。エンティティのプロパティに明示的に null 値を割り当てた場合、そのエンティティは、そのプロパティを参照するクエリの結果に含められる可能性があります。

注: 複数のプロパティで構成されているインデックスがある場合、個々のプロパティを「インデックスなし」に設定しないでください。

インデックス テーブルの行は祖先の順序で並べられた後に、インデックス定義で指定されたプロパティ値の順序で並べられます。クエリを最も効率的に実行できる「完璧なインデックス」とは、次のプロパティをこの順序で定義したものです。

  1. 等式フィルタで使用されるプロパティ
  2. 不等式フィルタ(プロパティを 1 つしか使用しないもの)で使用されるプロパティ
  3. 並べ替えの順序で使用されるプロパティ

これにより、実行される可能性があるすべてのクエリの結果が、テーブルの連続した行に表れます。Cloud Datastore は、次のステップに従って完璧なインデックスを使用し、クエリを実行します。

  1. クエリの種類、フィルタのプロパティ、フィルタの演算子、並べ替え順序に対応するインデックスを特定します。
  2. インデックスの先頭から、クエリのフィルタ条件をすべて満たす最初のエンティティまでスキャンします。
  3. インデックスのスキャンを続行し、各エンティティを返します。スキャンは、次の条件のいずれかが満たされるまで続けられます。
    • フィルタ条件を満たさないエンティティを検出する
    • インデックスの最後に到達する
    • クエリでリクエストされた結果の最大数に達した

たとえば、次のクエリについて考えてみます。

Java 8

Query q1 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Smith"),
                new FilterPredicate("height", FilterOperator.EQUAL, 72)))
        .addSort("height", Query.SortDirection.DESCENDING);

Java 7

Query q1 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Smith"),
                new FilterPredicate("height", FilterOperator.EQUAL, 72)))
        .addSort("height", Query.SortDirection.DESCENDING);

このクエリにとっての完璧なインデックスは、Person という種類のエンティティに対するキーのテーブルであり、lastNameheight のプロパティ値に対する列を持つものです。インデックスはまず lastName の昇順で、次に height の降順で並べられます。

形式が同じでフィルタの値が異なる 2 つのクエリは、同じインデックスを使用します。たとえば、次のクエリは上記の例と同じインデックスを使用します。

Java 8

Query q2 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Jones"),
                new FilterPredicate("height", FilterOperator.EQUAL, 63)))
        .addSort("height", Query.SortDirection.DESCENDING);

Java 7

Query q2 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Jones"),
                new FilterPredicate("height", FilterOperator.EQUAL, 63)))
        .addSort("height", Query.SortDirection.DESCENDING);

形式が異なるにもかかわらず、次の 2 つのクエリでも同じインデックスを使用します。

Java 8

Query q3 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Friedkin"),
                new FilterPredicate("firstName", FilterOperator.EQUAL, "Damian")))
        .addSort("height", Query.SortDirection.ASCENDING);

Java 7

Query q3 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Friedkin"),
                new FilterPredicate("firstName", FilterOperator.EQUAL, "Damian")))
        .addSort("height", Query.SortDirection.ASCENDING);

Java 8

Query q4 =
    new Query("Person")
        .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair"))
        .addSort("firstName", Query.SortDirection.ASCENDING)
        .addSort("height", Query.SortDirection.ASCENDING);

Java 7

Query q4 =
    new Query("Person")
        .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair"))
        .addSort("firstName", Query.SortDirection.ASCENDING)
        .addSort("height", Query.SortDirection.ASCENDING);

インデックスの構成

デフォルトでは、エンティティの種類ごとに各プロパティのインデックスが自動的に事前定義されます。等式だけのクエリや単純な不等式クエリなどの多くの単純なクエリは、これらの事前定義されたインデックスで十分に実行できます。他のすべてのクエリでは、アプリケーションが必要とするインデックスを datastore-indexes.xml という名前のインデックス構成ファイルで定義する必要があります。使用可能なインデックス(事前定義されたインデックスまたはインデックス構成ファイル内で指定されているインデックス)を使って実行できないクエリをアプリケーションが実行しようとすると、そのクエリは DatastoreNeedIndexException で失敗します。

データストアは、次の形式のクエリに対するインデックスを自動的に作成します。

  • 祖先フィルタとキーフィルタだけを使用した種類のないクエリ
  • 祖先フィルタと等式フィルタだけを使用したクエリ
  • 不等式フィルタ(プロパティを 1 つしか使用しないもの)だけを使用したクエリ
  • 祖先フィルタ、プロパティに対する等式フィルタ、キーに対する不等式フィルタだけを使用したクエリ
  • フィルタがなく、プロパティに対する並べ替え順序(昇順か降順)だけを使用したクエリ

上記以外の形式のクエリには、インデックス構成ファイルでインデックスを指定する必要があります。次のようなクエリがあります。

  • 祖先フィルタと不等式フィルタを使用したクエリ
  • 1 つのプロパティに対して 1 つ以上の不等式フィルタを使用し、他のプロパティに対して 1 つ以上の等式フィルタを使用したクエリ
  • キーの降順という並べ替え順序を使用したクエリ
  • 複数の並べ替え順序を持つクエリ

インデックスとプロパティ

ここでは、インデックスについて特に考慮すべきことと、インデックスを Cloud Datastore 内のエンティティのプロパティにどのように関連付けるかに関して留意しなければならないことを説明します。

値の型が混在するプロパティ

2 つのエンティティに名前が同じで値の型が異なるプロパティが存在する場合、エンティティはそのプロパティのインデックスにより、まず値の型で並べ替えられ、次にそれぞれの型に適した順序で並べ替えられます。たとえば 2 つのエンティティそれぞれに age という名前のプロパティが存在し、一方が整数値を持つプロパティで、もう一方が文字列値を持つプロパティの場合、age プロパティを基準とした並べ替えを行うと、プロパティの値自体には関係なく常に整数値を持つエンティティのほうが文字列値を持つエンティティよりも順序が先になります。

Cloud Datastore では整数値と浮動小数点型の数値が別の型として区別されるため、このような動作について特に注意する必要があります。すべての整数値はすべての浮動小数点型の数値よりも順序が前になるため、整数値 38 を持つプロパティは浮動小数点値 37.5 を持つプロパティよりも前になります。

インデックスなしのプロパティ

フィルタリングや並べ替えを行わないことが明確なプロパティについては、インデックスなしとして宣言することで、そのプロパティのインデックス エントリを保持しないように Cloud Datastore に命令できます。これにより、Cloud Datastore 書き込みの実行数が削減され、アプリケーションの実行コストが削減されます。インデックスなしのプロパティを持つエンティティは、そのプロパティが設定されていないかのように振る舞います。インデックスなしのプロパティに対するフィルタまたは並べ替え順を指定したクエリが、そのエンティティに一致することはありません。

注: 複数のプロパティで構成されるインデックスに含まれるプロパティの 1 つをインデックスなしに設定すると、複合インデックスにインデックス付けされなくなります。

たとえば、エンティティにプロパティ ab があり、WHERE a ="bike" and b="red" というクエリを満たすインデックスを作成するとします。また、WHERE a="bike" というクエリと WHERE b="red" というクエリについては考慮しないものとします。この場合、a を「インデックスなし」に設定し、「a および b」に対してインデックスを作成すると、Cloud Datastore は「a および b」インデックスに対してインデックス エントリを作成しないため、WHERE a="bike" and b="red" というクエリは機能しません。Cloud Datastore に「a および b」インデックスのエントリを作成させるには、ab の両方が「インデックスあり」でなければなりません。

低レベルの Java Datastore API では、プロパティはそのプロパティを設定するために使用するメソッド(setProperty()setUnindexedProperty())に応じて、エンティティ単位で「インデックスあり」または「インデックスなし」として定義されます。

Java 8

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Key acmeKey = KeyFactory.createKey("Company", "Acme");

Entity tom = new Entity("Person", "Tom", acmeKey);
tom.setProperty("name", "Tom");
tom.setProperty("age", 32);
datastore.put(tom);

Entity lucy = new Entity("Person", "Lucy", acmeKey);
lucy.setProperty("name", "Lucy");
lucy.setUnindexedProperty("age", 29);
datastore.put(lucy);

Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25);

Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter);

// Returns tom but not lucy, because her age is unindexed
List<Entity> results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());

Java 7

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Key acmeKey = KeyFactory.createKey("Company", "Acme");

Entity tom = new Entity("Person", "Tom", acmeKey);
tom.setProperty("name", "Tom");
tom.setProperty("age", 32);
datastore.put(tom);

Entity lucy = new Entity("Person", "Lucy", acmeKey);
lucy.setProperty("name", "Lucy");
lucy.setUnindexedProperty("age", 29);
datastore.put(lucy);

Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25);

Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter);

// Returns tom but not lucy, because her age is unindexed
List<Entity> results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());

インデックス付けされるプロパティをインデックス付けなしに変更するには、値を setUnindexedProperty() でリセットし、インデックス付けなしからインデックス付けありに変更するには値を setProperty() でリセットします。

ただし、プロパティをインデックスなしからインデックスありに変更しても、変更前に作成されていたエンティティには適用されない点に注意してください。そのようなプロパティに対してフィルタリングを行うクエリは、そうした既存のエンティティを返しません。それらはエンティティの作成時にクエリのインデックスに書き込まれていなかったためです。このようなエンティティに今後実行するクエリでアクセスできるようにするには、Cloud Datastore にエンティティを書き込み直して適切なインデックスに含まれるようにする必要があります。つまり、既存のエンティティについては次の手順を行う必要があります。

  1. Cloud Datastore からエンティティを取得します(get)。
  2. Cloud Datastore にエンティティを書き込みます(put)。

同様に、プロパティをインデックスありからインデックスなしに変更した場合も、この変更が適用されるのは、変更後に Cloud Datastore に書き込まれたエンティティだけです。そのプロパティを持つ既存のエンティティのインデックス エントリは、エンティティが更新または削除されるまで存在し続けます。望ましくない結果を回避するには、そのような(インデックスなしに変更した)プロパティでフィルタリングや並べ替えを行うすべてのクエリのコードを消去する必要があります。

インデックスの制限

データストアは、単一のエンティティに関連付けられるインデックス エントリの数と合計サイズに対して制限を定めています。これらの制限は十分な余裕があるため、ほとんどのアプリケーションは影響を受けません。ただし、場合によっては制限に達することもあります。

前述のとおり、Cloud Datastore では、長いテキスト文字列(Text)、長いバイト文字列(Blob)、埋め込みエンティティ(EmbeddedEntity)、「インデックスなし」と明示的に宣言したエンティティを除く、すべてのエンティティのすべてのプロパティに対して、事前定義されたインデックスにエントリが作成されます。また、datastore-indexes.xml 構成ファイル内で宣言した追加のカスタム インデックスにプロパティを含めることもできます。エンティティにリスト プロパティが存在しない場合、そのようなエンティティはカスタム インデックスごとに最大で 1 つのエントリ(非祖先インデックスの場合)、またはエンティティの祖先ごとに 1 つのエントリ(祖先インデックスの場合)を持つことになります。このようなインデックス エントリは、プロパティの値が変化するたびに更新しなければなりません。

エンティティごとに 1 つの値を持つプロパティについては、そのプロパティの事前定義インデックスにエンティティごとに値を 1 回保存するだけで済みます。ただし、そのような単一値のプロパティを大量に持つエンティティの場合は、インデックスのエントリ数またはサイズの制限を超過する可能性があります。同様に、同じプロパティに対して複数の値を持つエンティティは値ごとに個別のインデックス エントリが必要になるため、値の数が大量に存在する場合は、やはりエントリの制限を超過する可能性があります。

複数のプロパティが存在するエンティティで、それぞれのプロパティが複数の値を取る場合、状況はさらに悪化します。そのようなエンティティに対処するには、可能性のあるすべてのプロパティ値の組み合わせに対応するエントリをインデックスに含める必要があります。カスタム インデックスが複数のプロパティを参照し、それぞれのプロパティが複数の値を持つ場合、組み合わせ数が爆発的に増加する可能性があり、比較的少数のプロパティ値しか持たないエンティティのエントリが大量に必要になります。このようなインデックス爆発により大量のインデックス エントリを更新しなければならなくなるため、データストアへのエンティティの書き込みコストが大幅に増えます。さらに、エンティティのインデックス エントリ数またはサイズ制限を簡単に超えてしまう可能性もあります。

次のクエリを検討します。

Java 8

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.EQUAL, 1),
                new FilterPredicate("y", FilterOperator.EQUAL, 2)))
        .addSort("date", Query.SortDirection.ASCENDING);

Java 7

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.EQUAL, 1),
                new FilterPredicate("y", FilterOperator.EQUAL, 2)))
        .addSort("date", Query.SortDirection.ASCENDING);

この場合、SDK は次のインデックスを提案します。

Java 8

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget" ancestor="false" source="manual">
    <property name="x" direction="asc"/>
    <property name="y" direction="asc"/>
    <property name="date" direction="asc"/>
  </datastore-index>
</datastore-indexes>

Java 7

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget" ancestor="false" source="manual">
    <property name="x" direction="asc"/>
    <property name="y" direction="asc"/>
    <property name="date" direction="asc"/>
  </datastore-index>
</datastore-indexes>
このインデックスには、エンティティごとに合計 |x| * |y| * |date| 個のエントリが必要になります(|x| はプロパティ x のエンティティに関連付けられた値の数を示します)。たとえば、次のコードを見てください。

Java 8

Entity widget = new Entity("Widget");
widget.setProperty("x", Arrays.asList(1, 2, 3, 4));
widget.setProperty("y", Arrays.asList("red", "green", "blue"));
widget.setProperty("date", new Date());
datastore.put(widget);

Java 7

Entity widget = new Entity("Widget");
widget.setProperty("x", Arrays.asList(1, 2, 3, 4));
widget.setProperty("y", Arrays.asList("red", "green", "blue"));
widget.setProperty("date", new Date());
datastore.put(widget);

上記のコードが作成するエンティティには、プロパティ x の値が 4 つ、プロパティ y の値が 3 つあり、date に現在の日付が設定されます。この場合、全とおりの組み合わせのプロパティ値に対応するには、12 個のインデックス エントリが必要になります。

(1, "red", <now>) (1, "green", <now>) (1, "blue", <now>)

(2, "red", <now>) (2, "green", <now>) (2, "blue", <now>)

(3, "red", <now>) (3, "green", <now>) (3, "blue", <now>)

(4, "red", <now>) (4, "green", <now>) (4, "blue", <now>)

同じプロパティが複数回繰り返し使われている場合、Cloud Datastore がインデックス爆発を検出し、代替のインデックスを推奨する場合があります。ただし、それ以外のすべての状況(この例で定義されているクエリなど)では、インデックス爆発が生成されることになります。この場合、インデックス構成ファイルのインデックスを手動で次のように構成することにより、インデックス爆発を回避できます。

Java 8

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget">
    <property name="x" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
  <datastore-index kind="Widget">
    <property name="y" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
</datastore-indexes>

Java 7

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget">
    <property name="x" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
  <datastore-index kind="Widget">
    <property name="y" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
</datastore-indexes>
これにより、必要なエントリの数は、12 ではなく、(|x| * |date| + |y| * |date|)、つまり 7 つみのみに削減されます。

(1, <now>) (2, <now>) (3, <now>) (4, <now>)

("red", <now>) ("green", <now>) ("blue", <now>)

put オペレーションでインデックスのエントリ数またはサイズの制限が超過した場合、そのオペレーションは IllegalArgumentException で失敗します。例外のテキストには、超過した制限("Too many indexed properties" または "Index entries too large")および原因のカスタム インデックスが示されます。構築時にエンティティの制限を超える新しいインデックスを作成すると、そのインデックスに対するクエリは失敗し、そのインデックスは GCP Console では Error 状態で表示されます。Error 状態のインデックスを解決する手順は次のとおりです。

  1. datastore-indexes.xml ファイルから Error 状態のインデックスを削除します。

  2. datastore-indexes.xml があるディレクトリから次のコマンドを実行して、そのインデックスを Cloud Datastore から削除します。

    appcfg.sh vacuum_indexes [YOUR_APP_DIR]
    
  3. エラーの原因を解決します。次に例を示します。

    • インデックスの定義および対応するクエリを再構成する。
    • インデックス爆発の原因となっているエンティティを削除する。
  4. インデックスを datastore-indexes.xml ファイルに追加します。

  5. datastore-indexes.xml があるディレクトリから次のコマンドを実行して、Cloud Datastore にインデックスを作成します。

    appcfg.sh update_indexes datastore-indexes.xml
    

インデックス爆発は、リスト プロパティを使用するカスタム インデックスが必要なクエリを避けることで回避できます。すでに説明したように、このようなクエリとしては複数の並べ替え順序を持つクエリや、等式フィルタと不等式フィルタが混在するクエリなどが挙げられます。

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Java 8 の App Engine スタンダード環境