Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save igorcosta/8f7cf4a74c718da7167d91b717602ad7 to your computer and use it in GitHub Desktop.

Select an option

Save igorcosta/8f7cf4a74c718da7167d91b717602ad7 to your computer and use it in GitHub Desktop.
Autohand Code deep-research out out on Cloudflare durable objects for Pi agents

Cloudflare Durable Objects — Deep Research Report

Date: 2026-07-15 Sources: Official Cloudflare Durable Objects documentation (fetched live)


Summary

Durable Objects are a special kind of Cloudflare Worker that uniquely combines compute with storage in a single, globally-unique, single-threaded instance. They enable stateful serverless applications — real-time chat, multiplayer games, collaborative editing, booking systems, and distributed coordination — without requiring developers to build serialization, locking, or coordination primitives themselves.

Each Durable Object is implicitly created on first access, provisioned geographically close to the first requester, and can scale to millions of instances across Cloudflare's global network. Storage is colocated with compute (SQLite-backed, up to 10 GB per object), providing strongly consistent, transactional, zero-latency access. The Actor programming model underpins the design: each object receives messages (HTTP/RPC requests), executes logic in its own single-threaded context, and sends messages back out.

Key differentiators from plain Workers:

  • Globally-unique identity — addressable from anywhere
  • Colocated durable storage — strongly consistent, no network hop
  • Single-threaded concurrency — no race conditions by construction
  • Elastic horizontal scaling — millions of objects, no capacity planning

1. Core Architecture

What are Durable Objects?

A Durable Object is a special kind of Cloudflare Worker which uniquely combines compute with storage [1]. Like a Worker, it is automatically provisioned geographically close to where it is first requested, starts up quickly when needed, and shuts down when idle. Unlike regular Workers:

  • Each Durable Object has a globally-unique name, allowing requests to be sent to a specific object from anywhere in the world [1].
  • Each Durable Object has durable storage attached, which lives together with the object, is strongly consistent, and is fast to access [1].

Single-threaded concurrency model

Durable Objects are single-threaded and cooperatively multi-tasked, just like code running in a web browser [1]. This eliminates most concurrency pitfalls (race conditions, deadlocks) that arise from shared-memory concurrency.

The runtime uses input gates and output gates to prevent data races:

  • Input gates block new events (incoming requests, fetch responses) while synchronous JavaScript execution is in progress. Awaiting async operations like fetch() opens the input gate, allowing other requests to interleave. Storage operations provide special protection — while they execute, no other requests can interleave [2].
  • Output gates hold outgoing network messages (responses, fetch requests) until pending storage writes complete. This ensures clients never see confirmation of data that has not been persisted [2].
  • Write coalescing: Multiple storage writes without intervening await calls are automatically batched into a single atomic implicit transaction [2].

Actor programming model

Each Durable Object instance can be seen as an Actor instance, receiving messages (incoming HTTP/RPC requests), executing some logic in its own single-threaded context using its attached durable storage or in-memory state, and finally sending messages to the outside world (outgoing HTTP/RPC requests or responses), even to another Durable Object instance [1].

This is analogous to Actor models in Erlang, Elixir, Akka, or Microsoft Orleans [1].

RPC (Remote Procedure Call)

Durable Objects support Workers RPC, which allows applications to use JavaScript-native methods and objects to communicate between Workers and Durable Objects [1]. Using RPC makes application development easier and more efficient than HTTP fetch.

Projects with a compatibility date of 2024-04-03 or later should use RPC methods [2].


2. Lifecycle & Hibernation

State transitions

A Durable Object can be in one of five states [3]:

State Description
Active, in-memory Runs, in memory, handles incoming requests
Idle, in-memory non-hibernateable Waits for next request/event, but does not satisfy hibernation criteria
Idle, in-memory hibernateable Waits for next request/event, satisfies hibernation criteria. Runtime decides when to hibernate (currently after 10 seconds of inactivity)
Hibernated Removed from memory. Hibernated WebSocket connections stay connected
Inactive Completely removed from host process, may need cold start. Initial state of all Durable Objects

Hibernation conditions

Hibernation can only occur if all of the following are true [3]:

  • No setTimeout/setInterval scheduled callbacks are set
  • No I/O operation or promise passed to this.ctx.waitUntil() remains unfinished, and no outbound connection remains open
  • No WebSocket standard API is used
  • No request/event is still being processed

After 10 seconds of no incoming request or event (with all conditions satisfied), the object transitions to hibernated. In-memory state is discarded at this point [3].

Eviction

While in the idle, in-memory, non-hibernateable state, after 70–140 seconds of inactivity, the Durable Object is evicted entirely from memory and potentially from the Cloudflare host, transitioning to inactive [3].

Pending operations prevent eviction

Pending I/O and open outbound connections prevent eviction. Each operation prevents eviction until it completes or for up to 15 minutes from when it starts [3]. While an operation prevents eviction, the Durable Object continues to incur duration charges.

Shutdown behavior

Durable Objects will occasionally shut down and restart (running the constructor again) due to:

  • New Worker deployments with code updates
  • Lack of requests following state transitions
  • Cloudflare updates to the Workers runtime
  • Runtime decisions on where to host objects [3]

No shutdown hooks are provided. The recommended pattern is to write state incrementally to storage as you go, rather than relying on a final flush [3].

Duration charges

A Durable Object incurs charges only when it is actively running in-memory or idle in-memory and non-hibernateable [3]. Hibernated objects do not incur duration charges.


3. Storage & Persistence

SQLite-backed storage (recommended)

Cloudflare recommends all new Durable Object namespaces use the SQLite storage backend [4]. It provides:

  • A familiar SQL API for relational queries, indexes, and transactions
  • Point-in-Time Recovery (PITR) — restore to any point in the past 30 days
  • Both synchronous and asynchronous KV API methods
  • Better performance than the legacy key-value backend

Configure in Wrangler:

{
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["ChatRoom"] }
  ]
}

SQL API

Accessed via ctx.storage.sql.exec(query, ...bindings) [4]:

  • Supports ? placeholders for parameter bindings
  • Multiple SQL statements can be executed in one call
  • Returns a SqlStorageCursor (Iterable/Iterator) with .toArray(), .one(), .raw() methods
  • Supports FTS5 (full-text search), JSON extension, and Math functions
  • Cannot execute BEGIN TRANSACTION or SAVEPOINT — use ctx.storage.transaction() or transactionSync() instead

KV API (on SQLite-backed objects)

  • Synchronous: ctx.storage.kv.get/put/delete/list — data stored in a hidden __cf_kv SQLite table
  • Asynchronous: ctx.storage.get/put/delete/list — supports multi-key operations (up to 128 keys), allowConcurrency, noCache options

Transactions

  • Automatic write coalescing: Multiple put()/delete() calls without intervening await are submitted atomically [4]
  • transactionSync(callback): Synchronous transaction for SQL operations (SQLite-backed only)
  • transaction(closure): Asynchronous transaction for KV operations
  • sync(): Flushes pending writes to disk

PITR (Point-in-Time Recovery)

SQLite-backed Durable Objects support restoring the entire database to any point in the past 30 days [4]:

  • getCurrentBookmark() — get current point in time
  • getBookmarkForTime(timestamp) — get bookmark for a specific time
  • onNextSessionRestoreBookmark(bookmark) — schedule restore on next restart

Storage limits (SQLite-backed)

Feature Limit
Storage per Durable Object 10 GB (Paid) / 1 GB (Free)
Key + Value size 2 MB combined
Max columns per table 100
Max rows per table Unlimited (within per-object storage)
Max string/BLOB/row size 2 MB
Max SQL statement length 100 KB
Max bound parameters per query 100

[5]


4. Networking & Communication

RPC (preferred)

Workers call Durable Object methods directly via stubs:

const stub = env.MY_OBJECT.getByName("foo");
const result = await stub.someMethod(arg);

Each RPC method call is billed as one request. Calls on returned RpcTarget stubs are part of the same session [6].

HTTP fetch

Durable Objects can also be invoked via fetch() to their URL, using the standard fetch(request, env) handler.

WebSockets

Durable Objects support the WebSocket Standard API and the WebSocket Hibernation API [1]:

  • Standard: webSocketAccept(), webSocketMessage(), webSocketClose()
  • Hibernation: webSocketAcceptHibernatable() — reduces costs by not incurring billing during inactivity
  • WebSocket message size limit: 32 MiB (received)
  • A 20:1 billing ratio applies to incoming WebSocket messages [6]

Inter-object communication

Durable Objects can call other Durable Objects via RPC stubs. The parent-child pattern is recommended for hierarchical data (workspaces → projects, game servers → matches) [2].

Simultaneous outgoing connections

6 per request (same as Workers) [5].


5. Alarms API

Durable Objects provide an Alarms API to schedule future wake-ups [1][4]:

  • setAlarm(scheduledTime) — schedule a wake-up (millisecond granularity, may be delayed up to 1 minute)
  • getAlarm() — retrieve current alarm time
  • deleteAlarm() — cancel a pending alarm
  • alarm() handler method — invoked when the alarm fires
  • Guaranteed at-least-once execution
  • Each setAlarm() is billed as a single row written
  • Alarm handler wall time limit: 15 minutes [5]

Use cases: periodic work, batch processing, TTL expiration, subscription renewals, game timeouts.


6. Use Cases & Patterns

When to use Durable Objects [2]:

  • Coordination — multiple clients interacting with shared state (chat, multiplayer, collaborative docs)
  • Strong consistency — operations must be serialized (inventory, booking, turn-based games)
  • Per-entity storage — each user/tenant/resource needs isolated data (multi-tenant SaaS)
  • Persistent connections — long-lived WebSockets (real-time notifications, live updates)
  • Scheduled work per entity — per-entity timers (subscription renewals, game timeouts)

When NOT to use Durable Objects [2]:

  • Stateless request handling (use plain Workers)
  • Maximum global distribution (Workers handle at nearest edge)
  • High fan-out with independent requests

Key design patterns [2]:

  1. Model around your "atom" of coordination — one DO per chat room, game session, document, user
  2. Use deterministic IDs — getByName() with meaningful strings for consistent routing
  3. Parent-child relationships — parent coordinates, children handle their own state independently
  4. Location hints — get(id, { locationHint: region }) for latency-sensitive apps
  5. Initialize in constructor — use blockConcurrencyWhile() for migrations/schema setup
  6. Write state incrementally — no shutdown hooks; persist as you go
  7. Use indexes — for frequently-queried columns
  8. Avoid blockConcurrencyWhile() on every request — limits throughput to ~200 req/s

Throughput per Durable Object [2]:

Operation type Throughput
Simple pass-through ~1,000 req/s
Moderate processing ~500–750 req/s
Complex operations ~200–500 req/s

Real-world Cloudflare uses [1]:

  • Cloudflare Queues (10x speedup)
  • Stream Live (live streaming)
  • AI Gateway (WebSockets + auth)
  • Workers Builds (CI/CD)
  • Workflows (durable execution)
  • D1 (global database)
  • AI Gateway scaling (billions of logs)

7. Limitations & Constraints

Constraint Value
CPU per request 30s default, configurable to 5 min [5]
Memory per object 128 MB [6]
Storage per object 10 GB (Paid) / 1 GB (Free) [5]
Key + Value size 2 MB [5]
WebSocket message size 32 MiB [5]
Simultaneous outgoing connections 6 [5]
Max DO classes per account 500 (Paid) / 100 (Free) [5]
Storage per account Unlimited (Paid) / 5 GB (Free) [5]
Wall time (RPC/HTTP) Unlimited while caller connected [5]
Wall time (alarms) 15 minutes [5]
No file system access N/A
No shutdown hooks Design for incremental persistence [3]
Single-writer constraint Inherent in single-threaded model [1]
In-memory state lost on hibernation Must persist to storage [3]
PRAGMA user_version not supported Use alternative schema versioning [2]
Cursors across await No snapshot isolation guarantee [4]

Known pain points:

  • No shutdown hooks — must write state incrementally
  • In-memory state is volatile (lost on hibernation/eviction)
  • Non-storage I/O (fetch(), R2) allows request interleaving — use optimistic locking
  • blockConcurrencyWhile() across I/O is an anti-pattern
  • Code updates cause eventual-consistency window where new Worker version may call old DO version
  • Storage billing for SQLite starts January 2026

8. Pricing

Compute billing [6]:

Dimension Free Plan Paid Plan
Requests 100,000/day 1M/month included, +$0.15/M
Duration 13,000 GB-s/day 400,000 GB-s/month included, +$12.50/M GB-s
  • Duration is billed in wall-clock time while active and not hibernateable
  • Billed for 128 MB memory allocation regardless of actual usage
  • WebSocket messages: 20:1 ratio (100 messages = 5 billable requests)
  • RPC: each method call = 1 request; calls on returned stubs are free

Storage billing (SQLite) [6]:

Dimension Free Plan Paid Plan
Rows read 5M/day 25B/month included, +$0.001/M
Rows written 100K/day 50M/month included, +$1.00/M
Stored data 5 GB total 5 GB-month included, +$0.20/GB-month
  • Storage billing starts January 7, 2026
  • KV methods (get/put/delete/list) are billed as rows read/written
  • Each setAlarm() = 1 row written
  • Deletes count as rows written

Cost examples [6]:

  • Simple coordination service (1.5M requests, 1M seconds active): ~$5.15/month
  • 100 DOs, 50 WebSocket connections each, 8h/day: ~$142.95/month
  • 100 DOs, 1 WebSocket each, 24/7: ~$419.30/month
  • 100 DOs, 100 hibernatable WebSockets each, 1 msg/min: ~$20.65/month (WebSocket Hibernation saves ~95% vs non-hibernated)

Key insight:

WebSocket Hibernation dramatically reduces costs for real-time applications with sparse message patterns. Example 4 vs Example 2: $20.65 vs $142.95 for similar traffic [6].


9. Comparison with Alternatives

Feature Durable Objects Workers (stateless) KV D1 R2
Stateful ✅ Per-object ❌ ✅ (shared) ✅ (shared) ✅ (shared)
Strong consistency ✅ (per-object) N/A ❌ (eventual) ✅ ❌ (eventual)
Single-writer guarantee ✅ N/A ❌ ❌ ❌
Colocated storage ✅ ❌ ❌ ❌ ❌
WebSockets ✅ ✅ ❌ ❌ ❌
Alarms/scheduling ✅ Per-object ❌ (Cron) ❌ ❌ ❌
SQL queries ✅ (embedded) ❌ ❌ ✅ ❌
Global uniqueness ✅ Per-object ❌ ❌ ❌ ❌
Scale Millions of objects Unlimited 100M keys 100M rows Unlimited
Best for Coordination, real-time, per-entity state Stateless APIs Config, caching Relational data File storage

vs. Traditional serverful (DynamoDB, Redis):

  • No infrastructure management — DOs are serverless, auto-scaled, auto-migrated
  • No network hop to storage — storage is colocated (zero-latency)
  • No distributed locking — single-threaded model eliminates race conditions
  • No capacity planning — millions of objects scale elastically
  • Trade-off: Single-writer per object (shard your data), 128 MB memory, 30s CPU limit

10. Recent Developments (2024–2026)

  • SQLite storage GA: SQLite-backed Durable Objects moved from beta to general availability. New DO classes should use SQLite [1].
  • Free plan availability: SQLite-backed Durable Objects are now available on the Workers Free plan [1].
  • PITR API: Point-in-Time Recovery (30-day window) for SQLite-backed DOs [4].
  • WebSocket Hibernation: Reduces costs by not billing during WebSocket inactivity [1].
  • RPC as default: Projects with compatibility date ≥ 2024-04-03 should use RPC over fetch [2].
  • Storage billing (Jan 2026): SQLite storage billing enabled January 7, 2026 [4][6].
  • Data Studio (Beta): View and edit SQLite-backed DO storage data through the Cloudflare dashboard [7].
  • WebGPU: GPU access from within Durable Objects in local development [7].
  • Rust API: Durable Objects can be written in Rust [7].
  • deleteAll() deletes alarms: For compatibility date ≥ 2026-02-24, deleteAll() also deletes active alarms [4].
  • KV backend deprecation: Creating new namespaces with KV storage backend is no longer supported for accounts without an existing KV-backed namespace [4].

Open Questions

  1. Storage per object raising to 10 GB: The docs note "Storage per Durable Object with SQLite is currently 1 GB. This will be raised to 10 GB for general availability" [1], but the limits page already lists 10 GB for Paid [5]. Clarification on exact GA timeline needed.
  2. Global uniqueness guarantees under code updates: Known issue where a request may reach a new Worker version while calling a DO still running a previous version [3]. No documented mitigation beyond Gradual Deployments.
  3. Multi-region data residency: Location hints are suggestions, not guarantees. Jurisdictional restrictions exist but details are in a separate doc not fetched here.
  4. Performance under contention: The ~1,000 req/s soft limit per DO is documented, but behavior under sustained overload (queueing, backpressure) is not fully specified.

Sources

[1] Cloudflare Durable Objects — What are Durable Objects? (concepts) https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/

[2] Cloudflare Durable Objects — Rules of Durable Objects (best practices) https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/

[3] Cloudflare Durable Objects — Lifecycle of a Durable Object (concepts) https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/

[4] Cloudflare Durable Objects — SQLite-backed Durable Object Storage (API reference) https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/

[5] Cloudflare Durable Objects — Limits (platform) https://developers.cloudflare.com/durable-objects/platform/limits/

[6] Cloudflare Durable Objects — Pricing (platform) https://developers.cloudflare.com/durable-objects/platform/pricing/

[7] Cloudflare Durable Objects — Documentation Index https://developers.cloudflare.com/durable-objects/llms.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment