For the caveman who have yet to know what an index is, a database index is a value stored that maps items in the database to their locations. In other words, this lets queries happen faster as without an index, a query would have to crawl through items in the database one by one and this could be a slow and jarring process for larger databases.
How does Firestore handle indexing?
Fortunately for us, Firestore automatically indexes all queries… but this only applies for the single-field indexing, only one of the index types. So if we want to achieve the most optimal performance with our queries, we have to look into indexing ourselves.
When you define an index, you select a mode and type for the indexed field.
Index Modes
Ascending – Supports <, <=, ==, >=, and > query clauses on the field and supports sorting results in ascending order based on this field value
Descending – 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.
Index Types
Single 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. Firestore automatically handles this type of indexing.
You can exempt a field from this automatic indexing by creating a single-field index exemption in the Firebase Console. It’s considered best practice to do this for large string fields, large array or map fields, and documents with a high write rate.
Take for example. we create a few city documents. (Courtesy of Firebase docs).
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"] });
For each non-array field in each document, Firestore automatic indexing creates an ascending and descending index, then for each array field, it creates an array-contains index.
With these indexes, you can run simple queries like this.
citiesRef.where("state", "==", "CA") citiesRef.where("population", "<", 100000) citiesRef.where("regions", "array-contains", "west_coast")
Or even compound queries based on equalities (==).
citiesRef.where("state", "==", "CO").where("name", "==", "Denver") citiesRef.where("country", "==", "USA").where("capital", "==", false)
However, you can’t run compound queries that use other boolean operators (>, <=, etc.) or sort queries by a different field. That’s where you need to make a composite index.
Composite Index
You can use these to allow you to make more complex queries such as compound queries that use boolean operators (>, <=, etc.) and queries sorted by different fields.
As such, you can perform queries like these:
citiesRef.where("country", "==", "USA").orderBy("population", "desc") citiesRef.where("country", "==", "USA") .where("population", "<", 3800000) .orderBy("population", "desc") citiesRef.where("regions", "array_contains", "east_coast") .where("capital", "==", true)
Do note you can only include one array per composite index.
So why not just Index everything?
Indexes use up storage space. Firebase explains how this storage space is calculated, but as a general idea, most indexes (both types) average to about 80 bytes. It may not seem like much, but if your database is anything sizeable, this could build up to some huge chunks.
Indexing Limits
Let me just completely rip off this table from the docs.
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:
|
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:
|
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. |
Index Merging
For queries with multiple equality clauses (==) and optionally an orderBy clause, Firestore can re-use existing indexes in place of a larger one.
Take for example, this set of queries.
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 | category, star_rating |
restaurants | city, star_rating |
restaurants | category, city, star_rating |
restaurants | category, city, editors_pick, star_rating |
Or you could use a smaller set of indexes where you’ve identified a possible merge.
Collection | Fields indexed |
---|---|
restaurants | category, star_rating |
restaurants | city, star_rating |
restaurants | editors_pick, star_rating |
Disclaimer
Most of the content in this article is ripped off of the Firebase official docs and reworded for viewer ease and convenience. If you want to see a full comprehensive tutorial on the topic, I recommend you go check it out.