Blocknado is a small falling-block puzzle whose floor rotates a quarter turn every time you slam a piece down. Its developer added a global leaderboard, capped the budget at the second-cheapest Cloudflare tier, and built to see how far it would stretch at a million players.

The answer is $5 a month — and the postmortem turns out to be less about leaderboards than about how cloud costs actually accrue.

How it fits in the budget:

  • Reads never touch a Worker — the top 10 is a static file regenerated only when a score enters the top 10, a materialized view behind a cache with a one-minute TTL, plus an optimistic client update so you see your own rank immediately
  • Cloudflare caches by file extension and .json is not on that list, so the board needed its own subdomain with a blanket cache rule
  • D1 bills rows read, not rows returned: sorting the board by score and then submission time turned a 10-row query into a full table scan until time was indexed too
  • Log lines are billed (20M/month included), so the Worker logs roughly 10% of jobs
  • Rate limits per minute, per player and per address keep request volume a multiple of player count rather than of enthusiasm

What the free tier could not do:

  • Free-plan Workers get 10 ms of CPU per request, and validating a score means replaying the entire game server-side — the game’s TypeScript class is copied into the Worker, so it re-simulates submitted moves
  • Long games are the high-scoring ones, so they were exactly the submissions getting cut off halfway; the $5 tier’s 30 seconds per request is the only real reason it is not free
  • Trusting client-submitted scores would fit free: about 100k requests a day covers roughly 300k players

Safety rails, because no provider caps spending:

  • Writes sit behind the developer’s own domain with a WAF in front, so no-op and abusive traffic never reaches the Worker
  • Smoke tests on every deploy send real games to real APIs and assert that caching and query behaviour still hold
  • D1 has no reverse migrations built in, so failing smoke tests trigger a hand-written reverse migration, itself covered by tests asserting the pre-migration and post-reverse-migration schemas match

The principle underneath it all is to grow with users, not requests — cache on write, serve static files on read, and assume you will eventually open a hole in some update you were too careless to review.

The developer is clear that the complexity may not be worth it versus an off-the-shelf leaderboard, and that part of the motive was simply finding out whether it could be done. That is usually where the useful writeups come from.