Firestore is a NoSQL database that scales automatically, but poorly optimized queries can be slow and expensive. Here’s how to optimize Firestore queries for speed, cost-efficiency, and scalability.
"The query requires an index."
For querying users by city & age, create an index on { city, age }
.
const usersRef = db.collection("users")
.where("city", "==", "New York")
.where("age", ">=", 25);
* Avoid auto-indexing unnecessary fields to reduce storage costs.
const userSnapshot = await db.collection("users").get();
* This retrieves ALL user data (slow + expensive).
const userSnapshot = await db.collection("users")
.select("name", "email") // Fetch only required fields
.get();
* Less data transfer = faster queries & lower costs.
Fetching large datasets can cause performance bottlenecks.
.limit()
and .startAfter()
for paginationconst firstPage = await db.collection("posts")
.orderBy("timestamp")
.limit(10)
.get();
const lastDoc = firstPage.docs[firstPage.docs.length - 1];
const secondPage = await db.collection("posts")
.orderBy("timestamp")
.startAfter(lastDoc)
.limit(10)
.get();
* Benefits: Avoids fetching unnecessary data, improves UX.
const user1 = db.collection("users").where("id", "==", "123").get();
const user2 = db.collection("users").where("id", "==", "456").get();
array-contains-any
const users = db.collection("users")
.where("id", "in", ["123", "456"])
.get();
* Less read operations = lower cost & better performance.
IN
Queries with Large ListsFirestore allows in
queries with up to 30 elements.
Instead of storing a large array of IDs, create a subcollection.
/groups/{groupId}/members/{userId}
Now query directly:
db.collection("groups").doc("group123").collection("members").get();
* Faster lookups & better scalability.
Instead of multiple separate requests, use batch operations.
const batch = db.batch();
const userRef1 = db.collection("users").doc("user1");
const userRef2 = db.collection("users").doc("user2");
batch.update(userRef1, { points: 100 });
batch.update(userRef2, { points: 200 });
await batch.commit();
* Single request instead of multiple = Faster & Cheaper.
Firestore supports offline persistence.
firebase.firestore().enablePersistence();
* Reduces network calls by storing data locally.
Instead of fetching the entire dataset, use real-time listeners.
db.collection("messages").where("roomId", "==", "123")
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
console.log("New Message: ", change.doc.data());
});
});
* Only gets new/changed data instead of refetching everything.
Optimization | Benefit |
---|---|
Use Indexes | Faster composite queries |
Select only needed fields | Reduces data transfer |
Paginate queries | Avoids large reads |
Use array-contains-any |
Fewer queries, better efficiency |
Batch operations | Reduces network requests |
Enable caching | Faster queries, offline support |
Use real-time listeners | Avoids full dataset fetches |
Optimize Firestore structure | Better performance & scalability |