Build1 distinct publisher3 min readUpdated
A Convex chat app holding a few hundred kilobytes of messages read tens of megabytes a day. The billable unit is not the table, it is the subscription's blast radius.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
A developer running a Convex chat app with exactly two users, his own dev and prod accounts, poking at it for maybe half an hour, watched the dashboard's Database Bandwidth meter report tens of megabytes a day [1]. The stored messages were a few hundred kilobytes at most, and both numbers were correct [2][3].
The reason is in the definition. In Convex, Database Bandwidth counts the bytes of documents your functions read and write per execution, and a reactive query re-executes every time any document in its result changes [4][5]. A `useQuery` call in React is not a fetch; it is a subscription that the server keeps watching, re-running the whole query and pushing the fresh result to every subscribed client whenever a touched document changes [6]. So the author's formula is bandwidth equals result size times re-run count [7]. A 200 KB result that re-runs 150 times has moved 30 MB while storing 200 KB [8]. Against a few hundred kilobytes of actual data, tens of megabytes a day is roughly a hundredfold read amplification [9].
The failure mode this produces is specific and boring. The message list was a plain `useQuery` returning the conversation's messages, so every arriving message re-ran it and re-shipped the entire list: message 50 lands and Convex re-reads and re-sends 1 through 50, message 51 re-sends 1 through 51 [10]. The marginal cost of one new message grows with the length of the conversation already there [11]. The author's fix is `usePaginatedQuery`, where each page is its own subscription, so a new message at the top re-runs only the newest page and the older loaded pages sit untouched [12][13]. His conclusion is worth taking literally: in Convex there is no separate incremental-update knob, pagination is the knob, and any reactive list that can grow should be paginated [14].
The second problem is the one that compounds. While streaming a model reply token by token, he saved progress by writing the accumulated text back to a document on every flush, so each write carried the whole string so far [15]. Writing 1 token, then 2, then 3, up to N, sums to N squared over two: double the reply length and you quadruple the bytes written [16][17]. A reply whose final text is a few kilobytes can write tens of kilobytes getting there, and on a reactive read path every flush also re-ships the document to every subscriber [18]. At 200 flushes, rewriting the buffer moves about a hundred times the bytes of appending only the new piece [19]. The fix is to append the delta, or stream the text over a separate channel and write the document once at the end, which is linear rather than quadratic [20]. The general shape, in his words: anything that rewrites a growing value inside a loop is a quadratic trap, and reactivity multiplies the damage because every rewrite is also a re-read [21].
What to watch in your own code is the read set of each subscription on your hottest write path, not the size of your tables. The author also flags server functions that used `.collect()` behind an index to count or check usage, which pulls every matching row into an array [22]; that is the same mistake wearing different clothes, a query whose read set grows without bound sitting behind a trigger that fires constantly.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
A developer reported that his Convex chat app, with two users (his own dev account and prod account) poking at it for maybe half an hour, showed the dashboard's Database Bandwidth meter reading tens of megabytes a day.
The actual messages stored in the app were a few hundred kilobytes at most.
The author confirmed he had not stored anything huge by accident: the data really was tiny and the bandwidth was real, both true at once.
In Convex, 'Database Bandwidth' is the bytes of documents your functions read and write per execution.
A reactive Convex query re-executes every single time any document in its result changes.
A React useQuery call in Convex is a subscription, not a one-shot fetch: Convex runs the query on the server, sends the result, keeps watching, and when any document the query touched changes it re-runs the whole query and pushes the fresh result to every client subscribed to it.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One first-hand account, mechanism explained but unmeasured
Everything rests on a single self-published post by the developer who hit the problem. The causal mechanism is described precisely and is internally coherent — subscription re-execution, per-page subscriptions, N²/2 buffer rewrites, .collect() row expansion — and the code samples are specific enough to check. But there is no screenshot of the bandwidth meter, no exported metric, no before/after byte counts after the fixes, no cited Convex documentation confirming the bandwidth definition, and no independent corroboration. The two quantitative amplification figures are arithmetic on soft inputs ('tens of MB', 'a few hundred KB'), not measurements.
One hobby-scale deployment, self-reported
The only adoption footprint in the supplied material is the author's own Convex chat app with a dev and a prod account and two users, plus his own remediation of it. No other teams, organizations, or usage volumes appear; there is no evidence about how widespread either the anti-pattern or the pagination remedy is among Convex users, and no post-fix figures. That places adoption signal just above the floor.
Slightly overstated headline, sound underlying mechanics
The framing is dramatic — two users generating tens of megabytes — and the amplification ratios are presented crisply while resting on soft, unverified inputs and no dollar figure, which pushes the gap positive. It stays small because the author is explicitly careful about scope: he states both facts (tiny data, real bandwidth) plainly, attributes the outcome to his own code rather than to a vendor defect, shows the offending snippets, and prescribes fixes that follow directly from the stated mechanism rather than claiming a general platform indictment.
Personal developer-audience visibility, no disclosed vendor tie
The post is an individual's write-up on a developer publishing platform, where lessons-learned narratives earn reputation and reach; the arresting headline arithmetic serves that incentive. Offsetting factors are visible in the text: the author has no disclosed relationship with Convex, sells nothing, attributes the cost blowup to his own code rather than to the platform, and recommends only the vendor's built-in primitives. There is no evidence in the supplied material of sponsorship, affiliate arrangement, or competitive positioning.
Mechanism credible, magnitudes uncorroborated
Confidence is moderate-low. The qualitative core — reactive subscriptions bill re-runs rather than rows, growing lists must be paginated, whole-buffer rewrites are quadratic, .collect() on a hot path scales with table size — is coherent, code-backed, and independently checkable by any Convex user, so it is likely right. The quantitative claims are single-source, artifact-free, and hobby-scale, and no post-remediation measurement is offered, so the specific magnitudes and their generality should not be relied upon.
build
The Escape-key listener you cannot fix with a dependency array1 distinct publisher
build
Codex learns to click: the coding agent stops typing patches and starts operating the machine1 distinct publisher
build
Five frameworks, one store, and a benchmark that measures when the button works1 distinct publisher
build
The 35% Lift Was The Tide: What A Bundled Commit Hid About Which Fix Worked1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026