Zur Konsole

Index types in Cloud Firestore

Indexes are an important factor in the performance of a database. Much like the index of a book which maps topics in a book to page numbers, a database index maps the items in a database to their locations in the database. When you send a database a query, the database can use an index to quickly look up the locations of the items you requested.

This page describes the two types of indexes that Cloud Firestore uses, single-field indexes and composite indexes.

An index behind every query

If no index exists for a query, most databases crawl through their contents item by item, a slow process that slows down even more as the database grows. Cloud Firestore guarantees high query performance by using indexes for all queries. As a result, query performance depends on the size of the result set and not on the number of items in the database.

Less index management, more app development

Cloud Firestore includes features that reduce the amount of time you need to spend managing indexes. The indexes required for the most basic queries are automatically created for you. As you use and test your app, Cloud Firestore helps you identify and create additional indexes your app requires.

Index types

Cloud Firestore uses two types of indexes: single-field and composite. Besides the number of fields indexed, single-field and composite indexes differ in how you manage them.

Single-field indexes

A single-field index stores a sorted mapping of all the documents in a collection that contain a specific field. Each entry in a single-field index records a document's value for a specific field and the location of the document in the database. Cloud Firestore uses these indexes to perform many basic queries. You manage single-field indexes by configuring your database's automatic indexing settings and index exemptions.

Automatic indexing

By default, Cloud Firestore automatically maintains single-field indexes for each field in a document and each subfield in a map. Cloud Firestore uses the following default settings for single-field indexes:

  • For each non-array and non-map field, Cloud Firestore defines two collection-scope single-field indexes, one in ascending mode and one in descending mode.

  • For each map field, Cloud Firestore creates one collection-scope ascending index and one descending index for each non-array and non-map subfield in the map.

  • For each array field in a document, Cloud Firestore creates and maintains a collection-scope array-contains index.

  • Single-field indexes with collection group scope are not maintained by default.

Single-field index exemptions

You can exempt a field from your automatic indexing settings by creating a single-field index exemption. An indexing exemption overrides the database-wide automatic index settings. An exemption can enable a single-field index that your automatic indexing settings would otherwise disable or disable a single-field index that automatic indexing would otherwise enable. For cases where exemptions can be useful, see the indexing best practices.

If you create a single-field index exemption for a map field, the map's subfields inherit those settings. You can, however, define single-field index exemptions for specific subfields. If you delete an exemption for a subfield, the subfield will inherit its parent's exemption settings, if they exist, or the database-wide settings if no parent exemptions exist.

To create and manage single-field index exemptions, see Managing Indexes in Cloud Firestore.

Composite indexes

A composite index stores a sorted mapping of all the documents in a collection that contain multiple specific fields instead of just one.

Cloud Firestore uses composite indexes to support queries not already supported by single-field indexes.

Cloud Firestore does not automatically create composite indexes like it does for single-field indexes because of the large number of possible field combinations. Instead, Cloud Firestore helps you identify and create required composite indexes as you build your app.

If you attempt the query above without first creating the required index, Cloud Firestore returns an error message containing a link you can follow to create the missing index. This happens any time you attempt a query not supported by an index. You can also define and manage composite indexes manually by using the console or by using the Firebase CLI. For more on creating and managing composite indexes, see Managing Indexes.

Index modes and query scopes

You configure single-field and composite indexes differently, but both require that you configure index modes and query scopes for your indexes.

Index modes

When you define an index, you select an index mode for each indexed field. Each field's index mode supports specific query clauses on that field. You can select from the following index modes:

Index mode Description
Ascending arrow_upward Supports <, <=, ==, >=, and > query clauses on the field and supports sorting results in ascending order based on this field value.
Descending arrow_downward Supports <, <=, ==, >=, and > query clauses on the field and supports sorting results in descending order based on this field value.
Array‑contains Supports array_contains query clauses on the field.

Query scopes

Each index is scoped to either a collection or a collection group. This is known as the index's query scope:

Collection scope
Cloud Firestore creates indexes with collection scope by default. These indexes support queries that return results from a single collection.

Collection group scope
A collection group includes all collections with the same collection ID. To run a collection group query that returns filtered or ordered results from a collection group, you must create a corresponding index with collection group scope.

Indexing example

By automatically creating single-field indexes for you, Cloud Firestore allows your application to quickly support the most basic database queries. Single-field indexes allow you to perform simple queries based on field values and the comparators <, <=, ==, >=, and >. For array fields, they allow you to perform array_contains queries.

To illustrate, examine the following examples from the point of view of index creation. The following snippet creates a few city documents in a cities collection and sets name, state, country, capital, population, and tags fields for each document:

Web
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"] });

Assuming the default automatic indexing settings, Cloud Firestore updates one ascending single-field index per non-array field, one descending single- field index per non-array field, and one array-contains single-field index for the array field. Each row in the following table represents an entry in a single-field index:

Collection Field indexed Query scope
cities arrow_upward name Collection
cities arrow_upward state Collection
cities arrow_upward country Collection
cities arrow_upward capital Collection
cities arrow_upward population Collection
cities arrow_downward name Collection
cities arrow_downward state Collection
cities arrow_downward country Collection
cities arrow_downward capital Collection
cities arrow_downward population Collection
cities array-contains regions Collection

Queries supported by single-field indexes

Using these automatically created single-field indexes, you can run simple queries like the following:

Web
citiesRef.where("state", "==", "CA")
citiesRef.where("population", "<", 100000)
citiesRef.where("name", ">=", "San Francisco")

The array_contains index allows you to query the regions array field:

Web
citiesRef.where("regions", "array-contains", "west_coast")

You can also create compound queries based on equalities (==):

Web
citiesRef.where("state", "==", "CO").where("name", "==", "Denver")
citiesRef.where("country", "==", "USA").where("capital", "==", false).where("state", "==", "CA").where("population", "==", 860000)

If you need to run a compound query that uses a range comparison (<, <=, >, or >=) or if you need to sort by a different field, you must create a composite index for that query.

Queries supported by composite indexes

Cloud Firestore uses composite indexes to support compound queries not already supported by single-field indexes. For example, you would need a composite index for the following queries:

Web
citiesRef.where("country", "==", "USA").orderBy("population", "asc")
citiesRef.where("country", "==", "USA").where("population", "<", 3800000)
citiesRef.where("country", "==", "USA").where("population", ">", 690000)

These queries require the composite index below. Note that since the query uses an equality for the country field, the field's index mode can be either descending or ascending. By default, inequality queries will apply an ascending sort order based on the field in the inequality clause.

Collection Fields indexed Query scope
cities arrow_upward (or arrow_downward) country, arrow_upward population Collection

If you want to run the same queries but with a descending sort order, then you need an additional composite index in the descending direction for population:

Web
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")
Collection Fields indexed Query scope
cities arrow_upward country, arrow_upward population Collection
cities arrow_upward country, arrow_downward population Collection

You'll also need to create a composite index if you want to combine an array_contains query with additional clauses.

Web
citiesRef.where("regions", "array_contains", "east_coast")
         .where("capital", "==", true)
Collection Fields indexed Query scope
cities array-contains tags, arrow_upward (or arrow_downward) capital Collection

Queries supported by collection group indexes

To demonstrate an index with collection group scope, imagine you add a landmarks sub-collection to some of the city documents:

Web
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" });

Using the following single-field index with collection scope, you can query for park landmarks within a single city:

Collection Fields indexed Query scope
landmarks arrow_upward (or arrow_downward) category Collection
Web
citiesRef.doc("SF").collection("landmarks").where("category", "==", "park")

Now, imagine that you're interested in park landmarks across all cities. To run this query on the collection group consisting of all landmarks collections, you must enable a landmarks single-field index with collection group scope:

Collection Fields indexed Query scope
landmarks arrow_upward (or arrow_downward) category Collection group

With this index enabled, you can query the landmarks collection group:

Web
var landmarksGroupRef = db.collectionGroup("landmarks");

landmarksGroupRef.where("category", "==", "park")

To run a collection group query that returns filtered or ordered results, you must enable a corresponding single-field or composite index with collection group scope. Collection group queries that don't filter or order results, however, do not require any additional index definitions.

For example, you can run the following collection group query without enabling an additional index:

Web
db.collectionGroup("landmarks").get()

Indexes and pricing

Indexes contribute to the storage costs of your application. For more on how storage size for indexes is calculated, see Index entry size.

Taking advantage of index merging

Although Cloud Firestore uses an index for every query, it does not necessarily require one index per query. For queries with multiple equality (==) clauses and, optionally, an orderBy clause, Cloud Firestore can re-use existing indexes. Cloud Firestore can merge the indexes for simple equality filters to build the composite indexes needed for larger equality queries.

You can reduce indexing costs by identifying situations where you can take advantage of index merging. For example, imagine a restaurants collection for a restaurant rating app:

  • collections_bookmark restaurants

    • class burgerthyme

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

Now, imagine this app uses queries like the ones below. Notice that the app uses combinations of equality clauses for category, city, and editors_pick while always sorting by ascending star_rating:

Web
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")

You could create an index for each query:

Collection Fields indexed Query scope
restaurants arrow_upward category, arrow_upward star_rating Collection
restaurants arrow_upward city, arrow_upward star_rating Collection
restaurants arrow_upward category, arrow_upward city, arrow_upward star_rating Collection
restaurants arrow_upward category, arrow_upward city, arrow_upward editors_pick, arrow_upward star_rating Collection

As a better solution, you can reduce the number of indexes by taking advantage of Cloud Firestore's ability to merge indexes for equality clauses:

Collection Fields indexed Query scope
restaurants arrow_upward category, arrow_upward star_rating Collection
restaurants arrow_upward city, arrow_upward star_rating Collection
restaurants arrow_upward editors_pick, arrow_upward star_rating Collection

Not only is this set of indexes smaller, it also supports an additional query:

Web
db.collection("restaurants").where("editors_pick", "==", true)
                            .orderBy("star_rating")

Indexing limits

The following limits apply to indexes. For all quotas and limits, see Quotas and Limits.

Limit Details
Maximum number of composite indexes for a database 200
Maximum number of single-field index exemptions for a database 200

Maximum number of index entries for each document

40,000

The number of index entries is the sum of the following for a document:

  • The number of single-field index entries
  • The number of composite index entries
Maximum size of an index entry

7.5 KiB

To see how Cloud Firestore calculates index entry size, see index entry size.

Maximum sum of the sizes of a document's index entries

8 MiB

The total size is the sum of the following for a document:

  • The sum of the size of a document's single-field index entries
  • The sum of the size of a document's composite index entries
  • Maximum size of an indexed field value

    1500 bytes

    Field values over 1500 bytes are truncated. Queries involving truncated field values may return inconsistent results.

    Indexing best practices

    For most apps, you can rely on automatic indexing and the error message links to manage your indexes. However, you may want to add single-field exemptions in the following cases:

    Case Description
    Large string fields

    If you have a string field that often holds long string values that you don't use for querying, you can cut storage costs by exempting the field from indexing.

    High write rates to a collection containing documents with sequential values

    If you index a field that increases or decreases sequentially between documents in a collection, like a timestamp, then the maximum write rate to the collection is 500 writes per second. If you don't query based on the field with sequential values, you can exempt the field from indexing to bypass this limit.

    In an IoT use case with a high write rate, for example, a collection containing documents with a timestamp field might approach the 500 writes per second limit.

    Large array or map fields

    Large array or map fields can approach the limit of 20,000 index entries per document. If you are not querying based on a large array or map field, you should exempt it from indexing.