BuildNot yet confirmed elsewhere1 publisher3 min readPublished
244 kB, 500 a minute, 5 percent: three ceilings that fail for the same reason
A dev.to guide puts a bundle budget, a provider rate limit and a planner threshold in one frame. Run its own numbers out and the retry loop, not the invoice, is the thing that gets you.
The Engineer · Build desk

What happened
- A dev.to guide fixes the browser ceiling at 244 kB gzipped, the point where Lighthouse begins penalising Time to Interactive.
- The provider ceilings it cites are GitHub at 5,000 requests an hour, OpenAI Tier 1 at 500 a minute and Stripe at 100 writes a second.
- On the database side, an index at 0.5 selectivity is ignored and the query sequentially scans 50,000 rows instead.
- Before adding an index, the check it prescribes is a distinct count on the column, run against the table you intend to index.
Why it matters
- constraint A single utility library imported whole spends a tenth of the entire page budget before any feature code is measured, so the ceiling is largely gone by the time anyone reviews a route.
- exposure A retrying client is its own load generator: the 30 percent retry fraction adds 90 requests a minute on top of the 800 it already could not deliver, so the overload sustains itself without new users.
- cost All three overruns are collected from the user in paint time, a 429, or a query that never returns, which is why none of them show up as a bill to the team that shipped them.
- decision Five percent of 100,000 rows is 5,000, which lands at the low edge of the planner's flip band, so whether the index is worth having is decided per bound value rather than once per schema.
Each of these ceilings is enforced at an instant, while every number teams use to plan for them is an average over a window. A token bucket refills at a fixed rate up to a burst capacity, so a client can burst to capacity at once and still never sustain more than the refill [10]. Twenty requests a minute is the mean for a client that does 800 during a deploy [4]. GitHub's 5,000 an hour is 83.3 a minute, which is the figure a burst actually meets [16]. The planner has the same property in a different unit: selectivity is rows returned over rows total [13], and what the planner is handed is a bound value, not a column. A `status` column with two values averages to 0.5, while `active` is 0.95 and `pending` is 0.05 [5].
The retry arithmetic is where averaging gets expensive. In the guide's worked case, 800 requests a minute against a 500 limit with a 30 percent retry fraction gives 160 percent utilisation, 300 throttled per minute and 890 effective requests a minute; two minutes of that is priced at $3.56, or $76k a month sustained [26]. Carry the sustained figure out and it is $76,896 over a 43,200-minute month [20]. The bucket only admits 500 a minute, which is $43,200 of traffic in that same month, so about $33,700 of the headline is requests the provider refused [21]. The ceiling being modelled is on admitted throughput, not on spend, and the queue the guide prescribes (p-limit at a concurrency of 10) is the only thing that moves it, along with batching where the provider allows N calls to become 1 [11][12].
The database side prices out the same way. For `status = 'active'` on 100,000 rows with 95,000 active, the index scan costs 380k against a sequential scan's 100k, so the planner takes the scan; for a single-row `email` lookup it is 4 against 100k [14]. That is an index path 3.8 times more expensive than not using the index at all [23], for an index someone almost certainly added on purpose.
On the frontend the distinction is visible in the diff. lodash imported whole is 71.0 kB parsed and 24.4 kB gzipped, 122 ms of transfer on a 1.6 Mbps 3G link and 22 ms on 9 Mbps 4G [6]; `lodash/get` is 7.2 kB parsed, 2.9 kB gzipped, 15 ms on 3G [7], which is 8.4 times smaller on the line that matters [24]. The saving only exists if the bundler can drop the rest, and webpack tree-shakes `lodash-es` and named ESM imports, not the default `lodash` import [8]. A reviewer can check the import form by reading the patch; the gzip number needs a build, which is the argument for a CI budget of 130 kB per route and pasting the computed budget percentage into the PR description [9]. Swapping moment for date-fns is another 14 kB and 70 ms on 3G [18].
Two caveats on the thresholds themselves. The browser figure is where Lighthouse begins penalising Time to Interactive [27], a scoring penalty rather than a hard failure, which is exactly why it functions as a budget nobody notices breaking. And this is one guide, with figures reproducible through its own three calculators [17], so the constants are worth re-deriving against your provider tier and your planner before they become a merge gate.
What to watch
- Whether the 5 to 20 percent planner band survives non-default cost settings; the guide qualifies it as holding with default costs, which makes it a per-installation constant, not a universal one.
- Whether the quoted provider ceilings hold: OpenAI Tier 1 at 500 a minute and Stripe at 100 writes a second are tier properties, and a tier change moves the ceiling without a deploy on your side.
- Whether a CI bundle budget fails the build or merely annotates it. A threshold nobody blocks on is a report, not a budget.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence34
- Adoption
- Insufficient
- Hype gap+24
- Incentives58
- Confidence38
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Cited provider rate limits: 5,000 requests per hour on GitHub, 500 per minute on OpenAI Tier 1, 100 writes per second on Stripe.
- [2]
An index with 0.5 selectivity will be ignored by the planner and the query will sequentially scan 50,000 rows.
- [3]
Exceed any of the three budgets and the user pays: slower paint, 429 Too Many Requests, or a query that times out.
- [4]
An API that averages 20 requests per minute is 800 per minute during a burst deploy; the average hides the spike.
- [5]
A table where status has two values has selectivity 0.5 on average, but 0.95 for 'active' and 0.05 for 'pending'.
- [6]
lodash imported whole is 71.0 kB parsed, 24.4 kB gzip and 20.1 kB brotli; on a 1.6 Mbps 3G link that is 122 ms of transfer before first paint, and 22 ms on 9 Mbps 4G.
- [7]
The same utility as lodash/get is 7.2 kB parsed, 2.9 kB gzip and 15 ms on 3G, an 8x saving from a one-line import change.
- [8]
Bundlers like webpack only tree-shake lodash-es and named ESM imports; the default lodash import is not shaken.
- [9]
The guide recommends a CI budget of 244 kB gzip total and 130 kB per route, and pasting the calculator's Budget% into the PR description so reviewers see the cost without rebuilding.
- [10]
All the cited limits are token buckets: they refill at a fixed rate up to a burst capacity, so a client can burst to capacity instantly but sustained throughput cannot exceed the refill rate.
- [11]
The fix is not more retries but a queue that smooths the burst; the guide shows p-limit with a concurrency of 10.
- [12]
Batch where the API allows it: GitHub GraphQL and Stripe batches turn N requests into 1.
- [13]
Selectivity S = rows returned / total rows decides whether the planner uses the index; the threshold is around 5 to 20 percent with default costs.
- [14]
For status = 'active' on 100k rows where 95k are active, S = 0.95 and the index scan costs 380k against a sequential scan's 100k, so the scan wins; for email = 'a@b.com' returning one row, S = 0.00001 and the index costs 4 against 100k, winning by 25,000x.
- [15]
The pre-index check is SELECT COUNT(DISTINCT status) FROM users.
- [17]
Every number in the guide is stated to be reproducible with three accompanying calculators: the Bundle Size Impact Calculator, the API Rate Limit & Cost Calculator and the SQL Index Selectivity Calculator.
- [18]
A single import choice moves 21.5 kB gzipped, or 107 ms on 3G; replacing moment with date-fns saves another 14 kB, or 70 ms.
- [19]
One utility library imported whole at 24.4 kB gzipped consumes 10.0 percent of the entire 244 kB gzip page budget.
- [20]
Effective demand of 890 requests per minute sustained across a 43,200-minute month at $0.002 per request is $76,896.
- [21]
The 500 per minute limit admits only $43,200 of billable traffic in a 43,200-minute month, so roughly $33,700 of the $76,896 sustained figure is requests the bucket refuses.
- [22]
Selectivity of 0.05 on a 100,000-row table returns 5,000 rows, which sits at the bottom edge of the planner's 5 to 20 percent flip band.
- [23]
At selectivity 0.95 the index path costs 3.8 times the sequential scan.
- [24]
On the gzipped wire size, lodash whole against lodash/get is a factor of 8.4.
- [25]
The 30 percent retry fraction applied to 300 throttled requests adds 90 per minute, raising the client's own demand from 800 to 890 per minute.
- [26]
At $0.002 per request, a 500 per minute limit with an 800 per minute burst and a 30% retry fraction gives utilisation of 160%, 300 throttled per minute, 90 extra retry requests per minute and effective demand of 890 per minute: $3.56 for two minutes, or $76k per month if sustained.
- [27]
244 kB gzip is where Lighthouse starts penalizing Time to Interactive.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toWeb Performance on a Budget: Bundle Size, API Limits, and Database Indexes
1 article · August 24, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.