Why Does the Firestore Bill Blow Up?
With Firestore, thinking "my database is small, so the cost must be small too" is misleading. You are charged for how many times you touch the data, not for how much of it there is.
The easiest thing to overlook about Firestore is its pricing model. In a classic database the server determines the cost; in Firestore the bill comes down largely to how many documents you read.
Here is what that means: you can have a tiny collection of 500 documents, but if you read it from scratch every time a screen opens, you are generating thousands of reads per user per day. What follows are the patterns I ran into in my own apps and fixed.
1. Reading everything again on every screen open
This is the most common pattern. The user moves back and forth between tabs, and the list is fetched again on every return. You get charged even when nothing has changed.
The fix is to keep the offline cache enabled and try the query from the cache first:
// If the data has not changed, skip the network and read from the cache
db.collection("gorevler")
.get(GetOptions.CACHE) // local cache first
.addOnFailureListener {
db.collection("gorevler").get() // fall back to the server
}
In the mobile SDKs, offline persistence is already on by default. But if you force the query to hit the server every single time, that cache never gets used.
2. Never detaching the listener
Real-time listeners (snapshot listener) are powerful, but they can get
expensive fast. If you do not stop the listener when the screen closes, it keeps
generating reads in the background for as long as the user stays in the app.
If you open the app, do nothing for five minutes, and the read counter still climbs,
a listener has been left open. Tie your listeners to the screen's lifecycle and always
call remove() on teardown.
3. Firing a separate query for every item
You fetch a list, then query the related data separately for each item in it. On a list of 50 items that means 1 + 50 = 51 reads.
Since Firestore has no JOIN, the fix lives in the data model: copy the field
you need into the parent document. This is called denormalization, and it trades storage
cost against read cost — in Firestore that trade is almost always worth it.
// Instead of fetching the user separately for every task,
// copy the field you display into the task itself
{
"baslik": "Rapor hazırla",
"sahipId": "u_1842",
"sahipAdi": "Demir T.", // embedded copy
"sahipAvatar": "https://..." // embedded copy
}
When a copied field changes you have to update it — which is why it only makes sense to embed fields that change rarely and are read often.
4. Skipping pagination
Fetching the whole collection and filtering on the client is the most expensive and the slowest approach. In Firestore you are not charged for documents you do not read, so narrowing the query translates directly into savings.
db.collection("gorevler")
.whereEqualTo("durum", "acik") // filter on the server
.orderBy("tarih", DESCENDING)
.limit(20) // take only what you need
.get()
Fetching the next page with startAfter() once the user reaches the end of the
list brings down both the bill and the initial load time noticeably.
5. Reading everything just to count it
Pulling the entire collection to answer "how many tasks are there?" costs you one read per document counted. There are two alternatives:
- Aggregation query: a
count()query is far cheaper than reading the documents one by one. - Counter field: keep the number in a separate document and update it on insert and delete. A single read gets you the count.
Do not optimize without measuring
The usage page in the Firebase console shows read, write and delete counts on a daily basis. Note down the current numbers before you make a change, then compare afterwards.
Setting up a budget alert during development is also a good habit — it can be surprising to see how quickly a listener that accidentally ends up in a loop can grow the bill.
When is Firestore itself the wrong choice?
If your read count is still high after applying all of the above, the problem may not be in how you use it but in the shape of the data model. For projects whose data is relational and that need complex queries, I covered this in detail in a separate post.
In Firestore, every query is a purchase. While writing the code you have to ask "how many reads does this cost?" as often as you ask "does this work?".
- Firestore
- Firebase
- Cost
- Performance
- Architecture
Demir Taşdemir
Mobile App & Web Developer
I have been building software since 2018. I have published 11 apps on the App Store and Google Play; right now I am working on 6 mobile apps, 1 e-commerce platform and 1 desktop game.
A developer who thinks about cost too
Knowing what a query costs changes how you write that query.