Compute and isolates
What executes your code: V8 isolates rather than containers, why there is no cold-start planning, the hard CPU and memory limits, and what happens when a request exceeds them.
Your code runs in a V8 isolate — the same mechanism a browser uses to keep one origin's JavaScript away from another's. Not a container, not a VM, not a process per request. Most of this page's practical consequences follow from that one fact.
What this buys, and what it costs
| Consequence | |
|---|---|
| No capacity planning | No instance counts, no autoscaling policies, no warm-pool tuning. A traffic spike needs no action from you |
| No patch cycle | There is no OS, runtime, or base image of yours to maintain. The platform updates the runtime beneath you |
| No idle cost | Nothing sits provisioned between requests |
| Global by default | Every deploy puts your code in every location; there is no region to pick or replicate across |
| DDoS absorbed upstream | Filtering happens before an isolate runs — attack traffic costs you nothing |
| Hard resource ceilings | 128 MB memory and a CPU budget per request. Real constraints, listed below |
| No long-running processes | No daemons, no background threads, no persistent connections held open by your code |
| No native binaries | JavaScript, TypeScript, and WebAssembly. Not a place to run ImageMagick or ffmpeg |
The last three are where an architecture genuinely has to fit the platform rather than the reverse. Work that is long or heavy belongs in a queue consumer, a scheduled job, or a platform capability — not in a request.
The limits that matter
Current Cloudflare Workers limits on paid plans, which is what Spikefrost apps run on:
| Limit | Value | What happens at the edge of it |
|---|---|---|
| Memory per isolate | 128 MB (JS heap + WebAssembly) | Allocation fails; the isolate is terminated |
| CPU time per request | 30 s default, up to 5 min configurable | The request is terminated once the budget is spent |
| Wall-clock time | Unlimited while the client is connected | — |
waitUntil after response |
Up to 30 s | Background work is cut off |
| Cron, queue consumer, DO alarm | 15 min wall time | Invocation ends |
| Global-scope startup | 1 s to parse and execute module scope | Deploy or invocation fails |
| Script size | 10 MB gzipped (64 MB before) | Deploy rejected |
| Subrequests per request | 10,000 | Further fetch calls fail |
| Environment variables / secrets | 128 per Worker, 5 KB each | — |
Two of these are worth internalizing rather than looking up:
CPU time is not wall time. Thirty seconds of active computation is generous; waiting on a database, an API, or an external service doesn't spend it. A request that takes four seconds because it made three sequential queries has used only milliseconds of CPU. This is why the limit rarely bites in practice — and why, when it does, the cause is almost always a loop over data that should have been a query.
Module scope has a one-second budget and runs before you serve anything. Work at the top level of your entry file — parsing a large JSON blob, building an index — is paid before your first response. That's a design smell here: move data into D1 and query it.
Module scope is not a cache you can rely on
Isolates are created and evicted continuously, and they are not shared between requests in any guaranteed way. So a module-scope variable is:
- Not durable — it vanishes when the isolate is evicted, with no notice
- Not shared — another request may run in a different isolate that never saw your write
- Not consistent — two requests can observe different values simultaneously
// ❌ correctness-critical state at module scope — silently wrong under load
let currentInventory = 0;
// ✅ best-effort optimization only, safe to lose
const compiledPatterns = new Map<string, RegExp>();
Anything that must be correct goes in D1, KV, or a Durable Object according to the guarantee you need. This is the same reason a database session must never be created at module scope — it would be shared across users who each need their own.
Concurrency
Each request gets its own execution. There is no shared thread pool to exhaust and no connection pool to size, and requests don't queue behind each other for compute.
They can queue behind state. A D1 database executes queries serially, so the concurrency ceiling for a database-heavy app is usually the database, not the compute. Likewise a single Durable Object is single-threaded by design — that's what makes it correct, and it means it's a throughput bottleneck if you route everything through one. Both are covered in Choosing a data store.
Where your code physically runs
In the Cloudflare location nearest the user, chosen by anycast routing. You don't select it, and it can differ between two requests from the same user.
Practical implications for an enterprise review:
- Data residency is not controlled by compute placement. Your code may execute anywhere; where your data lives is a separate question, addressed in Isolation and tenancy. If you have hard residency requirements, raise them before you build.
- There is no "primary region" to fail over from. Every location is equivalent, which removes a whole class of failover design — and means there's no region-level failover runbook to write.
- Latency to your data is not uniform. A user far from your database's primary sees the round trip. Read replicas and the edge cache are the two levers, and the cache is the bigger one.
Next
- Choosing a data store — where state belongs, and the guarantee each store gives.
- The life of a request — how a request reaches an isolate in the first place.
- SLAs, limits and quotas — every limit in one reference table.
Frequently asked questions
Do we need to plan for cold starts?
Not in the way container platforms require. There is no per-request container to start; the platform runs your code in V8 isolates that are reused across requests. What you do control is module-scope work, which has a one-second budget and runs before your first request is served.
How much CPU time does a request get?
30 seconds of active CPU by default on paid plans, configurable up to five minutes. Wall-clock time is unlimited while the client stays connected, so waiting on a database or an external API doesn't consume the CPU budget.
How much memory?
128 MB per isolate, covering the JavaScript heap and any WebAssembly. That's a real constraint for in-memory processing — stream large payloads rather than buffering them.
How do we scale for a traffic spike?
You don't do anything. There are no instances to add and no autoscaling policy to tune. Your code is already present in every Cloudflare location, and each request runs wherever it lands.
Is module-scope state safe to use as a cache?
Only as a best-effort cache that may vanish. Isolates are created and evicted continuously and are not shared, so module-scope values are neither durable nor consistent between requests. Never keep correctness-critical state there.