Software Development

Firestore: Read/Write Optimization Strategies

Cloud Firestore is a highly scalable NoSQL database, but improper usage can lead to slow queries, high costs, and scalability bottlenecks. This guide covers advanced indexing strategies, query optimization, and cost-efficient techniques to maximize Firestore performance in production.

1. Understanding Firestore’s Performance Fundamentals

How Firestore Handles Reads, Writes, and Indexes

  • Reads:
    • Document reads are billed per document (not per query).
    • Small, frequent reads can be more expensive than batched operations.
  • Writes:
    • Atomic transactions help maintain consistency but increase latency.
    • Batch writes reduce costs and improve performance.
  • Indexes:
    • Automatic indexing works for simple queries but requires composite indexes for complex filtering.

Key Bottlenecks to Watch

  • Hotspots (frequent writes to the same document/collection).
  • Inefficient queries (unbounded result sets, lack of indexing).
  • Excessive document reads (reading full documents when only a few fields are needed).

2. Optimizing Reads for Speed and Cost Efficiency

A. Use Selective Field Fetching

Instead of retrieving entire documents, fetch only necessary fields:

1
2
3
4
5
6
7
// Bad: Reads entire document 
const snapshot = await db.collection('users').doc('user1').get(); 
 
// Good: Fetches only 'name' and 'email' 
const snapshot = await db.collection('users').doc('user1').get({ 
  select: ['name', 'email'
}); 

B. Implement Pagination (Limit & Offset)

Avoid unbounded queries with limit() and startAfter():

01
02
03
04
05
06
07
08
09
10
11
const firstPage = await db.collection('posts'
  .orderBy('timestamp'
  .limit(10) 
  .get(); 
 
const lastVisible = firstPage.docs[firstPage.docs.length - 1]; 
const nextPage = await db.collection('posts'
  .orderBy('timestamp'
  .startAfter(lastVisible) 
  .limit(10) 
  .get();  

C. Use Caching (Firestore Local Cache)

Enable offline persistence to reduce read costs:

1
2
// Enable caching in Firebase 
firebase.firestore().enablePersistence();   

3. Optimizing Writes for High Throughput

A. Batch Writes to Reduce Operations

Group multiple writes into a single batch:

1
2
3
4
5
6
const batch = db.batch(); 
 
batch.set(db.collection('users').doc('user1'), { name: 'Alice' }); 
batch.set(db.collection('users').doc('user2'), { name: 'Bob' }); 
 
await batch.commit(); // 1 write operation instead of 2    

B. Avoid Hotspots with Distributed Counters

For high-frequency counters (e.g., likes, views), shard writes across multiple documents:

01
02
03
04
05
06
07
08
09
10
// Instead of: 
await db.collection('posts').doc('post1').update({ 
  likes: firebase.firestore.FieldValue.increment(1) 
}); 
 
// Use sharded counters: 
const shardId = Math.floor(Math.random() * 10); 
await db.collection('posts').doc('post1').collection('likes').doc(`shard_${shardId}`).update({ 
  count: firebase.firestore.FieldValue.increment(1) 
});   

C. Use Transactions Wisely

Transactions lock documents, so minimize their scope:

1
2
3
4
5
6
const postRef = db.collection('posts').doc('post1'); 
 
await db.runTransaction(async (t) => { 
  const doc = await t.get(postRef); 
  t.update(postRef, { views: doc.data().views + 1 }); 
});  

4. Advanced Indexing Strategies

A. Create Composite Indexes for Complex Queries

Firestore requires composite indexes for multi-field queries:

1
2
3
4
5
// This query requires a composite index on ['category', 'timestamp'] 
const query = db.collection('products'
  .where('category', '==', 'electronics'
  .orderBy('timestamp', 'desc'
  .limit(10);   

To create an index:

  1. Check Firebase Console → Firestore → Indexes.
  2. Define the composite index manually if auto-indexing fails.

B. Avoid Expensive Query Patterns

❌ Bad: != (inequality), OR conditions (not natively supported).
✅ Good: Use multiple queries and merge results client-side.

C. Use Collection Group Queries Sparingly

Collection group queries scan all subcollections, increasing cost:

1
2
3
4
// Searches all 'comments' subcollections (expensive!) 
const comments = await db.collectionGroup('comments'
  .where('userId', '==', 'user1'
  .get();    

Optimization: Restrict with where() and limit().

5. Cost Optimization Strategies

A. Monitor and Reduce Read Operations

  • Use Firebase Usage Dashboard to track expensive queries.
  • Replace real-time listeners with polling where possible.

B. Delete Unused Indexes

  • Unused indexes still incur storage costs.
  • Audit indexes in Firebase Console → Firestore → Indexes.

C. Use Firestore Emulator for Testing

Before deploying, test queries locally:

1
firebase emulators:start --only firestore      

6. Conclusion

Optimizing Firestore requires smart indexing, efficient queries, and batch operations to balance performance and cost. By following these best practices, you can scale Firestore effectively while minimizing expenses.

Further Reading

Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button