GAE Datastore の Single-property Index と Composite Index は全く違うものと理解する

Datastore の Single-property Index と Composite Index はどちらも似たようなものだと思っていたのですが、実際のところはかなり違う性質をそれぞれ持っていることが段々とわかってきたので、現時点の自分の理解をメモしておきます。 恐らく Cloud Datastore にも該当する話だと思います。

Datastore のインデックスの種類

Datastore には2種類のインデックスが存在します。

  • Single-property Index (Built-in Index とも呼ばれる)
  • Composite Index (Custom Index とも呼ばれる)

このうち Single-property Index はデフォルトで全てのプロパティに有効になっているインデックスであり、Entity を保存した段階でそれぞれのプロパティに対応するインデックスが自動で作られます。 Composite Index はあらかじめ設定ファイルで定義しておく必要があり、それによって複数のプロパティを組み合わせた複合インデックスを作ることが出来ます。

両インデックスの違い

一番大きな違いだと思うのが、Single-property Index が Entity 単位にインデックスの ON/OFF をコントロールできる一方、Composite Index は Datastore の Kind 毎に ON/OFF されるという点です。 この性質の違いがいくつかの挙動の違いを生みます。

1. Single-property Index を途中からつけても、既存の Entity には効果がない

これはドキュメントに書かれてる内容ですが、今まで明示的に Unindex としていたプロパティに途中からインデックスを定義しようとしても、既存の Entity に対応するインデックスは作成されません。 そのため、古い Entity は MapReduce などを用いて fetch → put と再保存してインデックスを構築し直す必要があります。 これは Entity 単位にインデックスの ON/OFF が制御されるという性質上しょうがないものかと思います。

逆に Composite Index は All or Nothing なので、途中から定義したとしても既存を含む全ての Entity に作られることになります。

2. Single-property Index がないと Composite Index にエントリが追加されない

これは(自分の知る限り)アンドキュメントな内容ですが、Composite Index を設定ファイルで定義したとしても、そこで使用しているプロパティの Single-property Index が一つでも存在しない場合、Composite Index にはエントリが追加されません。 実際にその複合インデックスを使うようなクエリを投げても、単純にクエリに引っかからないような挙動になり、エラーもでないのでかなりハマります。 なぜこのような挙動になっているのか原理や理由はわかっていませんが、Composite Index は全ての Entity が対象となるので、一部の Entity をインデックスに追加しないようにするためにそういう機構があるのでしょうか。

Single-property Index は必ず定義しておくべき

上記のようなことがあるため、特別な理由がない限り Single-property Index は定義しておくべきだと考えています。 以前はインデックスの作成操作も Datastore write ops に含まれてしまっていたので必要のないインデックスはなるべく避ける傾向にあったと思いますが、今はそれらが Entity 単位の Quota になったため、あまり気にしなくていい感じになりました。 Single-property Index が既に存在していると、あとから Composite Index も張りやすくなりますし、なにより Datastore のコンソール画面からクエリを投げられるのでデバッグがやりやすくなると思います。

とはいってもインデックスの作成はその分ストレージを消費しますし、インデックスに追加されるまでの時間もインデックス数に応じて増えていくと思うので、他のインデックス追加に影響を及ぼして参照整合性が弱くなってしまうのを避けたい、といった場合には控えたほうが良さそうです。 (cf. Anti Pattern #2: Too Many Indexes - Balancing Strong and Eventual Consistency with Google Cloud Datastore)

一点実際のプログラミング時に注意が必要なのは、GAE/Java + Objectify で開発していると明示的に @Index アノテーションをプロパティに付加しておかないと setUnindexedProperty でプロパティが定義されてしまい、インデックス=OFFとなってしまう点です。 これが例えば GAE/Python + ndb だとプロパティを定義するとデフォルトでインデックス=ONとなるので、個人的には Objectify もデフォルトはインデックス=ONの挙動にしておいて欲しかった感じはあります...。

まとめ

Datastore の Single-property Index と Composite Index の性質をまとめると以下のようになります。

制御単位 インデックスの構築 注意点
Single-property Index Entity 単位 半自動 古い Entity は手動でインデックスを作る
Composite Index Kind 単位 自動 Single-property Index が作られていないとダメ

手探りで色々試している面もあるので、理解が間違っている等ありましたらご指摘下さい。

参考