{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}?
Feature | Firestore | Realtime Database |
---|---|---|
Data Model | Document-based | JSON Tree |
Scalability | High | Limited |
Offline Support | Yes | Yes |
Querying | Advanced | Limited |
Pricing | Pay per operation | Pay per data size |
Firebase Authentication provides user authentication with various providers like :
It manages user sessions securely and integrates with other Firebase services.
Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that allows developers to send notifications and messages to Android, iOS, and web applications. Here’s how it works:
Firebase Remote Config is a cloud-based service that allows you to change the behavior and appearance of your app dynamically without requiring users to update it. This is useful for A/B testing, feature toggles, and personalization.
setDefaults()
.// Initialize Remote Config
val remoteConfig = FirebaseRemoteConfig.getInstance()
val configSettings = FirebaseRemoteConfigSettings.Builder()
.setMinimumFetchIntervalInSeconds(3600) // Fetch every hour
.build()
remoteConfig.setConfigSettingsAsync(configSettings)
// Set default values
remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
// Fetch and activate values
remoteConfig.fetchAndActivate().addOnCompleteListener { task ->
if (task.isSuccessful) {
val newValue = remoteConfig.getString("welcome_message")
Log.d("RemoteConfig", "Fetched value: $newValue")
}
}
* Feature Flags: Enable/disable features remotely.
* UI Customization: Change colors, layouts, or text dynamically.
* Pricing Adjustments: Modify prices for different regions.
* User Segmentation: Show different content to specific users.
Firebase provides various services like Authentication, Firestore, FCM, and more. Below is a step-by-step guide to integrating Firebase into an Android and iOS app.
com.example.myapp
).google-services.json
file and place it inside:
app/src/main/
build.gradle (Project level)
:
dependencies {
classpath 'com.google.gms:google-services:4.3.10' // Ensure latest version
}
build.gradle (Module level - app)
:
plugins {
id 'com.google.gms.google-services' // Add this at the bottom
}
dependencies {
implementation platform('com.google.firebase:firebase-bom:32.0.0')
implementation 'com.google.firebase:firebase-analytics'
}
MainActivity.kt
(Kotlin):
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
class MainActivity : AppCompatActivity() {
private lateinit var firebaseAnalytics: FirebaseAnalytics
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize Firebase Analytics
firebaseAnalytics = Firebase.analytics
}
}
(Same steps as Android)
com.example.myiosapp
).GoogleService-Info.plist
file.GoogleService-Info.plist
into the Xcode Runner directory.cd ios
pod init
Podfile
and add:
platform :ios, '10.0'
use_frameworks!
pod 'Firebase/Core'
pod install
.xcworkspace
file in Xcode.AppDelegate.swift
and modify:
import UIKit
import Firebase
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
return true
}
}
adb logcat -s Firebase
Xcode → Run App → Debug Console
* Firebase Authentication – User login/sign-up
* Cloud Firestore – Real-time database
* Firebase Cloud Messaging (FCM) – Push notifications
* Firebase Remote Config – Dynamic app updates
Firebase In-App Messaging is a Firebase service that allows you to engage with users who are actively using your app. Essentially, it lets you display targeted and contextual messages to users while they're inside your application. Here's a breakdown of its key aspects:
Purpose :
Key Features :
In essence :
Firebase In-App Messaging is a tool that helps you communicate with your app users in a timely and relevant manner, improving user engagement and driving desired actions.
Firebase In-App Messaging offers several key benefits for app developers and marketers looking to enhance user engagement :
In essence, Firebase In-App Messaging empowers you to create meaningful interactions with your app users, leading to increased engagement, retention, and conversions.
Firebase Cloud Functions is a serverless backend for your Firebase app that allows you to run backend logic in response to events triggered by Firebase services and HTTPS requests.
* Event-driven execution – Respond to Firebase & Google Cloud events.
* Serverless – No need to manage infrastructure.
* Scalable – Automatically scales based on demand.
* Secure – Runs in an isolated cloud environment.
* Integrates with Firebase & external APIs – Can interact with Firestore, FCM, Auth, and more.
* Authentication Triggers – Send welcome emails when users sign up.
* Firestore Triggers – Validate or modify data when added/updated.
* FCM Triggers – Send push notifications on specific events.
* Scheduled Tasks – Run periodic jobs (e.g., cleanup inactive users).
* Custom APIs – Expose REST API endpoints for web & mobile apps.
npm install -g firebase-tools
firebase init functions
Open functions/index.js
and add:
const functions = require("firebase-functions");
// Simple HTTP function
exports.helloWorld = functions.https.onRequest((req, res) => {
res.send("Hello from Firebase Cloud Functions!");
});
firebase deploy --only functions
Once deployed, Firebase provides a URL:
https://us-central1-YOUR_PROJECT.cloudfunctions.net/helloWorld
Open it in a browser or call it from a mobile app.
Send a push notification when a new document is added to Firestore.
const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp();
exports.sendNotification = functions.firestore
.document("messages/{messageId}")
.onCreate(async (snap, context) => {
const messageData = snap.data();
const payload = {
notification: {
title: "New Message!",
body: messageData.text,
click_action: "FLUTTER_NOTIFICATION_CLICK"
}
};
return admin.messaging().sendToTopic("messages", payload);
});
Trigger | Description |
---|---|
HTTPS Requests | Expose REST APIs (onRequest ) |
Firestore | Listen for document changes (onCreate , onUpdate , onDelete ) |
Authentication | React to user sign-ups or deletions (onCreate , onDelete ) |
Firebase Storage | Run functions on file uploads (onFinalize , onDelete ) |
Firebase Messaging (FCM) | Process push notifications (onMessagePublished ) |
Scheduled Functions | Run tasks on a cron schedule (pubsub.schedule ) |
firebase functions:log
).{ uid, name, email }
).{ postId, userId, content, timestamp }
).{ commentId, userId, text }
).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 |
To fetch the 10 most recent posts from Firestore, assuming each post has a timestamp
field (e.g., createdAt
with a Firestore Timestamp), you can use the .orderBy()
and .limit()
functions.
const db = firebase.firestore();
const postsRef = db.collection("posts")
.orderBy("createdAt", "desc") // Order by timestamp (newest first)
.limit(10); // Limit to 10 posts
const snapshot = await postsRef.get();
const posts = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
console.log(posts);
* orderBy("createdAt", "desc")
→ Sorts posts by createdAt
in descending order (newest first).
* .limit(10)
→ Fetches only the latest 10 posts, improving efficiency.
* snapshot.docs.map(doc => doc.data())
→ Converts Firestore snapshot to a usable JavaScript array.
If you need pagination (e.g., fetching older posts), you can use .startAfter(lastDoc)
:
const firstBatch = await db.collection("posts")
.orderBy("createdAt", "desc")
.limit(10)
.get();
const lastDoc = firstBatch.docs[firstBatch.docs.length - 1];
// Fetch the next batch of posts
const secondBatch = await db.collection("posts")
.orderBy("createdAt", "desc")
.startAfter(lastDoc) // Start after last fetched document
.limit(10)
.get();
* This ensures efficient pagination instead of reloading everything.
firebase.firestore().enablePersistence();
firebase.database().ref(".info/connected").on("value", (snapshot) => {
console.log("Connected:", snapshot.val());
});
const message = {
notification: {
title: 'New Post',
body: 'Check out this update!'
},
token: 'user-device-token'
};
admin.messaging().send(message)
.then((response) => console.log('Sent:', response))
.catch((error) => console.error('Error:', error));?
Firebase offers a pay-as-you-go pricing model with two main plans:
Feature | Spark (Free) | Blaze (Pay-as-you-go) |
---|---|---|
Reads | 50,000/month | $0.06 per 100,000 |
Writes | 20,000/month | $0.18 per 100,000 |
Deletes | 20,000/month | $0.02 per 100,000 |
Storage | 1GB Free | $0.18 per GB/month |
Network Egress | 10GB Free | $0.12 per GB |
* Optimization Tip: Minimize reads by using caching, pagination, and selective field queries.
Feature | Spark (Free) | Blaze (Pay-as-you-go) |
---|---|---|
Simultaneous Connections | 100 | 200,000+ |
Data Storage | 1GB Free | $5 per GB/month |
Download Costs | 10GB Free | $1 per GB |
* Optimization Tip: Use Firestore for scalable queries; Realtime DB is better for frequent real-time updates.
Feature | Spark (Free) | Blaze (Pay-as-you-go) |
---|---|---|
Email/Google/Auth Providers | Free | Free |
Phone Authentication | 10K verifications/month | $0.01–$0.05 per verification |
* Optimization Tip: Prefer email/password for authentication to reduce costs.
Feature | Spark (Free) | Blaze (Pay-as-you-go) |
---|---|---|
Storage | 1GB Free | $0.026 per GB |
Download (Egress) | 10GB Free | $0.12 per GB |
* Optimization Tip: Use compression, optimize file sizes, and store images in lower resolutions.
Feature | Spark (Free) | Blaze (Pay-as-you-go) |
---|---|---|
Hosting Storage | 10GB Free | $0.026 per GB |
Bandwidth | 10GB Free | $0.15 per GB |
* Optimization Tip: Use caching, lazy loading, and CDN for lower bandwidth usage.
Feature | Spark (Free) | Blaze (Pay-as-you-go) |
---|---|---|
Invocations | 2M Free/month | $0.40 per million |
CPU & Memory | 400K GB-seconds Free | $0.0000025 per GB-s |
Network Egress | 10GB Free | $0.12 per GB |
* Optimization Tip: Use scheduled functions to batch tasks instead of triggering functions too frequently.
* Unlimited free push notifications across Android, iOS, and web.
npm install firebase?
import { initializeApp } from "firebase/app";
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
const firebaseConfig = { /* Your config here */ };
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);?
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
Building a high-performance, scalable chat app using Firebase requires optimized database design, efficient queries, and cost-effective strategies. Below are best practices to handle millions of users and messages while keeping performance high and costs low.
Feature | Firestore | Realtime Database |
---|---|---|
Scalability | Horizontally scales | Limited scaling |
Query Performance | Indexed queries | Nested queries slow down |
Cost Efficiency | Pay-per-use (reads/writes) | Charges for data size & connections |
Offline Support | Strong caching | Basic caching |
To handle high throughput, structure data for fast reads & minimal writes.
/chats/{chatId}/messages/{messageId} <-- Subcollection for scalability
/chats/{chatId}/members/{userId} <-- Tracks members in a chat
/users/{userId} <-- Stores user profile & last seen
/chats/chat123
{
"chatName": "Developers Group",
"lastMessage": "Hello!",
"lastMessageTime": 1700000000,
"members": ["userA", "userB"]
}
/chats/chat123/messages/messageXYZ
{
"senderId": "userA",
"text": "Hello!",
"timestamp": 1700000000
}
/users/userA
{
"name": "John Doe",
"profilePic": "url",
"lastSeen": 1700000000
}
/messages/{messageId}
instead of arrays)?Large-scale chats require pagination & indexing.
const messagesRef = db.collection("chats/chat123/messages")
.orderBy("timestamp", "desc")
.limit(20);
const snapshot = await messagesRef.get();
const messages = snapshot.docs.map(doc => doc.data());
startAfter()
for pagination :const lastDoc = snapshot.docs[snapshot.docs.length - 1];
const nextPage = await db.collection("chats/chat123/messages")
.orderBy("timestamp", "desc")
.startAfter(lastDoc)
.limit(20)
.get();
Use onSnapshot() to get real-time updates without excessive reads.
db.collection("chats/chat123/messages")
.orderBy("timestamp", "desc")
.limit(10)
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === "added") {
console.log("New message:", change.doc.data());
}
});
});
* Reduces Firestore reads by only fetching new messages instead of reloading everything.
Since Firestore doesn’t support real-time presence, use Realtime Database.
/status/userA
{
"online": true,
"lastSeen": 1700000000
}
const userStatusRef = firebase.database().ref("/status/userA");
firebase.auth().onAuthStateChanged(user => {
if (user) {
userStatusRef.set({ online: true });
window.addEventListener("beforeunload", () => {
userStatusRef.set({ online: false, lastSeen: Date.now() });
});
}
});
* Keeps presence updates fast while storing user messages in Firestore.
Use Firebase Cloud Messaging (FCM) to notify users of new messages.
const admin = require("firebase-admin");
exports.sendMessageNotification = functions.firestore
.document("chats/{chatId}/messages/{messageId}")
.onCreate(async (snap, context) => {
const messageData = snap.data();
const payload = {
notification: {
title: "New Message!",
body: messageData.text,
click_action: "FLUTTER_NOTIFICATION_CLICK"
},
token: "USER_FCM_TOKEN"
};
await admin.messaging().send(payload);
});
* Push notifications keep users engaged without excessive polling.
.select()
.onSnapshot()
instead of polling.lastMessage
for every message).const userRef = db.collection('users').doc('userId');
userRef.update({ name: 'New Name' })
.then(() => console.log('Updated'))
.catch((error) => console.error(error));
Securing Firebase databases (Firestore & Realtime Database) is crucial to prevent data leaks, unauthorized modifications, and excessive billing. Here’s how to lock down access effectively.
Firestore security rules define who can read, write, or update data.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Require authentication for all collections
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
* Only authenticated users can access Firestore.
* Prevents public access (even with a direct API request).
Use custom claims to assign roles like admin, user, moderator.
On the backend, set user roles:
const admin = require("firebase-admin");
async function setAdminRole(uid) {
await admin.auth().setCustomUserClaims(uid, { role: "admin" });
console.log("Admin role assigned!");
}
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth.uid == userId;
}
match /admin/{document=**} {
allow read, write: if request.auth.token.role == "admin";
}
}
}
* Users can only read their own data.
* Admins have full access to specific documents.
If some data should be public (e.g., blog posts), but writes should be restricted, modify the rules:
match /posts/{postId} {
allow read: if true; // Public read access
allow write: if request.auth != null; // Only authenticated users can write
}
* Anyone can read, but only authenticated users can create posts.
Prevent malicious writes by validating data types and structure.
match /chats/{chatId}/messages/{messageId} {
allow create: if request.auth != null &&
request.resource.data.text is string &&
request.resource.data.text.size() < 500;
}
* Ensures only authenticated users can send messages, with a max of 500 characters.
For Realtime Database, define strict rules:
{
"rules": {
".read": "auth != null", // Only authenticated users can read
".write": "auth != null" // Only authenticated users can write
}
}
* Prevents public access.
* By default, Realtime Database allows anyone to read/write (dangerous!).
Ensure users can only upload their own profile pictures.
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /profile_pictures/{userId}/{fileName} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
* Users can only upload/read their own files.
Ensure only verified users can access data:
Prevent spamming by limiting writes per user.
match /messages/{messageId} {
allow create: if request.auth != null &&
request.time > request.auth.token.lastWriteTime + duration.seconds(5);
}
* Users can only send a message every 5 seconds.
* Enable it in Firebase Console > App Check > Add Provider (Play Integrity, reCAPTCHA, etc.).
Security Measure | Firestore Rules Example |
---|---|
Require Authentication | allow read, write: if request.auth != null; |
Role-Based Access (RBAC) | allow write: if request.auth.token.role == "admin"; |
Restrict User Data Access | allow read: if request.auth.uid == userId; |
Limit Public Access | allow read: if true; allow write: if request.auth != null; |
Validate Input Data | request.resource.data.text is string && size() < 500; |
Secure Storage Files | allow write: if request.auth.uid == userId; |
Limit Request Frequency | request.time > request.auth.token.lastWriteTime + duration.seconds(5); |
If Firebase Analytics shows a high drop-off rate on a specific screen, it means users are exiting at that point, possibly due to bad UX, slow performance, or unclear content. Here’s how to diagnose & fix the issue.
* Symptom: Users drop off before interacting.
Fix :
* Optimize Firestore queries (use .select()
, indexing, pagination).
* Reduce image size (use Firebase Storage & lazy loading).
* Cache data using Firestore Caching or Local Storage.
* Use Cloud Functions to pre-process heavy tasks.
* Symptom: Users navigate away quickly without interacting.
Fix :
* Improve button visibility (increase contrast & placement).
* Add progress indicators if loading takes time.
* Use clear CTAs (Call to Actions) to guide users.
* A/B test different layouts using Firebase Remote Config.
* Symptom: App crashes or users exit after performing an action.
Fix :
* Check Crashlytics logs for errors on that screen.
* Test on low-end devices (UI issues on older phones?).
* Handle network errors gracefully (show retry options).
* Symptom: Users don’t interact much before leaving.
Fix :
* Add onboarding tooltips to explain key features.
* Show micro-interactions (e.g., button animations for engagement).
* Use A/B testing (Firebase Remote Config) to compare different versions.
* Symptom: Users leave halfway through a form or checkout.
Fix :
* Break long forms into steps (use progress bars).
* Autofill & suggest inputs to speed up user entry.
* Reduce mandatory fields (ask only for necessary data).
Before rolling out changes to all users, use Firebase Remote Config to A/B test improvements.
const buttonPosition = remoteConfig().getValue('button_position');
document.getElementById('cta-button').style.position = buttonPosition;
* Compare "top" vs. "bottom" button placements and see which gets better engagement.
After making changes, track analytics again:
Issue | Fix |
---|---|
Slow Load Times | Optimize queries, cache data, reduce image sizes |
Confusing UI | Improve button placement, add CTAs, A/B test |
Crashes/Bugs | Check Crashlytics, fix network handling, test on devices |
Unclear Purpose | Add onboarding tips, interactive tooltips |
Long Forms | Use step-by-step forms, autofill, remove unnecessary fields |
Firestore is powerful, but inefficient queries can increase costs, slow performance, and hit usage limits. Here’s how to optimize queries for large datasets effectively.
Firestore doesn’t support joins like SQL, so data modeling is crucial.
Instead of storing all messages inside a chat document (bad for scalability):
* Use subcollections for messages:
/chats/{chatId}/messages/{messageId}
Firestore automatically indexes fields used in simple queries, but complex queries (e.g., multiple where
conditions or orderBy
) require composite indexes.
db.collection("orders")
.where("status", "==", "shipped")
.where("customerId", "==", "user123")
.orderBy("orderDate", "desc")
.limit(10);
* Firestore will reject this query unless you create a composite index.
status
(Filter)customerId
(Filter)orderDate
(Sort: Descending).select()
to Reduce Data TransferBy default, Firestore returns all fields in a document, even if you don’t need them.
db.collection("users")
.select("name", "email") // Fetch only name & email (not full profile)
.get();
* Reduces data transfer cost and query execution time.
.startAfter()
Fetching too much data at once can cause slow performance and expensive reads.
let firstQuery = db.collection("posts")
.orderBy("createdAt", "desc")
.limit(20);
let snapshot = await firstQuery.get();
let lastVisible = snapshot.docs[snapshot.docs.length - 1]; // Get last doc
let nextQuery = db.collection("posts")
.orderBy("createdAt", "desc")
.startAfter(lastVisible) // Fetch next page
.limit(20);
Firestore processes where()
before orderBy()
.
Mistake : Ordering first and then filtering results in errors.
db.collection("products")
.where("category", "==", "electronics") // Filter first
.orderBy("price", "asc") // Then sort
.get();
!=
& Array Queries When PossibleFirestore does not support !=
queries directly, and array queries can be costly.
db.collection("users")
.where("role", "!=", "admin") // Firestore does not support '!='
.get();
array-contains-any
db.collection("users")
.where("role", "in", ["editor", "viewer"]) // Instead of '!='
.get();
in
(max 10 values) but not !=
.If old data isn’t needed, automate deletion to reduce query size.
timestamp
in documents.exports.cleanupOldLogs = functions.pubsub.schedule('every 24 hours').onRun(async () => {
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000; // 30 days ago
const query = db.collection("logs").where("createdAt", "<", cutoff);
const snapshot = await query.get();
let batch = db.batch();
snapshot.forEach(doc => batch.delete(doc.ref));
await batch.commit();
});
* Keeps dataset small, improving query performance.
.onSnapshot()
Firestore re-fetches all matching documents when using real-time updates.
docChanges()
to Track Only New Datadb.collection("messages")
.orderBy("timestamp", "desc")
.limit(20)
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === "added") {
console.log("New message:", change.doc.data());
}
});
});
Issue | Solution |
---|---|
Large data sets | Use subcollections instead of nested arrays |
Slow queries | Index fields used in where() & orderBy() |
High read costs | Use .select() to fetch only necessary fields |
Too much data at once | Use pagination (startAfter() ) |
Sorting before filtering | Always filter first, then sort |
Inefficient real-time updates | Use docChanges() instead of full reloads |
Too many old documents | Set up TTL & batch deletes |