The 10ms Budget: Architecting a One-Person Company's Website on Free Cloudflare

2026-10-15edgecloudflare-workersarchitectureone-person-company

The 10ms Budget

_Or: how I decided what my server is allowed to do before I wrote a line of code._

The premise

I run a one-person company. My infrastructure budget is $0/month. My domain (openddd.com) lives on Cloudflare's free tier. That tier has a rule I only discovered after picking it: each request gets 10ms of CPU time on the Worker isolate before it's killed.

Ten milliseconds sounds tiny. It isn't — for a routing + templating workload. But it is tiny relative to what most backend code assumes it can spend. Most web handlers I've seen would blow through 10ms just in JSON parsing and ORM query building. So before writing any code, I drew a budget.

The budget

StageCostNotes
Routing0.5msHono's trie router is fast enough that we don't think about it
Cache lookup1msIsolate-local LRU; miss → hit D1, still under budget
Compute2msBusiness logic. This is the only line item allowed to grow
Serialization0.5msJSON.stringify on a 5–20KB payload
Transport+slack6msTLS, RTT within a PoP, event-loop jitter

Total: 10ms. Any handler that goes over its compute line item gets pushed off the request path, not optimized in place.

The "端到端 10ms" claim would be physically unreachable — DNS alone costs more than that on a cold resolver. So we don't promise it. What we promise is server-side P99 under 10ms, which is what the CPU meter actually measures.

The rules that fall out of the budget

1. The request path only touches memory and local storage

No synchronous calls to external APIs. No Postgres round-trips. If a handler needs data from the outside world, that data is either already in the isolate (cache), or in D1 (SQLite at the edge), or it isn't needed for this response.

This is the reason D1 is on the map at all — its reads are colocated with the Worker in the same PoP. A 5ms D1 read at edge beats a 30ms Postgres read across the Pacific, even if Postgres is "faster" on paper.

2. The main database is only in the async write path

Every state change gets queued. A Durable Object or a Worker-with-KV queue holds the mutation, and a background Worker flushes to the canonical database. Reads never see the queue; they see the edge copy, which is eventually consistent.

For a personal-growth scoreboard or a content index — eventual consistency at seconds granularity is fine. For payments, it wouldn't be. So payments would go somewhere else (Stripe, with a webhook writing back into D1).

3. Language: TypeScript first, Rust+WASM second, Go never

TypeScript starts fastest and is the only runtime Cloudflare's free tier really optimizes for. When a compute-line-item exceeds budget (image processing, hashing, ML inference), the fallback is a WASM module written in Rust — those run inside the same isolate with no cold start.

Go is off the table because Cloudflare Workers doesn't accept Go binaries. If I ever need Go, I'll use Fastly Compute@Edge or move the specific function to a Cloudflare Worker-as-Container (paid tier only, and even that is still in preview as of this writing).

What "compute ≤ 2ms" actually rules out

Two milliseconds of compute is:

hash a token with crypto.subtle, do a 1000-element array reduce.

do any synchronous network I/O, run an LLM.

Every "no" gets either cached, batched, moved to D1, or pushed to a Queue. That's it. There's no fourth option.

What this does NOT save you

The free tier caps you at:

Mitigation: Bot Fight Mode is free and enabled by default; anything over quota falls back to static Pages.

session store. If you need more writes, they go through D1 or Queues.

scoreboard; not fine for a multi-tenant SaaS.

None of these limits bit me in the first week of building this site. They would bite around 500–1000 real users on a content product. At which point the whole thing has proven itself worth a $5/month Workers Paid upgrade.

What it saved

Zero dollars. That's the actual win. The 10ms budget forces me to write smaller, sharper handlers because I can't hide waste behind compute. It forces caching because the CPU clock won't tolerate a per-request DB hit. And it makes the deployment story boring: wrangler deploy, done, no container, no orchestrator, no cold-start tuning, no VPC peering.

For a one-person company, boring infrastructure is the highest-leverage feature you can buy. It's the difference between "spent the afternoon shipping content" and "spent the afternoon debugging a Kubernetes node pool".

What's next

The first thing this architecture powers is a .agent.md generator — a form that emits a valid YAML-frontmatter agent definition file you can drop into a Hermes-style workspace. The whole thing runs in one D1 read and one string-templating pass. Server CPU per request: measured at 0.4ms.

That's a small feature. But it's a live demonstration that this budget is workable, and every subsequent feature will have to justify exceeding it.