インデックス

Datastore モードの Firestore のクエリはいずれも、1 つ以上のインデックスを使用して結果を計算します。インデックスには、インデックスのプロパティとエンティティの祖先(省略可)によって順序が指定されたエンティティ キーが格納されています。インデックスは、アプリケーションがエンティティに加えた変更を反映して更新されます。そのため追加の計算を行うことなく、すべてのクエリの正しい結果が得られます。

インデックスは次の 2 種類に分けられます。

組み込みインデックス
Datastore モードのデータベースの場合、デフォルトでは、エンティティの種類ごとに各プロパティのインデックスが自動的に事前定義されます。このような単一プロパティのインデックスは単純な種類のクエリに適しています。
複合インデックス
複合インデックスでは、インデックス付けされたエンティティごとに複数のプロパティ値がインデックス付けされます。複雑なクエリをサポートする複合インデックスは、インデックス構成ファイルindex.yaml)で定義されます。

インデックスの種類については、後で詳しく説明します。

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

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

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

複合インデックスは、インデックス定義で指定された順序で、まず祖先、次にプロパティ値で並べられます。これを理解すると、効率的なクエリが可能になる完全なインデックスを作成できます。

インデックスの構成

Datastore モードの Firestore では、次の形式のクエリに対して組み込みインデックスまたは自動インデックスを使用できます。

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

たとえば、Datastore モードのデータベースのデフォルトでは、エンティティの個々の種類に対して、1 つのプロパティにつき 2 つの単一プロパティ インデックス(昇順用と降順用)が自動的に事前定義されます。データベースでプロパティのインデックスを維持する必要がない場合は、そのプロパティをインデックスから除外します。プロパティを除外すると、このプロパティがすべての複合インデックスから削除されることに注意してください。

組み込みインデックスは、等式だけのクエリや単純な不等式クエリなど、多くの単純なクエリを実行するのに十分です。

組み込みインデックスは、Google Cloud コンソールの [インデックス] ページには表示されません。

複雑なクエリの場合は複合インデックス、つまり手動のインデックスを定義する必要があります。複合インデックスは、次の形式のクエリで必要になります。

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

複合インデックスはアプリケーションのインデックス構成ファイルindex.yaml)で定義されます(組み込みインデックスはインデックス構成ファイルに含まれていません)。

複合インデックスは複数のプロパティで構成されており、個々のプロパティがインデックスから除外されていてはなりません。

複合インデックスは、Google Cloud コンソールの [インデックス] ページで確認できます。Google Cloud コンソールを使用して複合インデックスを作成または更新することはできません。

使用可能なインデックス(組み込みインデックスまたはインデックス構成ファイルで指定されているインデックス)を使って実行できないクエリをアプリケーションが実行しようとすると、そのクエリは失敗します。

Datastore モードの API は、ほとんどのアプリケーションに適したインデックスを自動的に提示します。アプリケーションで Datastore モードのデータベースを使用する方法や、データのサイズと形状によっては、インデックスを手動で調整したほうが確実な場合があります。たとえば、複数のプロパティ値を持つエンティティを書き込むとインデックス爆発が発生し、ストレージ コストが高くなり、書き込みのレイテンシが長くなる可能性があります。

Datastore エミュレータを使用すると、インデックス構成ファイルを簡単に管理できます。必要なインデックスが存在しないクエリの実行には失敗しますが、Datastore エミュレータは、そのクエリを正常に実行するためのインデックス構成を生成できます。アプリケーションのローカルテストで、あらゆる組み合わせのフィルタと並べ替え順を使用して、アプリケーションが発行する可能性があるすべてのクエリを試した場合、生成されたエントリはインデックスの完全なセットを表すことになります。テストで可能性のあるすべての形式のクエリが試行されない場合は、インデックスを更新する前にインデックス構成ファイルを確認、調整できます。

index.yaml の詳細については、インデックスの構成をご覧ください。

インデックスのデプロイ、削除

インデックス構成ファイルの変更が完了したら、gcloud datastore indexes create コマンドを実行してインデックスの使用を開始します。詳細については、インデックスの更新をご覧ください。

以前にデプロイしたインデックスが不要になった場合は、使われていないインデックスを削除できます。

ストレージ コストと書き込みレイテンシ

インデックスはストレージ コストに影響します。インデックス エントリのサイズでは、組み込みインデックスと複合インデックスがデータベースのストレージ サイズにどのように影響するかを説明しています。Datastore モードの Firestore の統計情報を使用すると、インデックス エントリとインデックス ストレージ サイズの詳細を確認できます。

インデックスも書き込みレイテンシに影響を与えます。プロパティ値を更新すると、関連するすべてのインデックスも更新されます。データベースで更新する必要があるインデックスが多いほど、オペレーションに時間がかかります。

使用されていないインデックスを削除し、インデックスからプロパティを除外することで、ストレージ コストを削減し、書き込みパフォーマンスを向上できます。これにより、インデックスの制限によるオペレーションの失敗も防げます。

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

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

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

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

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

除外されたプロパティ

特定のプロパティに対するフィルタリングや並べ替えが不要な場合は、インデックスから除外することで、Datastore モードのデータベースにプロパティのインデックス エントリが保持されなくなります。これによりインデックス エントリの保持に必要な分だけストレージ サイズが低減されるため、アプリケーションの実行にかかるコストが低く抑えられます。これにより、書き込みレイテンシも向上します。除外されたプロパティを持つエンティティは、そのプロパティが設定されていないかのように振る舞います。除外されたプロパティに対するフィルタまたは並べ替え順を持つクエリが、そのエンティティに一致することはありません。

次の例の description プロパティは、インデックスから除外されています。

C#

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore C# API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

Entity task = new Entity()
{
    Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
    ["category"] = "Personal",
    ["created"] = new DateTime(1999, 01, 01, 0, 0, 0, DateTimeKind.Utc),
    ["done"] = false,
    ["priority"] = 4,
    ["percent_complete"] = 10.0,
    ["description"] = new Value()
    {
        StringValue = "Learn Cloud Datastore",
        ExcludeFromIndexes = true
    },
};

Go

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Go API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

type Task struct {
	Category        string
	Done            bool
	Priority        int
	Description     string `datastore:",noindex"`
	PercentComplete float64
	Created         time.Time
}
task := &Task{
	Category:        "Personal",
	Done:            false,
	Priority:        4,
	Description:     "Learn Cloud Datastore",
	PercentComplete: 10.0,
	Created:         time.Now(),
}

Java

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Java API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

Entity task =
    Entity.newBuilder(taskKey)
        .set("category", "Personal")
        .set("created", Timestamp.now())
        .set("done", false)
        .set("priority", 4)
        .set("percent_complete", 10.0)
        .set(
            "description",
            StringValue.newBuilder("Learn Cloud Datastore").setExcludeFromIndexes(true).build())
        .build();

Node.js

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Node.js API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

const task = [
  {
    name: 'category',
    value: 'Personal',
  },
  {
    name: 'created',
    value: new Date(),
  },
  {
    name: 'done',
    value: false,
  },
  {
    name: 'priority',
    value: 4,
  },
  {
    name: 'percent_complete',
    value: 10.0,
  },
  {
    name: 'description',
    value: 'Learn Cloud Datastore',
    excludeFromIndexes: true,
  },
];

PHP

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore PHP API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

$task = $datastore->entity(
    $key,
    [
        'category' => 'Personal',
        'created' => new DateTime(),
        'done' => false,
        'priority' => 4,
        'percent_complete' => 10.0,
        'description' => 'Learn Cloud Datastore'
    ],
    ['excludeFromIndexes' => ['description']]
);

Python

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Python API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

import datetime

key = client.key("Task")
task = datastore.Entity(key, exclude_from_indexes=("description",))
task.update(
    {
        "category": "Personal",
        "description": "Learn Cloud Datastore",
        "created": datetime.datetime.now(tz=datetime.timezone.utc),
        "done": False,
        "priority": 4,
        "percent_complete": 10.5,
    }
)
client.put(task)

Ruby

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Ruby API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

task = datastore.entity "Task" do |t|
  t["category"] = "Personal"
  t["created"] = Time.now
  t["done"] = false
  t["priority"] = 4
  t["percent_complete"] = 10.0
  t["description"] = "Learn Cloud Datastore"
  t.exclude_from_indexes! "description", true
end

GQL

該当なし

description プロパティが除外されている場合、次の例のクエリは結果を一切返しません。

C#

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore C# API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

Query query = new Query("Task")
{
    Filter = Filter.Equal("description", "Learn Cloud Datastore")
};

Go

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Go API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

query := datastore.NewQuery("Tasks").
	FilterField("Description", "=", "A task description")

Java

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Java API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

Query<Entity> query =
    Query.newEntityQueryBuilder()
        .setKind("Task")
        .setFilter(PropertyFilter.eq("description", "A task description"))
        .build();

Node.js

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Node.js API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

const query = datastore
  .createQuery('Task')
  .filter(new PropertyFilter('description', '=', 'A task description.'));

PHP

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore PHP API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

$query = $datastore->query()
    ->kind('Task')
    ->filter('description', '=', 'A task description.');

Python

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Python API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query = client.query(kind="Task")
query.add_filter(
    filter=datastore.query.PropertyFilter(
        "description", "=", "Learn Cloud Datastore"
    )
)

Ruby

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Ruby API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

query = datastore.query("Task")
                 .where("description", "=", "A task description.")

GQL

# Will not return any results!
SELECT * FROM Task WHERE description = 'A task description.'

後でプロパティを変更して再度インデックス付けできます。

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

  1. エンティティを検索または取得する
  2. データベースにエンティティを書き込む

同様に、インデックス付けされているプロパティを変更してインデックスから除外した場合も、変更後にデータベースに書き込まれたエンティティにしか影響しません。そのプロパティを持つ既存のエンティティのインデックス エントリは、エンティティが更新または削除されるまで存在し続けます。目的に合わない結果を回避するには、そのような(現在は除外されている)プロパティを基準としてフィルタリングや並べ替えを行うすべてのクエリのコードを消去する必要があります。

インデックスの上限

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

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

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

複数のプロパティが存在するエンティティで、それぞれのプロパティが複数の値を取る場合、状況はさらに悪化します。そのようなエンティティに対処するには、可能性のあるすべてのプロパティ値の組み合わせに対応するエントリをインデックスに含める必要があります。カスタム インデックスが複数のプロパティを参照し、それぞれのプロパティが複数の値を持つ場合、組み合わせ数が爆発的に増加する可能性があり、比較的少数のプロパティ値しか持たないエンティティでもエントリが大量に必要になります。このような「インデックス爆発」が起こると、大量のインデックス エントリを格納する必要がでてくるため、エンティティのストレージ サイズが大幅に増加する可能性があります。またインデックス爆発は、インデックスのエントリ数やサイズの制限を超過する原因となる可能性もあります。

たとえば、次のコードについて考えてみます。

C#

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore C# API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

Entity task = new Entity()
{
    Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
    ["tags"] = new ArrayValue() { Values = { "fun", "programming", "learn" } },
    ["collaborators"] = new ArrayValue() { Values = { "alice", "bob", "charlie" } },
    ["created"] = DateTime.UtcNow
};

Go

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Go API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

task := &Task{
	Tags:          []string{"fun", "programming", "learn"},
	Collaborators: []string{"alice", "bob", "charlie"},
	Created:       time.Now(),
}

Java

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Java API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

Entity task =
    Entity.newBuilder(taskKey)
        .set("tags", "fun", "programming", "learn")
        .set("collaborators", "alice", "bob", "charlie")
        .set("created", Timestamp.now())
        .build();

Node.js

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Node.js API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

const task = {
  method: 'insert',
  key: datastore.key('Task'),
  data: {
    tags: ['fun', 'programming', 'learn'],
    collaborators: ['alice', 'bob', 'charlie'],
    created: new Date(),
  },
};

PHP

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore PHP API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

$task = $datastore->entity(
    $datastore->key('Task'),
    [
        'tags' => ['fun', 'programming', 'learn'],
        'collaborators' => ['alice', 'bob', 'charlie'],
        'created' => new DateTime(),
    ]
);

Python

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Python API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

import datetime

task = datastore.Entity(client.key("Task"))
task.update(
    {
        "tags": ["fun", "programming", "learn"],
        "collaborators": ["alice", "bob", "charlie"],
        "created": datetime.datetime.now(tz=datetime.timezone.utc),
    }
)

Ruby

Cloud Datastore 用のクライアント ライブラリをインストールして使用する方法については、Cloud Datastore のクライアント ライブラリをご覧ください。 詳細については、Cloud Datastore Ruby API のリファレンス ドキュメントをご覧ください。

Cloud Datastore への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

task = datastore.entity "Task" do |t|
  t["tags"] = ["fun", "programming", "learn"]
  t["collaborators"] = ["alice", "bob", "charlie"]
  t["created"] = Time.now
end

GQL

該当なし

Task エンティティには、プロパティ tags の値が 3 つ、プロパティ collaborators の値が 3 つあり、created に現在の日付が設定されます。この場合、プロパティ値の組み合わせとして可能性のあるものそれぞれに対応するには、9 つのインデックス エントリが必要になります。

('fun', 'alice', NOW())
('fun', 'bob', NOW())
('fun', 'charlie', NOW())

('programming', 'alice', NOW())
('programming', 'bob', NOW())
('programming', 'charlie', NOW())

('learn', 'alice', NOW())
('learn', 'bob', NOW())
('learn', 'charlie', NOW())

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

indexes:
- kind: Task
  properties:
  - name: tags
  - name: created
- kind: Task
  properties:
  - name: collaborators
  - name: created

これにより、必要なエントリの数は、9 ではなく、(|tags| * |created| + |collaborators| * |created|)、つまり 6 つに削減されます。

('fun', NOW())
('programming', NOW())
('learn', NOW())

('alice', NOW())
('bob', NOW())
('charlie', NOW())

commit オペレーションによってインデックスのエントリ数またはサイズの上限が超過した場合、そのオペレーションは失敗します。エラーのテキストは、どの上限を超えたか("Too many indexed properties" または "Index entries too large")、および原因となったカスタム インデックスを示します。構築時にエンティティの上限を超える新しいインデックスを作成すると、そのインデックスに対するクエリは失敗し、Google Cloud コンソールでは Error 状態で表示されます。このような Error 状態のインデックスに対処するには、次の手順に従います。

  1. 該当するインデックスをインデックス構成ファイル(index.yaml)から削除します。
  2. 使用されていないインデックスの削除の説明に従って、Google Cloud CLI で datastore indexes cleanup コマンドを使用してデータベースからインデックスを削除します。
  3. 次のいずれかを実行します。
    • インデックスの定義および対応するクエリを再構成する
    • インデックス爆発の原因となっているエンティティを削除する
  4. インデックスを index.yaml に追加し直します。
  5. インデックスの更新の説明に従って、Google Cloud CLI で datastore indexes create コマンドを実行してデータベースにインデックスを追加します。

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

射影のインデックス

射影クエリでは、射影で指定されているすべてのプロパティがインデックスに含まれるようにする必要があります。Datastore エミュレータを使用すると、必要なインデックスがインデックス構成ファイル(index.yaml)内に自動生成されます。このファイルはアプリケーションと一緒にアップロードされます。

必要なインデックスの数を最小限に抑える方法の 1 つとして、必ずしもすべてが必要でない場合であっても、同じ複数のプロパティを一貫して射影する方法があります。たとえば、次のクエリはそれぞれ個別のインデックスを必要とします。

SELECT priority, percent_complete FROM Task

SELECT priority, percent_complete, created FROM Task

ただし、created が必要でないときでもプロパティ prioritypercent_completecreated を常に射影すれば、必要となるインデックスは 1 つのみとなります。

射影のプロパティがクエリの別の部分に含まれていない場合、既存のクエリを射影クエリに変換する際に新しいインデックスを構築しなければならない場合があります。たとえば、次のような既存のクエリがあるとします。

SELECT * FROM Task
WHERE priority > 1
ORDER BY priority, percent_complete

必要なインデックスは次のとおりです。

indexes:
- kind: Task
  properties:
  - name: priority
  - name: percent_complete

このクエリを次のいずれかの射影クエリに変換します。

SELECT created FROM Task
WHERE priority > 1
ORDER BY priority, percent_complete

SELECT priority, percent_complete, created FROM Task
WHERE priority > 1
ORDER BY priority, percent_complete

新しいプロパティ(created)が取り入れられるため、新しいインデックスを作成する必要があります。

indexes:
- kind: Task
  properties:
  - name: priority
  - name: percent_complete
  - name: created

ただし、次のクエリ

SELECT priority, percent_complete FROM Task
WHERE priority > 1
ORDER BY priority, percent_complete

では、必要なインデックスに変化はありません。これは射影される priority プロパティと percent_complete プロパティが既存のクエリにすでに含まれているためです。

複数のデータベース

gcloud firestore を使用して、データストア モードの単一のインデックスを管理できます。または、index.yaml ファイルで gcloud datastore を使用して、データベースのすべてのインデックスを管理できます。

gcloud firestore
gcloud firestore indexes composite create --api-scope=datastore-mode-api  --query-scope=QUERY_SCOPE --database=DATABASE_ID
gcloud datastore
gcloud alpha datastore indexes create index.yaml --database=DATABASE_ID

以下を置き換えます。

  • DATABASE_ID: データベース ID。
  • QUERY_SCOPE: 祖先インデックスの場合は collection-recursive、非祖先インデックスの場合は collection-group のいずれか。