Cloud Firestore のインデックスの種類

インデックスは、データベースのパフォーマンスにおける重要な要素です。書籍内のトピックをページ番号に対応付ける書籍の索引(インデックス)と同様に、データベースのインデックスはデータベース内のアイテムをデータベース内の場所にマッピングします。データベースにクエリが送信される際、データベースはインデックスを使用して、リクエストされたアイテムの場所をすばやく検索します。

このページでは、Cloud Firestore で使用される、単一フィールド インデックス複合インデックスの 2 種類のインデックスについて説明します。

すべてのクエリの背後にインデックスが存在

クエリを実行する際にインデックスが存在しないと、ほとんどのデータベースはアイテムごとにデータベース内のコンテンツ全体をクロールすることになるため、データベースが大きくなるにつれて処理がますます遅くなります。Cloud Firestore では、すべてのクエリに対してインデックスを使用することで、クエリの高パフォーマンスを確保します。結果的に、クエリのパフォーマンスは、データベース内のアイテム数ではなく、結果セットのサイズに依存することになります。

インデックス管理の手間を減らし、アプリ開発により注力する

Cloud Firestore には、インデックスの管理にかかる時間を短縮できる機能があります。最も基本的なクエリに必要なインデックスは、自動的に作成されます。Cloud Firestore は、アプリを使用してテストする際に、アプリで必要な追加のインデックスを識別して作成するのに役立ちます。

インデックスのタイプ

Cloud Firestore では、単一フィールド インデックスと複合インデックスという 2 つのタイプのインデックスを使用します。単一フィールド インデックスと複合インデックスは、インデックス登録されるフィールドの数、および管理方法が異なります。

単一フィールド インデックス

単一フィールド インデックスは、特定の 1 つのフィールドを含む、コレクション内のすべてのドキュメントの並べ替え済みマッピングを保持します。単一フィールド インデックスの各エントリには、ドキュメント内の特定のフィールドの値と、そのドキュメントのデータベース内での位置が記録されます。Cloud Firestore はこうしたインデックスを使用して、多くの基本的なクエリを実行します。データベースの自動インデックスの設定とインデックス除外を構成して、単一フィールド インデックスを管理します。

自動インデックス

デフォルトでは、Cloud Firestore はドキュメント内のフィールドおよびマップ内のサブフィールドごとに単一フィールド インデックスを自動的に維持します。Cloud Firestore では単一フィールド インデックスに次のデフォルト設定が使用されます。

  • 配列でもなくマップでもないフィールドに対しては、コレクションのスコープを使用する 2 つの単一フィールド インデックス(1 つは昇順モード、1 つは降順モード)が定義されます。

  • Cloud Firestore は、マップ フィールドごとに以下を作成します。

    • 配列でもマップでもないサブフィールドごとに、コレクションのスコープを使用する 1 つの昇順インデックス。
    • 配列でもマップでもないサブフィールドごとに、コレクションのスコープを使用する 1 つの降順インデックス。
    • 配列サブフィールドごとにコレクションのスコープを使用する 1 つの配列の内容インデックス。
    • Cloud Firestore は、各マップのサブフィールドを再帰的にインデックス登録します。
  • ドキュメント内の配列フィールドに対しては、コレクションのスコープ配列の内容インデックスが作成、維持されます。

  • コレクション グループのスコープがある単一フィールド インデックスは、デフォルトでは維持されません。

単一フィールド インデックス除外

単一フィールド インデックス除外を作成することで、自動インデックス設定からフィールドを除外できます。インデックス除外は、データベース全体の自動インデックス設定より優先されます。除外すると、自動インデックス設定によって本来は無効になる単一フィールド インデックスを有効にしたり、自動インデックスによって本来は有効になる単一フィールド インデックスを無効にできます。除外が役立つケースについては、インデックスに関するベスト プラクティスをご覧ください。

* フィールドのパス値を使用して、コレクション グループ内のすべてのフィールドにコレクション レベルのインデックス除外を追加します。たとえば、コレクション グループ comments の場合、comments コレクション グループのすべてのフィールドと一致するようにフィールドパスを * に設定して、コレクション グループ内のすべてのフィールドのインデックス登録を無効にします。その後、除外を追加して、クエリに必要なフィールドのみをインデックスに登録できます。インデックス フィールドの数を減らすと、ストレージ コストが削減され、書き込みパフォーマンスが向上します。

マップ フィールドの単一フィールド インデックス除外を作成すると、マップのサブフィールドはそれらの設定を継承します。ただし、特定のサブフィールドに対して単一フィールド インデックス除外を定義することはできます。サブフィールドの除外を削除すると、サブフィールドはその親の除外設定が存在する場合はそれを継承し、親の除外が存在しない場合はデータベース全体の設定を継承します。

単一フィールド インデックス除外を作成および管理する方法については、Cloud Firestore でのインデックス管理をご覧ください。

複合インデックス

複合インデックスには、インデックスを作成するフィールドの順序付きリストに基づいて並べ替えられた、コレクション内のすべてのドキュメントのマッピングが格納されます。

Cloud Firestore では複合インデックスを使用して、単一フィールド インデックスではサポートされないクエリをサポートします。

Cloud Firestore では、可能なフィールドの組み合わせの数が多くなるため、単一フィールド インデックスの場合とは異なり、複合インデックスの自動作成は行いません。代わりに、Cloud Firestore は、アプリを作成する際に必要な複合インデックスを特定して作成できるようにします。

最初に必要なインデックスを作成せずに上記のクエリを実行しようとすると、Cloud Firestore はリンクが含まれたエラー メッセージを返します。そのリンク先にアクセスすることによって、不足しているインデックスを作成できます。この動作は、インデックスでサポートされていないクエリを試行するたびに発生します。また、コンソールまたは Firebase CLI で複合インデックスを手動で定義して管理することもできます。複合インデックスの作成と管理の詳細については、インデックスの管理方法をご覧ください。

インデックス モードとクエリのスコープ

単一フィールド インデックスと複合インデックスの構成方法は異なりますが、どちらもインデックス モードとインデックスのクエリのスコープを構成する必要があります。

インデックス モード

インデックスを定義するときは、インデックスを作成するフィールドごとにインデックス モードを選択します。各フィールドのインデックス モードでは、そのフィールドに対する特定のクエリ句がサポートされます。次のインデックス モードから選択できます。

インデックス モード 説明
昇順 フィールドでの <<===>=>!=innot-in の各クエリ句の使用と、そのフィールド値に基づいた結果の並べ替え(昇順)がサポートされます。
降順 フィールドでの <<===>=>!=innot-in の各クエリ句の使用と、そのフィールド値に基づいた結果の並べ替え(降順)がサポートされます。
配列の内容 フィールドでの array-contains クエリ句と array-contains-any クエリ句の使用がサポートされます。

クエリのスコープ

各インデックスは、コレクションまたはコレクション グループのいずれかをスコープとします。これはインデックスのクエリのスコープと呼ばれます。

コレクションのスコープ
Cloud Firestore では、デフォルトでコレクションのスコープがあるインデックスが作成されます。こうしたインデックスでは、単一のコレクションから結果を返すクエリがサポートされます。

コレクション グループのスコープ
コレクション グループには、同じコレクション ID があるすべてのコレクションが含まれます。コレクション グループからフィルタ処理された結果、もしくは並べ替えられた結果を返すコレクション グループ クエリを実行するには、コレクション グループのスコープに対応するインデックスを作成する必要があります。

デフォルトの順序と __name__ フィールド

インデックスでは、各フィールドに指定されたインデックス モード(昇順または降順)によるドキュメントの並べ替えに加え、各ドキュメントの __name__ フィールドによる最終的な並べ替えが適用されます。__name__ フィールドの値は、ドキュメントの完全なパスに設定されます。つまり、同じフィールド値を持つ結果セット内のドキュメントは、ドキュメント パスで並べ替えられます。

デフォルトでは、__name__ フィールドはインデックス定義で最後に並べ替えられたフィールドと同じ方向に並べ替えられます。次に例を示します。

コレクション インデックス登録されるフィールド クエリのスコープ
cities name, __name__ コレクション
cities state, __name__ コレクション
cities country, population, __name__ コレクション

デフォルト以外の __name__ 方向で結果を並べ替えるには、そのインデックスを作成する必要があります。

インデックス登録の例

Cloud Firestore は単一フィールド インデックスを自動的に作成するため、アプリケーションでは最も基本的なデータベース クエリを迅速にサポートできます。単一フィールド インデックスを使用すると、フィールドの値と、比較演算子 <<===>=>in に基づいて単純なクエリを実行できます。配列フィールドについては array-contains クエリと array-contains-any クエリを実行できます。

たとえば、次の例をインデックス作成の観点から確認します。次のスニペットでは、cities コレクションにいくつかの city ドキュメントを作成し、各ドキュメントの namestatecountrycapitalpopulationtags フィールドを設定します。

ウェブ
var citiesRef = db.collection("cities");

citiesRef.doc("SF").set({
    name: "San Francisco", state: "CA", country: "USA",
    capital: false, population: 860000,
    regions: ["west_coast", "norcal"] });
citiesRef.doc("LA").set({
    name: "Los Angeles", state: "CA", country: "USA",
    capital: false, population: 3900000,
    regions: ["west_coast", "socal"] });
citiesRef.doc("DC").set({
    name: "Washington, D.C.", state: null, country: "USA",
    capital: true, population: 680000,
    regions: ["east_coast"] });
citiesRef.doc("TOK").set({
    name: "Tokyo", state: null, country: "Japan",
    capital: true, population: 9000000,
    regions: ["kanto", "honshu"] });
citiesRef.doc("BJ").set({
    name: "Beijing", state: null, country: "China",
    capital: true, population: 21500000,
    regions: ["jingjinji", "hebei"] });

デフォルトの自動インデックス設定を想定する場合、Cloud Firestore は、配列以外の 1 つのフィールドにつき 1 つの昇順単一フィールド インデックス、配列以外の 1 つのフィールドにつき 1 つの降順単一フィールド インデックス、配列フィールドにつき 1 つの配列の内容の単一フィールド インデックスを更新します。次の表の各行は、単一フィールド インデックスのエントリを表しています。

コレクション インデックス登録されるフィールド クエリのスコープ
cities name コレクション
cities state コレクション
cities country コレクション
cities capital コレクション
cities population コレクション
cities name コレクション
cities state コレクション
cities country コレクション
cities capital コレクション
cities population コレクション
cities array-contains regions コレクション

単一フィールド インデックスでサポートされるクエリ

自動的に作成されるこれらの単一フィールド インデックスを使用すると、次のようなシンプルなクエリを実行できます。

ウェブ
const stateQuery = citiesRef.where("state", "==", "CA");
const populationQuery = citiesRef.where("population", "<", 100000);
const nameQuery = citiesRef.where("name", ">=", "San Francisco");

in クエリと複合等価(==)クエリを作成することもできます。

ウェブ
citiesRef.where('country', 'in', ["USA", "Japan", "China"])

// Compound equality queries
citiesRef.where("state", "==", "CO").where("name", "==", "Denver")
citiesRef.where("country", "==", "USA")
         .where("capital", "==", false)
         .where("state", "==", "CA")
         .where("population", "==", 860000)

範囲比較(<<=>>=)を使用する複合クエリを実行する場合や、他のフィールドに基づいて並べ替える場合は、そのクエリ用の複合インデックスを作成する必要があります。

array-contains インデックスを使用すると、regions 配列フィールドをクエリできます。

ウェブ
citiesRef.where("regions", "array-contains", "west_coast")
// array-contains-any and array-contains use the same indexes
citiesRef.where("regions", "array-contains-any", ["west_coast", "east_coast"])

複合インデックスでサポートされるクエリ

Cloud Firestore では複合インデックスを使用して、単一フィールド インデックスではサポートされない複合クエリをサポートします。たとえば、次のクエリには複合インデックスが必要です。

ウェブ
citiesRef.where("country", "==", "USA").orderBy("population", "asc")
citiesRef.where("country", "==", "USA").where("population", "<", 3800000)
citiesRef.where("country", "==", "USA").where("population", ">", 690000)
// in and == clauses use the same index
citiesRef.where("country", "in", ["USA", "Japan", "China"])
         .where("population", ">", 690000)

これらのクエリには、以下に示す複合インデックスが必要です。クエリでは country フィールドに対して等式(== または in)を使用しているため、このフィールドのインデックス モードは降順と昇順のどちらでもかまいません。デフォルトでは、不等式句は、その不等式句のフィールドに基づいて昇順の並べ替えを適用します。

コレクション インデックス登録されるフィールド クエリのスコープ
cities (or ) country, population コレクション

同じクエリを実行するけれども並べ替えが降順の場合は、population 用に降順方向の複合インデックスを追加する必要があります。

ウェブ
citiesRef.where("country", "==", "USA").orderBy("population", "desc")

citiesRef.where("country", "==", "USA")
         .where("population", "<", 3800000)
         .orderBy("population", "desc")

citiesRef.where("country", "==", "USA")
         .where("population", ">", 690000)
         .orderBy("population", "desc")

citiesRef.where("country", "in", ["USA", "Japan", "China"])
         .where("population", ">", 690000)
         .orderBy("population", "desc")
コレクション インデックス登録されるフィールド クエリのスコープ
cities country, population コレクション
cities country, population コレクション

array-contains または array-contains-any クエリと他の句を組み合わせる場合も、複合インデックスを作成する必要があります。

ウェブ
citiesRef.where("regions", "array-contains", "east_coast")
         .where("capital", "==", true)

// array-contains-any and array-contains use the same index
citiesRef.where("regions", "array-contains-any", ["west_coast", "east_coast"])
         .where("capital", "==", true)
コレクション インデックス登録されるフィールド クエリのスコープ
cities array-contains tags, (or ) capital コレクション

コレクション グループ インデックスでサポートされるクエリ

コレクション グループのスコープがあるインデックスを示すために、いくつかの city ドキュメントに landmarks サブコレクションを追加するとします。

ウェブ
var citiesRef = db.collection("cities");

citiesRef.doc("SF").collection("landmarks").doc().set({
    name: "Golden Gate Bridge",
    category : "bridge" });
citiesRef.doc("SF").collection("landmarks").doc().set({
    name: "Golden Gate Park",
    category : "park" });

citiesRef.doc("DC").collection("landmarks").doc().set({
    name: "National Gallery of Art",
    category : "museum" });
citiesRef.doc("DC").collection("landmarks").doc().set({
    name: "National Mall",
    category : "park" });

次のコレクションのスコープの単一フィールド インデックスを使用すると、category フィールドに基づいて単一の都市の landmarks コレクションに対してクエリを実行できます。

コレクション インデックス登録されるフィールド クエリのスコープ
landmarks (or ) category コレクション
ウェブ
citiesRef.doc("SF").collection("landmarks").where("category", "==", "park")
citiesRef.doc("SF").collection("landmarks").where("category", "in", ["park", "museum"])

たとえば、クエリですべての都市でのランドマークを調べるとします。すべての landmarks コレクションで構成されるコレクション グループに対してこのクエリを実行するには、コレクション グループのスコープがある landmarks 単一フィールド インデックスを有効にする必要があります。

コレクション インデックス登録されるフィールド クエリのスコープ
landmarks (or ) category コレクション グループ

このインデックスを有効にすると、landmarks コレクション グループに対してクエリを実行できます。

ウェブ
var landmarksGroupRef = db.collectionGroup("landmarks");

landmarksGroupRef.where("category", "==", "park")
landmarksGroupRef.where("category", "in", ["park", "museum"])

フィルタ処理された結果、もしくは並べ替えられた結果を返すコレクション グループ クエリを実行するには、コレクション グループのスコープに対応する単一フィールド インデックスまたは複合インデックスを有効にする必要があります。ただし、結果のフィルタ処理や並べ替えを行わないコレクション グループ クエリでは、追加のインデックス定義は必要ありません。

たとえば、追加のインデックスを有効にせずに次のコレクション グループ クエリを実行できます。

ウェブ
db.collectionGroup("landmarks").get()

インデックス エントリの数

プロジェクトの構成済みインデックスとドキュメントの構造が、ドキュメントのインデックス エントリの数を左右します。インデックス エントリは、インデックス エントリ数の上限にカウントされます。

次の例は、ドキュメントのインデックス エントリを示しています。

ドキュメント

/cities/SF

city_name : "San Francisco"
temperatures : {summer: 67, winter: 55}
neighborhoods : ["Mission", "Downtown", "Marina"]

単一フィールド インデックス

  • city_name 昇順
  • city_name 降順
  • temperatures.summer 昇順
  • temperatures.summer 降順
  • temperatures.winter 昇順
  • temperatures.winter 降順
  • neighborhoods 配列の内容インデックス(昇順および降順)

複合インデックス

  • city_name 昇順、neighborhoods 配列
  • city_name 降順、neighborhoods 配列

インデックス エントリの数

このインデックス構成により、このドキュメントには次の 18 個のインデックス エントリが作成されます。

インデックス インデックス登録されたデータ
単一フィールド インデックス エントリ
city_name 昇順 city_name: "San Francisco"
city_name 降順 city_name: "San Francisco"
temperatures.summer 昇順 temperatures.summer: 67
temperatures.summer 降順 temperatures.summer: 67
temperatures.winter 昇順 temperatures.winter: 55
temperatures.winter 降順 temperatures.winter: 55
neighborhoods 配列の内容インデックス昇順 neighborhoods: "Mission"
neighborhoods 配列の内容インデックス降順 neighborhoods: "Mission"
neighborhoods 配列の内容インデックス昇順 neighborhoods: "Downtown"
neighborhoods 配列の内容インデックス降順 neighborhoods: "Downtown"
neighborhoods 配列の内容インデックス昇順 neighborhoods: "Marina"
neighborhoods 配列の内容インデックス降順 neighborhoods: "Marina"
複合インデックスのエントリ
city_name 昇順、neighborhoods 配列 city_name: "San Francisco"、neighborhoods: "Mission"
city_name 昇順、neighborhoods 配列 city_name: "San Francisco"、neighborhoods: "Downtown"
city_name 昇順、neighborhoods 配列 city_name: "San Francisco"、neighborhoods: "Marina"
city_name 降順、neighborhoods 配列 city_name: "San Francisco"、neighborhoods: "Mission"
city_name 降順、neighborhoods 配列 city_name: "San Francisco"、neighborhoods: "Downtown"
city_name 降順、neighborhoods 配列 city_name: "San Francisco"、neighborhoods: "Marina"

インデックスと料金

インデックスはアプリケーションのストレージの費用の対象です。インデックスのストレージ サイズの計算方法の詳細については、インデックス エントリのサイズをご覧ください。

インデックス マージの活用

Cloud Firestore ではすべてのクエリにインデックスを使用しますが、クエリごとに必ず 1 つのインデックスが必要になるわけではありません。複数の等式(==)句(およびオプションで orderBy 句)が含まれるクエリに対しては、Cloud Firestore は既存のインデックスを再利用できます。Cloud Firestore では、シンプルな等式フィルタのインデックスをマージして、より大規模な等式クエリに必要な複合インデックスを作成できます。

インデックス マージを利用できる状況を特定すると、インデックスの費用を削減できます。たとえば、レストランを評価するアプリに restaurants というコレクションがある場合を考えます。

  • restaurants

    • burgerthyme

      name : "Burger Thyme"
      category : "burgers"
      city : "San Francisco"
      editors_pick : true
      star_rating : 4

ここで、このアプリは以下のようなクエリを使用するとします。categorycityeditors_pick に等式句の組み合わせを使用し、並べ替えは常に star_rating の昇順です。

ウェブ
db.collection("restaurants").where("category", "==", "burgers")
                            .orderBy("star_rating")

db.collection("restaurants").where("city", "==", "San Francisco")
                            .orderBy("star_rating")

db.collection("restaurants").where("category", "==", "burgers")
                            .where("city", "==", "San Francisco")
                            .orderBy("star_rating")

db.collection("restaurants").where("category", "==", "burgers")
                            .where("city", "==" "San Francisco")
                            .where("editors_pick", "==", true )
                            .orderBy("star_rating")

それぞれのクエリに対してインデックスを作成するという方法が考えられます。

コレクション インデックス登録されるフィールド クエリのスコープ
restaurants category, star_rating コレクション
restaurants city, star_rating コレクション
restaurants category, city, star_rating コレクション
restaurants category, city, editors_pick, star_rating コレクション

より適切なソリューションとして、等式句のインデックスをマージする Cloud Firestore の機能を利用することにより、インデックスの数を減らすことができます。

コレクション インデックス登録されるフィールド クエリのスコープ
restaurants category, star_rating コレクション
restaurants city, star_rating コレクション
restaurants editors_pick, star_rating コレクション

このインデックス セットはサイズが小さくなっただけでなく、次のような追加のクエリもサポートします。

ウェブ
db.collection("restaurants").where("editors_pick", "==", true)
                            .orderBy("star_rating")

インデックスの上限

インデックスには以下の上限が適用されます。すべての割り当てと上限の詳細については、割り当てと上限をご覧ください。

上限 詳細
データベース 1 つあたりの複合インデックスの最大数
データベース 1 つあたりの単一フィールド構成の最大数

1 つのフィールド レベルの構成に、同じフィールドの構成を複数含めることができます。たとえば、単一フィールド インデックス除外と、同じフィールドに対する TTL ポリシーは、上限に対して 1 つのフィールド構成としてカウントされます。

ドキュメントごとのインデックス エントリの最大数

40,000

インデックス エントリの数は、ドキュメントに関する次の数の合計です。

  • 単一フィールド インデックス エントリ数
  • 複合インデックス エントリ数

Cloud Firestore がドキュメントと一連のインデックスをインデックス エントリに変換する仕組みについては、このインデックス エントリ数の例をご覧ください。

複合インデックス内のフィールドの最大数 100
インデックス エントリの最大サイズ

7.5 KiB

Cloud Firestore でインデックス エントリのサイズを計算する方法については、インデックス エントリのサイズをご覧ください。

ドキュメントのインデックス エントリの最大合計サイズ

8 MiB

合計サイズは、ドキュメントに関する次の数の合計です。

  • ドキュメントの単一フィールド インデックス エントリの合計サイズ
  • ドキュメントの複合インデックス エントリの合計サイズ
  • インデックス登録されるフィールド値の最大サイズ

    1,500 バイト

    1,500 バイトを超えるフィールド値は切り捨てられます。切り捨てられたフィールド値が含まれるクエリからは、整合性のない結果が返されることがあります。

    インデックスに関するベスト プラクティス

    ほとんどのアプリでは、自動インデックス作成とエラー メッセージのリンクを使用してインデックスを管理できます。ただし、以下のケースについては、単一フィールド除外を追加したほうがいい場合があります。

    ケース 説明
    大きな文字列フィールド

    文字列フィールドに長い文字列値を保持することが多く、その文字列値がクエリには使用されない場合、インデックス作成からそのフィールドを除外することでストレージの費用を削減できます。

    コレクションへの書き込みレートが高く、連続した値を持つドキュメントが含まれる

    コレクション内のドキュメント間で順次に増加または減少するフィールド(タイムスタンプなど)のインデックスを作成する場合は、コレクションへの最大書き込みレートは 500 回/秒です。連続した値を持つフィールドに基づいてクエリを実行することがない場合は、そのフィールドをインデックス作成から除外すれば、この上限を回避できます。

    たとえば、書き込みレートが高い IoT のユースケースでは、タイムスタンプ フィールドを持つドキュメントを含むコレクションは、500 回/秒の書き込み上限に近づく可能性があります。

    TTL フィールド

    TTL(有効期間)ポリシーを使用する場合は、TTL フィールドをタイムスタンプにする必要があります。TTL フィールドのインデックス登録はデフォルトで有効になっており、トラフィック レートが高くなるとパフォーマンスに影響する可能性があります。TTL フィールドには、単一フィールド除外を追加することをおすすめします。

    大規模な配列フィールドまたはマップ フィールド

    大規模な配列フィールドまたはマップ フィールドの場合、インデックス エントリの上限となっている、ドキュメントあたり 40,000 件に近づく可能性があります。大規模な配列フィールドまたはマップ フィールドに基づいてクエリを実行することがない場合は、そのフィールドをインデックス作成から除外してください。

    インデックスの問題を解決する方法(インデックス ファンアウト、INVALID_ARGUMENT エラー)について詳しくは、トラブルシューティング ページをご覧ください。