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.

The Spikefrost Team30 Jul 20265 min read

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.

Containers pay a start-up cost per instance and sit idle between requests. Isolates are created in the location that receives the request and reused across requests, with no idle capacity to provision.

Isolate model — Workers

request

nearest location

isolate
reused · 1s global-scope budget

your app

Container model

request

cold start
pull · boot · init

your app

idle instance
provisioned, waiting

Containers pay a start-up cost per instance and sit idle between requests. Isolates are created in the location that receives the request and reused across requests, with no idle capacity to provision.

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

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.