Benchmarks & limitations
What it costs, how it performs, and where it falls short
All numbers are from runs against real Cloudflare deployments in October 2026 (raw logs in
bench/results). Where the pool is worse than real Durable Objects, this page says so.
Cost
Real Durable Objects bill wall-clock time at 128 MB per awake object. The pool bills a container's memory plus the CPU it actually uses. The pool wins once objects are awake more than about 0.3% of the time (~2 h/month). Below that, hibernating real DOs are cheaper.
| Workload (per month) | Real DO | do-pool |
|---|---|---|
| 100 always-awake objects | $405 | $13 |
| 1,000 always-awake objects | $4,050 | $22 |
| 10,000 always-awake objects | $40,500 | $188 |
| 1,000 objects awake 5% of the time | $203 | $13 |
| 1,000 objects awake 0.1% of the time | $4 | $13 |
| Chatty rooms: 50 × 10 users × 5 msg/s | $251 | $124 |
List prices; free allowances ignored. Pool capacity is measured: 1,000 awake 1 Hz objects on ¼ vCPU billed ≈ 0.2 vCPU. v2 nodes default to 1 vCPU / 2 GiB because the new scheduling mode doesn't accept the ¼-vCPU size, which is similar per object.
Where the money goes
- Real DO: 0.125 GB × every awake second × $12.50 per million GB-s = $4.05 per always-awake object per month, plus requests (incoming WebSocket messages at 1/20). Outgoing frames are free.
- do-pool: container memory ($0.0000025 per GiB-s) + disk + active CPU ($0.00002 per vCPU-s), plus 2 R2 PUTs per node per 30 s. The fronting PoolNode DO isn't billed for wall-clock time on raw-passthrough sockets (measured).
- Fan-out flips it. Outgoing frames are free on real DOs but cost CPU in a container, so chatty broadcast rooms come out only ~2× cheaper.
Recovery & availability
How long until an object answers again after each kind of event. Time is on a log scale. A real Durable Object has no equivalent for most of these, because Cloudflare handles them invisibly.
Performance & density
| Measure | Real DO | do-pool |
|---|---|---|
| Memory per resident object | billed as 128 MB | ~0.2 MB (SQLite object in workerd) |
| Memory per WebSocket | n/a (not billed) | ~77 KB; ~370 KB with permessage-deflate (we strip it) |
| Throughput | each object has its own thread | ~11k WebSocket frames/s per vCPU (naive broadcast); hosts vary ~1.7× |
| Awake 1 Hz objects per ¼ vCPU | n/a | ~1,000 comfortably; saturates at ~1,500–1,700 |
| Connections per container | n/a | hard cap 4,096 (platform limit) |
| Browser round trip (cursors demo) | 18–114 ms | v1: 82 ms (container far away) · v2: 97 ms (region-local) |
| KV get from an object (median) | 5 ms | 19–30 ms (via the bridge) |
| Service-binding RPC from an object | ~0 ms (same thread) | 16–23 ms |
| DO → DO RPC | 36–37 ms | 31–33 ms (via Placement) |
Round-trip numbers come from a home connection in California and are noisy. In v1 containers started wherever Cloudflare had room (we saw Paris and Osaka). In v2 they start in the same city as their node DO.
| Operation | Measured |
|---|---|
| Checkpoint writes | 2 R2 PUTs per node per 30 s, regardless of object count |
| Drain 690 objects (one segment) | 2.8 s |
| Filesystem snapshot (sleep) | 2.5–4.4 s · 0.2–10 MB |
| Restore from snapshot | 0.6–2.2 s |
| Load app code (facet), cold / warm | ~3 ms / ~0 ms |
| Memory per app version / per object | ~2.2 MB / ~0.4 MB on shared code |
| Sandboxed tenant process | ~17 MB, ~25 ms to start |
Limitations: read these before using it
Durability
- Writes are acknowledged before they're durable. A node that dies without draining loses up to 30 s of writes. Real DOs replicate first. Don't put money or anything irreplaceable in a pooled object.
- No point-in-time recovery. Old R2 segments are never compacted or garbage-collected yet.
Deploys
- Under Cloudflare's
durable_objectscheduling, a container dies with its DO, so deploying the control-plane Worker restarts every node over ~1–13 min. No acknowledged writes were lost in tests, but one write stalled for 32 s. - Edge Worker deploys don't touch the pool, which is why it's split into two Workers.
Scale & shape
- A single Placement DO handles every new placement; lookups are cached per isolate.
- 4,096 connections per container; one hot object competes with its neighbours for CPU.
- workerd doesn't enforce per-tenant CPU limits; the sandboxed tier gives each app its own process instead.
API gaps
- Bindings (KV, R2, AI, services, other DOs) work with the same code, but each call adds ~15–25 ms (it's replayed in the control Worker), bodies are buffered rather than streamed, and WebSockets can't go over a binding. Sandboxed apps get no bindings.
- Moves close WebSockets with 1012; clients must reconnect (most libraries do).
- Facets can't own alarms anywhere (Cloudflare or workerd); the host proxies them.
How these were measured
test/parity.test.ts runs the same API assertions against all five backends.
test/lifecycle.test.ts exercises moves, crashes, deploys, sleep/wake and the sandbox, each against the live
deployment. bench/ has the load generator, analytics pull (billing data from Cloudflare's GraphQL API)
and cost model. Total Cloudflare spend for everything: about $1.20.