← Writing

Should you run Prisma on Cloudflare Workers in 2026?

An honest assessment from running a 180+ model Prisma schema on Workers in production: the WASM memory math, the transaction timeouts, what we tried, and when I'd do it again.

On this page

I’ve run Prisma on Cloudflare Workers in production for an extended period, on a real workload: an AI-agent platform whose schema has grown past 180 models, serving dashboards, webhooks, queue producers, and agent tooling from a single Worker. This post is what I’d tell a past version of me deciding whether to do it.

Short version: it runs, and for a while it runs fine. Then the schema grows, the traffic gets bursty, and you discover you’ve bought a very specific set of problems. Here they are, with what we did about each.

Problem 1: the memory math

A Worker isolate gets 128 MB. On our schema, the numbers were:

  • 11.16 MB bundle — a large chunk of it the generated Prisma client
  • 3.5 MB of Prisma query-compiler WASM, loaded before the first request
  • ~3.81 MB per concurrent Prisma client, because each client compiles the schema inside WASM

Three properties of that per-client cost turn it from a line item into an incident generator:

  1. The compile is synchronous — it blocks the isolate’s only thread. We watched a query against a 30-row table take 4,992 ms, not because the database was slow, but because the thread was busy compiling schemas for other requests.
  2. WASM memory never shrinks. memory.grow() is one-way, so an isolate’s footprint is set by its historical peak concurrency, forever.
  3. The cost scales with schema size, so it silently worsens as the product grows.

Ten concurrent clients put us at ~53 MB before any real work. A frontend burst on top of that killed isolates — 11,058 times in one week, at the worst of it.

Problem 2: transactions through a proxy

Workers can’t hold direct database connections, so Prisma talks to Postgres through a connection proxy — in our case Cloudflare Hyperdrive in front of the database. Most of the time this is invisible. Under load, interactive transactions started failing with P2028, “Unable to start a transaction” — timeouts acquiring what the transaction needed, at exactly the moments we could least afford retries.

Transactions want a held connection and predictable latency. A pooled proxy under bursty load offers neither. The workloads that hurt most were the long-running ones: queue consumers doing batch work inside transactions, sharing the same Worker (and the same proxy budget) as interactive dashboard traffic.

What we tried, in order

Client lifecycle discipline. Requests originally built clients freely. We moved to ref-counted clients with careful release — and found lifecycle bugs of our own, including a client released mid-stream while an AI-SDK response was still consuming it. Fixable, but this is a category of bug you only have because the client is expensive.

A hard concurrency cap. We capped concurrent Prisma clients per isolate, and later lowered the cap from 16 to 10. This is containment, not cure: it bounds the worst-case memory at ~53 MB and converts overload into queueing.

Compat-flag experiments. We experimented with runtime flags like enable_weak_ref in the hope of better engine memory behaviour. No silver bullet there.

Evacuating heavy consumers. The genuinely effective structural move: queue consumers left the Worker entirely and became Node pull-consumers on a cheap box we already ran. Long transactions stopped sharing a runtime with dashboard clicks, and P2028 went from recurring to rare.

Evaluating the exit ramps. Two real options: Prisma’s managed accelerator (moves the query engine off the isolate; adds per-query pricing that you should model against your volume before committing), or dropping to the raw serverless Postgres driver — Neon’s driver with a thin query layer — which removes the WASM engine from the Worker entirely. We recorded the driver route as the cleanest permanent solution. The cost is real, though: you give up Prisma’s generated types and migrations ergonomics, or you keep Prisma for migrations and types while querying through something lighter.

So: should you?

My honest framework, from the far side of it:

Prisma on Workers is fine when:

  • your schema is small (the per-client WASM cost scales with it)
  • traffic is request-shaped and modest in concurrency — no twenty-request page bursts, no queue consumers in the same Worker
  • you treat the client as a scarce resource from day one: one per request maximum, capped globally, never in a hot loop

Take a different road when:

  • the schema is large or growing fast — you can’t schema-shrink your way out later without a rewrite
  • you run mixed workloads (interactive + background) in one Worker
  • transactions matter under load — a proxy will eventually time out at the worst moment
  • you’re already fighting the 128 MB ceiling for other reasons

And whichever side you land on: measure per-client memory on your schema before you commit. The number that mattered most in our entire saga — 3.81 MB per client — took an afternoon to measure and would have changed the original architecture decision if we’d known it on day one.

Prisma is a good ORM. Workers is a good runtime. The combination is a marriage where both partners need to stay small — and production systems don’t stay small.

← All writing Book a call →
Book a call → WhatsApp