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.

Single-field indexes

A single-field index stores a sorted mapping of every document in a collection with 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 automatically defines and maintains two single-field indexes for each field in a document, one in ascending order (arrow_upward) and one in descending order (arrow_downward). For fields containing map objects, Cloud Firestore creates one ascending index and one descending index for each subfield in the map.

By automatically creating these 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 the field values and the comparators <, <=, ==, >=, and >.

To illustrate, examine the cities examples from the point of view of index creation. The following example creates a few city documents in a cities collection and sets name, state, country, capital, and population 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 });
citiesRef.doc("LA").set({
    name: "Los Angeles", state: "CA", country: "USA",
    capital: false, population: 3900000 });
citiesRef.doc("DC").set({
    name: "Washington, D.C.", state: null, country: "USA",
    capital: true, population: 680000 });
citiesRef.doc("TOK").set({
    name: "Tokyo", state: null, country: "Japan",
    capital: true, population: 9000000 });
citiesRef.doc("BJ").set({
    name: "Beijing", state: null, country: "China",
    capital: true, population: 21500000 });

For each set operation, Cloud Firestore updates two single-field indexes per field. Each row in the following table represents a single-field index:

Collection Field indexed
cities arrow_upward name
cities arrow_downward name
cities arrow_upward state
cities arrow_downward state
cities arrow_upward country
cities arrow_downward country
cities arrow_upward capital
cities arrow_downward capital
cities arrow_upward population
cities arrow_downward population

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

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

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.

Composite indexes

A composite index is like a single-field index, but it stores a sorted mapping of each document in a collection that contains multiple specific fields instead of just one. A composite index also defines a sort mode for each of its fields, ascending or descending, and the index is sorted based these sort modes. 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 sort 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
cities arrow_upward (or arrow_downward) country, arrow_upward population

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 indexes, see Managing Indexes.

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
cities arrow_upward country, arrow_upward population
cities arrow_upward country, arrow_downward population

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
restaurants arrow_upward category, arrow_upward star_rating
restaurants arrow_upward city, arrow_upward star_rating
restaurants arrow_upward category, arrow_upward city, arrow_upward star_rating
restaurants arrow_upward category, arrow_upward city, arrow_upward editors_pick, arrow_upward star_rating

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
restaurants arrow_upward category, arrow_upward star_rating
restaurants arrow_upward city, arrow_upward star_rating
restaurants arrow_upward editors_pick, arrow_upward star_rating

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

Index limitations

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

Limit Details
Maximum sum of the sizes of a document's composite index entries 2 MiB
Maximum number of composite indexes for a database 200

Maximum number of index entries for each document

20,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

Cloud Firestore automatically creates two single-field index entries for each field, one in the ascending index and one in the descending index.

Considering the limit of 20,000, each document can index a maximum of 10,000 fields minus the document's number of composite index entries.

Maximum size of an index entry

1500 bytes

Index entries over 1500 bytes are truncated. Queries involving truncated index values may return inconsistent results.

Send feedback about...

Need help? Visit our support page.