Cloud Datastore インデックス

注: 新しいアプリケーションを作成する際には NDB クライアント ライブラリを使用することを強くおすすめします。NDB クライアント ライブラリには、Memcache API によるエンティティの自動キャッシュなど、DB クライアント ライブラリにはないメリットがあります。古い DB クライアント ライブラリを使用している場合は、DB から NDB への移行ガイドをお読みください。

App Engine は、エンティティの各プロパティに単純なインデックスを事前に定義します。さらに、App Engine アプリケーションでは独自のインデックスを index.yaml という名前のインデックス構成ファイルに定義することもできます。開発用サーバーは、既存のインデックスでは実行できないクエリが出現したときに、このファイルに自動的に候補を追加します。アプリケーションをアップロードする前にこのファイルを編集して、インデックスを手動で調整できます。

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

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

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

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

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

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

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

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

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

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

たとえば、次のクエリ(GQL で記述)について考えてみます。

SELECT * FROM Person WHERE last_name = "Smith"
                       AND height < 72
                  ORDER BY height DESC

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

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

SELECT * FROM Person WHERE last_name = "Jones"
                       AND height < 63
                     ORDER BY height DESC

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

SELECT * FROM Person WHERE last_name = "Friedkin"
                       AND first_name = "Damian"
                     ORDER BY height ASC

SELECT * FROM Person WHERE last_name = "Blair"
                  ORDER BY first_name, height ASC

インデックスの設定

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

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

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

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

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

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

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

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

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

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

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

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

注: プロパティが複数のプロパティで構成されるインデックスに含まれている場合、そのプロパティをインデックスなしとして設定すると、複合インデックスでそのプロパティはインデックス付けされません。

たとえば、エンティティにプロパティ 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 の両方が「インデックスあり」でなければなりません。

property を「インデックスなし」として宣言するには、プロパティ コンストラクタindexed=False を設定します。

class Person(db.Model):
  name = db.StringProperty()
  age = db.IntegerProperty(indexed=False)

プロパティを「インデックスあり」に戻すには、indexed=True を設定してコンストラクタを再度呼び出します。

class Person(db.Model):
  name = db.StringProperty()
  age = db.IntegerProperty(indexed=True)

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

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

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

インデックスの制限

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

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

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

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

次のクエリをみてください。

SELECT * FROM Widget WHERE x=1 AND y=2 ORDER BY date

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

indexes:
- kind: Widget
  properties:
  - name: x
  - name: y
  - name: date
このインデックスには、エンティティごとに合計 |x| * |y| * |date| 個のエントリが必要になります(|x| はプロパティ x のエンティティに関連付けられた値の数を示します)。たとえば、次のコードを見てください。
class Widget(db.Expando):
  pass

e2 = Widget()
e2.x = [1, 2, 3, 4]
e2.y = ['red', 'green', 'blue']
e2.date = datetime.datetime.now()
e2.put()

上記のコードが作成するエンティティには、プロパティ 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 がインデックス爆発を検出し、代替のインデックスを推奨する場合があります。ただし、それ以外のすべての状況(この例で定義されているクエリなど)では、インデックス爆発が生成されることになります。この場合、インデックス構成ファイルのインデックスを手動で次のように構成することにより、インデックス爆発を回避できます。

indexes:
- kind: Widget
  properties:
  - name: x
  - name: date
- kind: Widget
  properties:
  - name: y
  - name: date
これにより、必要なエントリの数は、12 ではなく、(|x| * |date| + |y| * |date|)、つまり 7 つのみに削減されます。

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

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

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

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

  2. index.yaml が配置されているディレクトリから次のコマンドを実行して、そのインデックスを Cloud Datastore から削除します。

    gcloud datastore cleanup-indexes index.yaml
    
  3. エラーの原因を解決します。次に例を示します。

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

  5. index.yaml が配置されているディレクトリから次のコマンドを実行して、Cloud Datastore にインデックスを作成します。

    gcloud datastore create-indexes index.yaml
    

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

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

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

Python の App Engine スタンダード環境