Cloud Datastore インデックス

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

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

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

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

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

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

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

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

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

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

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

SELECT * FROM Person WHERE LastName = "Smith"
                       AND Height < 72
                  ORDER BY Height DESC

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

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

SELECT * FROM Person WHERE LastName = "Jones"
                       AND Height < 63
                     ORDER BY Height DESC

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

SELECT * FROM Person WHERE LastName = "Friedkin"
                       AND FirstName = "Damian"
                     ORDER BY Height ASC

SELECT * FROM Person WHERE LastName = "Blair"
                  ORDER BY FirstName, Height ASC

インデックスの構成

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

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

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

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

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

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

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

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

2 つのエンティティに名前が同じで値の型が異なるプロパティが存在する場合、エンティティはそのプロパティのインデックスにより、まず値の型で並べ替えられ、次にそれぞれの型に適した第 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」に対してインデックスを作成すると、「a および b」インデックスに対するインデックス エントリは作成されず、WHERE a="bike" and b="red" というクエリは機能しません。Cloud Datastore に「a および b」インデックスのエントリを作成させるには、ab の両方が「インデックスあり」でなければなりません。

プロパティを「インデックスなし」として宣言するには、struct のフィールドタグnoindex を設定します。

type Person struct {
	Name string
	Age  int `datastore:",noindex"`
}

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

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

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

インデックスの制限

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

上記で説明したとおり、Cloud Datastore はすべてのエンティティのすべてのプロパティについて、事前定義されたインデックスにエントリを作成します。ただし、[]byte フィールドと、明示的にインデックスなしと宣言したものは除きます。また、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 のエンティティに関連付けられた値の数を示します)。たとえば、次のコードを見てください。
type Widget struct {
	X    []int
	Y    []string
	Date time.Time
}

func f(ctx context.Context) {
	e2 := &Widget{
		X:    []int{1, 2, 3, 4},
		Y:    []string{"red", "green", "blue"},
		Date: time.Now(),
	}

	k := datastore.NewIncompleteKey(ctx, "Widget", nil)
	if _, err := datastore.Put(ctx, k, e2); err != nil {
		// Handle error.
	}
}

上記のコードが作成するエンティティには、プロパティ 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 オペレーションによってインデックスのエントリ数またはサイズの制限が超過した場合、そのオペレーションはエラーが発生して失敗します。どの制限を超過し、どのカスタム インデックスが原因となったのかは、エラーのテキスト("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
    

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

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

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

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