お気に入り | 日本語 | ログイン

クエリとインデックス

すべてのデータストア クエリはインデックスを使用します。インデックスは、希望する順に並べられたクエリ結果を含むテーブルです。App Engine アプリケーションは、index.yaml という名前の設定ファイルでインデックスを定義します。 開発用 Web サーバーは、インデックスが設定されていないクエリが発生すると、このファイルに自動的に候補を追加します。アプリケーションをアップロードする前に、手動でファイルを編集して、インデックスを調整できます。

インデックス ベースのクエリ メカニズムは、ほとんどの一般的な種類のクエリに対応していますが、他のデータベース技術の慣れ親しんだクエリの中には対応していないものもあります。クエリの制限とその説明は次のとおりです。

クエリの概要

クエリは条件のセットに一致するエンティティをデータストアから取得します。クエリはエンティティの種類、エンティティのプロパティ値に基づいたゼロ以上の条件(「フィルタ」と呼ばれることもあります) とゼロ以上の並び替え順序の記述を指定します。クエリは実行されると、指定された種類で、指定されたすべての条件に一致するエンティティをすべてフェッチし、記述された順序に並び替えます。

Datastore Python API はクエリの作成用と実行用に 2 つのインターフェースを提供します。Query インターフェースはメソッドを使用してクエリを作成し、GqlQuery インターフェースは SQL に似た言語である GQL を使用してクエリ文字列からクエリを作成します。これらのインターフェースについては、データの作成、取得、削除: クエリを使用したエンティティの取得と関連する参照ページに詳しい説明があります。

class Person(db.Model):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  city = db.StringProperty()
  birth_year = db.IntegerProperty()
  height = db.IntegerProperty()

# The Query interface prepares a query using instance methods.
q = Person.all()
q.filter("last_name =", "Smith")
q.filter("height <", 72)
q.order("-height")

# The GqlQuery interface prepares a query using a GQL query string.
q = db.GqlQuery("SELECT * FROM Person " + 
                "WHERE last_name = :1 AND height < :2 " +
                "ORDER BY height DESC",
                "Smith", 72)

# The query is not executed until results are accessed.
results = q.fetch(5)
for p in results:
  print "%s %s, %d inches tall" % (p.first_name, p.last_name, p.height)

フィルタには、プロパティ名、比較演算子と値が含まれます。指定された名前のプロパティが存在し、その値が指定された値と演算子の条件を満たす場合に、エンティティはフィルタを満たします。すべてのフィルタを満たした場合、エンティティはクエリの結果として返されます。

フィルタ演算子には次のいずれかを指定できます。

  • < 未満
  • <= 以下
  • = 等しい
  • > より大きい
  • >= 以上
  • != 等しくない
  • IN 指定されたリストのいずれかの値に等しい

!= 演算子は実際には 2 つのクエリを実行します。1 つは、指定された通りのフィルタを使用し、「等しくない」フィルタのみが「未満」フィルタで置き換えられたものです。もう 1 つクエリでは「等しくない」フィルタが「より大きい」フィルタで置き換えられたものです。結果はマージされて、並び替えられます。不等式フィルタについて下記で説明したように、クエリには「等しくない」フィルタを 1 つだけ含むことができます。「等しくない」フィルタと他の不等式フィルタを同時に使用することはできません。

IN 演算子も複数のクエリを実行します。フィルタで指定されたリストのアイテムごとにクエリが実行され、指定された通りのフィルタが使用されます。アイテムごとのクエリにおいては、IN フィルタは「等しい」フィルタで置き換えられます。結果はマージされ、リスト内のアイテムの順番で並び替えられます。クエリに 1 つ以上の IN フィルタがある場合、複数のクエリとして実行されます。IN フィルタの値の組み合わせごとに 1 つのクエリが実行されます。

!= および IN 演算子を含む単一のクエリのサブクエリは 30 個に制限されます。

インデックスの概要

App Engine データストアは、アプリケーションが作成しようとするすべてのクエリについてインデックスを保持します。アプリケーションがデータストア エンティティに変更を加えると、データストアは正しい結果でインデックスを更新します。アプリケーションがクエリを実行すると、データストアは対応するインデックスから直接結果をフェッチします。

アプリケーションは、クエリに使用される種類、フィルタ プロパティおよび演算子、並び替え順序の各組み合わせのインデックスを持ちます。GQL で記述されたクエリ例をご覧ください。

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

このクエリのインデックスは Person の種類のエンティティ用のキーのテーブルで、height 値と last_name プロパティの入る列を持っています。このインデックスは、height を基準にして降順に並び替えられます。

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

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

データストアは次の手順に従ってクエリを実行します:

  1. データストアはクエリの種類、フィルタ プロパティ、フィルタ演算子、並び替え順序に対応するインデックスを特定します。
  2. データストアはクエリのフィルタ値を使用して、すべてのフィルタ条件に一致する最初のエンティティで、インデックスのスキャンを開始します。
  3. データストアは各エンティティを返しながら、次のエンティティがフィルタ条件を満たさなくなるまで、または、インデックスの最後に到達するまで、またはクエリが要求した最大結果件数に到達するまで、インデックスのスキャンを続行します。

インデックス テーブルには、フィルタまたは並び替え順序に使用されるすべてのプロパティのための列が含まれます。行は次のアスペクトを基準にして順番に並び替えられます。

  • 祖先
  • 等式フィルタで使用されるプロパティ値
  • 不等式フィルタで使用されるプロパティ値
  • 並び替え順序で使用されるプロパティ値

注: インデックスを目的として、IN フィルタは = フィルタと同じように扱われ、!= フィルタはその他の不等式フィルタと同じように扱われます。

これにより、このインデックスを使用する、すべての可能なクエリのすべての結果がテーブル内の連続した行に置かれます。

ヒント: クエリ フィルタには文字列値の一部を一致させる明確な方法はありませんが、不等式フィルタを使用して、接頭辞が一致するように見せかけることができます。

db.GqlQuery("SELECT * FROM MyModel WHERE prop >= :1 AND prop < :2", "abc", u"abc" + u"\ufffd")

これは MyModelprop 文字列プロパティを持ち、abc の文字で始まるすべてのエンティティに一致します。Unicode 文字列である u"\ufffd" は可能な限り最大の Unicode 文字を表します。このプロパティ値がインデックス内で並び替えられると、この範囲に当てはまる値は指定された接頭辞で始まるすべての値となります。

このメカニズムは広範囲のクエリをサポートしており、ほとんどのアプリケーションに適しています。しかし、他のデータベース技術の慣れ親しんだクエリの中には対応していないものもあります。

フィルタ プロパティを持たないエンティティはクエリでは返されない

インデックスに含まれるのは、すべてのプロパティがインデックスによって参照されるエンティティに限られます。エンティティがインデックスによって参照されるプロパティを持たない場合、エンティティはインデックスに出現せず、インデックスを使用するクエリの結果となることはありません。

App Engine データストアは、プロパティを持たないエンティティと null 値のプロパティを持つエンティティ(None)を区別するので注意してください。種類のすべてのエンティティがクエリの結果に含むことができるようにするために、クエリ フィルタで使用するプロパティにNone などのデフォルト値を割り当てるデータモデルを使用できます。

Text 値と Blob 値はインデックスされない

長いテキスト型(Text)または長いバイナリ型(Blob)の値を持つプロパティはインデックスには含まれないため、クエリで検索できません。

これらのプロパティ値をインデックスしない結果として、プロパティにフィルタまたは並び替え順序を持つクエリが、プロパティに Text 値または Blob 値を持つエンティティに一致することはありません。このような値を持つプロパティは、プロパティにクエリ フィルタまたは並び替え順序が設定されていないような動作をします。

混在した型のプロパティ値を型で並び替える

2 つのエンティティが同じ名前で異なる値型のプロパティを持つ場合、プロパティのインデックスは、エンティティをまず値型で並び替え、続いて型の適切な順序で並び替えます。たとえば、2 つのエンティティが「age」というプロパティをそれぞれ持ち、1 つが整数値、もう 1 つが文字列値である場合、「age」プロパティで並び替えると、値自体に関係なく、整数値のエンティティが常に文字列値のエンティティより前に並べられます。

データストアで別の型として認識される整数値と浮動小数点値の場合には特に注意が必要です。すべての整数値は浮動小数点値より前に並べられるため、整数値 38 を持つプロパティは、浮動小数点値 37.5 のプロパティよりも前の順序となります

インデックスの定義と設定

App Engine はデフォルトでいくつかのシンプルなクエリのインデックスを構築します。その他のクエリに対しては、アプリケーションは必要なインデックスを index.yaml という名前の設定ファイルに指定する必要があります。App Engine で実行しているアプリケーションが、(デフォルトによって提供されるか index.yaml で記述されるかした)対応するインデックスがないクエリを実行しようとすると、クエリは失敗となります。

App Engine は次の形式のクエリに自動的にインデックスを提供します:

  • 等式、および祖先フィルタのみを使用するクエリ
  • 不等式フィルタのみを使用するクエリ(単一のプロパティを持つものに限られます)
  • プロパティに昇順または降順のいずれかの並び替え順序が 1 つだけ設定されているクエリ

次のような他の形式のクエリはインデックスが index.yaml で指定されている必要があります。

  • 複数の並び替え順序を持つクエリ
  • キーに降順の並び替え順序の指定されているクエリ
  • 1 つのプロパティに対して 1 つ以上の不等式フィルタを持ち、その他のクエリに対して 1 つ以上の等式フィルタを持つクエリ
  • 不等式フィルタと祖先フィルタを持つクエリ

開発 Web サーバーを使用している場合、インデックス設定は容易に実行できます。必要なインデックスが存在しないクエリを実行すると、操作は失敗しませんが、逆に開発 Web サーバーがクエリを成功させるために必要なインデックス設定を生成します。アプリケーションのローカル テスト時に、アプリケーションが作成する可能性のあるクエリ(種類、祖先、フィルタ、並び替え順序のすべての組み合わせ)をすべて呼び出す場合、生成されたエンティティはインデックスの完全なセットを表します。テストですべての可能性のあるクエリ形式を実行しない場合、アプリケーションをアップロードする前にインデックス設定を見直し、調整することもできます。

App Engine はデフォルトでいくつかのシンプルなクエリのインデックスを構築します。その他のクエリに対しては、アプリケーションは必要なインデックスを index.yaml という名前の設定ファイルに指定する必要があります。App Engine で実行しているアプリケーションが、(デフォルトによって提供されるか index.yaml で記述されるかした)対応するインデックスがないクエリを実行しようとすると、クエリは失敗となります。

index.yaml はそれぞれのインデックス テーブルを、種類、クエリのフィルタと並び替え順序に必要なプロパティ、クエリが祖先節を使用するかどうか(Query.ancestor() または GQL ANCESTOR IS 節)を含めて記述します。プロパティは指定された並び替え順序でリストされます。等式または IN フィルタに使用されるプロパティ、不等式フィルタに使用されるプロパティ、クエリ結果の並び替え順序とその方向(昇順または降順)、という順序です。

次のクエリの例をもう一度考えてみましょう。

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

アプリケーションがこのクエリ(および、このクエリに類似しているが "Smith"72 に異なった値を持つクエリ)のみを実行する場合、index.yaml は以下のようになります。

indexes:
- kind: Person
  properties:
  - name: last_name
  - name: height
    direction: desc

エンティティが作成または更新されると、すべての適切なインデックスが同じように更新されます。エンティティに適用されるインデックス数は、エンティティの作成または更新に要する時間に影響します。

index.yaml のシンタックスに関する詳しい説明は、インデックスの設定をご覧ください。

キーに対するクエリ

エンティティ キーはクエリ フィルタまたは並び替え順序に使用されます。プロパティ名の代わりに特別な名前である __key__ が使用されます。データストアは、エンティティの親パス、種類、アプリケーションが割り当てたキー名の文字列またはシステムが割り当てた数値 ID など、クエリにおける完全なキー値を考慮します。

エンティティ キーはシステム全体のエンティティにおいて一意であるため、__key__ クエリは、データストアのコンテンツのバッチ ダンプなど、指定された種類のバッチのエンティティを容易に取得できます。オフセットとは異なり、任意の数のエンティティに対して効率的に動作します。次に例を示します。

class MainHandler(webapp.RequestHandler):
  def get(self):
    query = Entity.gql('ORDER BY __key__')

    # Use a query parameter to keep track of the last key of the last
    # batch, to know where to start the next batch.
    last_key_str = self.request.get('last')
    if last_key_str:
      last_key = db.Key(last_key_str)
      query = Entity.gql('WHERE __key__ > :1 ORDER BY __key__', last_key)

    # For batches of 20, fetch 21, then use result #20 as the "last"
    # if there is a 21st.
    entities = query.fetch(21)
    new_last_key_str = None
    if len(entities) == 21:
      new_last_key_str = str(entities[19].key())

    # Return the data and new_last_key_str.  Client would use
    # http://...?last=new_last_key_str to fetch the next batch.
    # ...

キーはまず親パスで並び替えられ、続いて種類、そしてキー名または ID の順序に従います。種類とキー名は文字列で、バイト値によって並び替えられます。ID は整数値で数値順に並び替えられます。同じ親と種類を持つエンティティが、キー名の文字列と数値 ID を組み合わせて使用している場合、数値 ID を使用しているエンティティの方が、キー名文字列を使用しているエンティティより前であると判断されます。親パスの要素も同様に、種類(文字列)、キー名(文字列)、ID(数値)で比較されます。

キーを含むクエリは、プロパティを含むクエリと同様にインデックスを使用しますが、1 つだけ違う点があります。プロパティとは異なり、キーに対する等式フィルタと同時にそれ以外のフィルタを含むクエリは、アプリケーションのインデックス定義ファイルに含まれるカスタム インデックスを使用する必要があります。すべてのクエリに対する場合と同様に、カスタム インデックスを必要とするクエリのテストが行われた場合、開発 Web サーバーはこのファイルに適切な設定エントリを作成します。

クエリに対する制限

インデックス クエリのメカニズムの性質により、クエリは多少の制限を受けます。

プロパティをフィルタまたは並び替えするには、プロパティが存在することが必要

クエリ フィルタ条件やプロパティの並び替え順序は、エンティティがプロパティ値を持っているという条件が前提になっています。

データストア エンティティには、同じ種類のエンティティが持つプロパティ値は必要ではありません。プロパティのフィルタはプロパティ値を持つエンティティにのみ一致できます。プロパティ値を持たないエンティティで、フィルタまたは並び替え順序に使用されているエンティティはクエリ用に構築されるインデックスから省略されます。

プロパティを持たないエンティティに一致するフィルタはない

指定されたプロパティがないエンティティにクエリは実行できません。1 つの代替方法は、None のデフォルト値を持つ固定(モデル化された)プロパティを作成し、None をプロパティ値として持つエンティティに対するフィルタを作成することです。

不等式フィルタが使用できるのは 1 つのプロパティに限られる

クエリが不等式フィルタ(<<=>=>!=)を使用できるのは、すべてのフィルタにわたって 1 つのプロパティに限られます。

たとえば、このクエリは記述できます。

SELECT * FROM Person WHERE birth_year >= :min
                       AND birth_year <= :max

しかし、このクエリは、同じクエリ内で 2 つの異なったプロパティに不等式フィルタを使用しているため、使用できません。

SELECT * FROM Person WHERE birth_year >= :min_year
                       AND height >= :min_height     # ERROR

フィルタは、同じクエリ内の異なったプロパティに対して、1 つ以上の不等式条件を持つクエリを含め、等号(=)比較を組み合わせても構いません。これは使用可能です。

SELECT * FROM Person WHERE last_name = :last_name
                       AND city = :city
                       AND birth_year >= :min_year

クエリ メカニズムは、結果を得るためにテーブル全体をスキャンする必要のないように、クエリのすべての結果がインデックス テーブル内で隣接し合うことで成り立っています。単一のインデックス テーブルは、すべての結果をテーブル内で連続させながら、複数のプロパティに対する複数の不等式フィルタを表すことはできません。

他の並び替え順序より先に、不等式フィルタのプロパティを並び替える必要がある

クエリに不等式比較のフィルタと 1 つ以上の並び替え順序がある場合、クエリは不等式に使用されるプロパティの並び替え順序を含んでいる必要があり、この並び替え順序は他のプロパティに対する並び替え順序より先に出現する必要があります。

このクエリは不等式フィルタを使用しており、また、フィルタされたプロパティ順序で並び替えられないので、有効ではありません。

SELECT * FROM Person WHERE birth_year >= :min_year
                     ORDER BY last_name              # ERROR

同様に、その他の順序で並び替えられるより先に、フィルタされたプロパティ順序に並び替えられていないので、この クエリも有効ではありません。

SELECT * FROM Person WHERE birth_year >= :min_year
                     ORDER BY last_name, birth_year  # ERROR

このクエリは有効です。

SELECT * FROM Person WHERE birth_year >= :min_year
                     ORDER BY birth_year, last_name

不等式フィルタに一致するすべての結果を得るために、クエリはインデックス テーブルの最初に一致する行をスキャンし、次に、一致しない行が見つかるまで、連続するすべての結果を返します。行が完全な結果のセットを表すためには、行は他の並び替え順序より先に不等式フィルタで並び替えられている必要があります。

複数の値での並び替え順序とプロパティ

複数の値のプロパティのインデックス方法は通常とは異なるため、これらのプロパティの並び替え順序は通常とは異なります。

  • エンティティが複数の値のプロパティを基準にして昇順に並び替えられる場合、並び替えに使用される値は最小値になります。
  • エンティティが複数の値のプロパティを基準にして降順に並び替えられる場合、並び替えに使用される値は最も大きな値になります。
  • その他の値は並び替え順序や値の数に影響しません。
  • 要素の大きさが同じ場合、エンティティのキーはタイブレーカとして使用されます。

この並び替え順序は通常とは異なった結果となり、昇順および降順ともに [1, 9][4, 5, 6, 7] より先になります。

複数の値のプロパティに等式フィルタと並び替え順序の両方を指定したクエリには、十分に注意する必要があります。これらのクエリでは、並び替え順序は無視されます。単一の値のプロパティの場合、これはシンプルな最適化になります。すべての結果はプロパティに対して同じ値を持つことになり、そのため、結果はそれ以上並び替える必要がありません。

しかし、複数の値のプロパティでは追加的な値を持つ場合があります。並び替え順序が無視されるため、クエリ結果は並び替え順序が適用された場合とは異なった順序で返される可能性があります(無視された並び替え順序の保存は負荷が高く、余分なインデックスを必要としますが、めったにないケースなので、クエリの計画者はこれを省略します)。

大きなエンティティとエクスプローディング インデックス

上記で説明したように、すべてのエンティティのすべてのプロパティ(Text または Blob 値を持つものは例外)はデフォルトで提供されるシンプルなインデックスも含めて、少なくとも 1 つのインデックス テーブルに追加されます。プロパティを参照するアプリケーションの ファイルに記述されたインデックスindex.yaml があればそれにも追加されます。各プロパティに 1 つの値を持つエンティティについては、App Engine はシンプルなインデックスにプロパティ値をいったん格納し、プロパティがカスタム インデックスで参照されるたびに 1 回ずつ格納します。これらのインデックス エンティティはプロパティ値が変更されるたびに更新される必要があるので、プロパティを参照するインデックスが多ければ多いほど、プロパティの更新に要する時間が長くなります。

エンティティの更新にあまり長い時間がかからないように、データストアは単一のエンティティが持つことのできるインデックス数を制限しています。制限数は大きいものなので、ほとんどのアプリケーションには影響ありません。しかし、制限に達する場合もあります。たとえば、非常に多くの単一値を持つプロパティを所有するエンティティはインデックスのエントリ制限を超えることがあります。

複数の値のプロパティは、各値をインデックスの別のエントリとして格納します。非常に多くの値を持つ単一プロパティを所有するエンティティはインデックスのエントリ制限を超えることがあります。

複数の値を持つ複数のプロパティを参照するカスタム インデックスは、値の数が少なくても非常に大きなものとなることがあります。こうしたプロパティを完全に記録するには、インデックス テーブルはインデックスのすべてのプロパティの、値のすべての置換のための行を含む必要があります。

たとえば、次のインデックス(index.yaml シンタックスで記述されたもの)はエンティティの MyModel の種類の x プロパティおよび y プロパティを含んでいます。

indexes:
- kind: MyModel
  properties:
  - name: x
  - name: y

次のコードはプロパティ x に対して 2 値のプロパティを持つエンティティとプロパティ y に対して 2 値のプロパティを持つエンティティを作成します。

class MyModel(db.Expando):
  pass

e2 = MyModel()
e2.x = ['red', 'blue']
e2.y = [1, 2]
e2.put()

この値を正確に表すには、インデックスは 12 個のプロパティ値を格納する必要があります。xy に組み込みのインデックスに 2 つずつ、カスタム インデックスの xy の 4 つの置換それぞれに 2 つずつです。複数の値のプロパティに多くのリスト値がある場合、インデックスは単一のエンティティに対して非常に多くのインデックス エンティティを格納しなければならないことになります。複数の値を持つ複数のプロパティを参照するインデックスは「エクスプローディング インデックス」と呼ばれることがあります。値の数が少なくても非常に大きなものとなることがあるからです。

put() の結果がインデックス数のエントリ制限を超えると、呼び出しは例外となり失敗します。構築された時点でエンティティのインデックス数がエントリ制限を超える新しいインデックスを作成すると、インデックスに対するクエリは失敗し、インデックスは管理コンソールで「Error」状態として表示されます。

「Error」インデックスを処理するには、まず index.yaml ファイルからそれらを削除し、appcfg.py vacuum_indexes を実行します。次に、インデックス定義と対応するクエリを修正するか、インデックスを「エクスプロード(爆発、急増)」させているエンティティを削除します。最後にインデックスを index.yaml に戻し、appcfg.py update_indexes を実行します。

リスト プロパティを使用する、カスタム インデックスを必要とするクエリを使用しないことで、エクスプローディング インデックスを回避できます。上記で説明したように、これには複数の並び替え順序、等式と不等式が混合したフィルタ、祖先フィルタを持つクエリが含まれます。