Build1 publisher3 min readPublished
Twenty Firestore posts cost forty reads until the author name lives in the post document
Firestore meters document operations by count. The read cost of a screen is therefore set by its schema, and fixing an N+1 access pattern means moving fields and paying for the move on the write path.
The Engineer · Build desk

What happened
- Firestore's unit of billing is one document operation. Queries, bytes and connections are not metered, so a query that returns 100 documents costs 100 reads regardless of how large those documents are.
- A timeline that loads 20 posts and then one author document each costs 40 reads per render, or 120,000 reads a day for 1,000 users opening it three times.
- Copying the author name and avatar into the post document halves that to 20 reads and moves the cost to writes, because a rename has to fan out across the author's historical posts.
- With a realtime likeCount, 100 likes on one post produce 100 document updates and 100 reads for every open listener, so 1,000 open tabs take 100,000 reads in a few seconds.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Where a field is stored sets the read count of every screen that displays it, and an index or a connection pool added later leaves that count where it is. Removing the reads means a data migration plus new write-path fanout.
- cost Denormalizing moves the spend to the write side, where 100,000 operations cost three times what 100,000 reads do, so it only pays where reads outnumber writes heavily.
- decision Each screen now needs an explicit call on get() versus onSnapshot, because a listener's bill is set by other people's write traffic and by how long the tab stays open.
- exposure A subscription left open after navigation keeps drawing reads for a screen nobody is looking at, and the charge tracks writes made by other users.
At the Blaze rates the dev.to post quotes for the US multi-region, that 120,000-read day sits 70,000 reads above the 50,000 the free tier allows, which is about four cents a day and roughly $1.26 over thirty days [1][6]. The schema is where that number gets set. Reads per day equal users times screen opens times documents fetched per screen [9]. Only the third factor is a design choice, and it is decided by where the author's display name is stored [1]. A query that returns nothing still carries a minimum charge [4].
Batching the author lookups with an `in` query collapses N round trips into one, but the read count for the user documents is unchanged unless the same author appears twice on the page, and `in` takes at most 30 values per query [10]. Denormalizing the author fields into the post is the fix that removes reads, and it is a schema change: the write path now updates every historical post when a user renames [11]. The post puts the boundary at reads dominating writes by an order of magnitude, and rules out denormalizing an email address used for auth decisions [12]. On the free allowance alone, 50,000 reads a day buys 1,250 renders at 40 reads and 2,500 at 20 [4].
Counting is the sharper case. Pulling a likes subcollection to read its size costs 1,000 reads for 1,000 likes, all to display a four-digit number [13]. `getCountFromServer` is billed at one read per 1,000 index entries scanned, so the same count is a single read, one thousandth of the client-side cost [14][3]. For counters that show up in list views the post keeps a materialized `likeCount` and maintains it with `FieldValue.increment(1)` [15]. One document sustains roughly one write per second [16]. A hundred likes arriving in a few seconds is well past that, so the counter needs sharding [6].
A listener charges the initial result set once, then one read per changed document delivered [18]. Put a live `likeCount` on the post and the cost becomes the product of two numbers set outside the client: writes to that document, and open tabs. The 100,000-read burst in the example costs six cents at the quoted read rate [5]. The post reserves `onSnapshot` for chat, presence and collaborative editing, and gives settings screens, archives and list views `get()` [20].
Whether these figures transfer depends on the shape of the page. Forty reads per render assumes twenty posts with twenty distinct authors, and deduplication cuts it as soon as one author appears twice [7][10]. The 100,000-read burst assumes a thousand clients holding the same document open in the same few seconds [19]. The rates are indicative Blaze figures for one multi-region [5], so a different location changes only the per-read price.
What to watch
- Any revision to the Blaze read and write rates. New rates change the per-render cost and leave the schema decision where it was.
- Whether the `in` query's 30-value ceiling moves, since it sets how many chunks a batched author lookup needs.
- Whether the roughly one write per second sustained limit on a single document rises. A higher limit removes the need to shard hot counters.