Skip to content

Fixing Durable Object storage operation timeouts

Covers oversized single writes and misuse of blockConcurrencyWhile() as the two most common causes of a Durable Object storage timeout. Does not cover the equivalent, differently-worded timeout message that D1 itself surfaces (D1 runs on Durable Objects internally, but its error text and remediation — sharding queries, not blockConcurrencyWhile — are D1-specific, not this playbook).

Unverifiedno reproductions yetWhy this confidence?
Revision 1published by DevYou curationrevision history
Run the diagnosisEvidence and compatibility

Symptoms

The diagnostic path

8 steps. Every step is written out below in full — the interactive version simply follows the branches for you.

  1. Step 1 · StartDurable Object storage operation exceeded timeout

    A ctx.storage write inside a Durable Object fails and the object resets — any in-memory state is lost, and in-flight requests that were touching storage are cut off.

    What happens next

    • passed step 2, Check the size of the value being written
  2. Step 2 · TestCheck the size of the value being written

    Log the size of whatever is being passed to the failing storage call — a single oversized put() (a big JSON blob, base64-encoded file, or an entire accumulated history) is the most common cause.

    Read-onlyts
    console.log("write size:", JSON.stringify(value).length);

    Expected result

    A value size in the hundreds of KB to multiple MB, written in a single storage call.

    What happens next

    • passed step 3, Shard the large value, or move it out of Durable Object storage
    • failed step 4, Check for blockConcurrencyWhile() wrapping slow work
    • unknown step 3, Shard the large value, or move it out of Durable Object storage
  3. Step 3 · FixShard the large value, or move it out of Durable Object storage

    Split the oversized value into smaller keys/rows written separately (using the SQL API if this is a SQLite-backed Durable Object), or store the bulk data in R2 and keep only a reference to it in the Durable Object.

    Changes statets

    Changes system or service state. Review before running.

    Changes how the same data is stored (many small rows instead of one large value) — it does not change the data itself. Existing large values already written under the old key are not migrated automatically.

    // Instead of one large blob:
    // await ctx.storage.put("history", hugeArray);
    
    // Store rows individually via the SQL API:
    for (const entry of hugeArray) {
      ctx.storage.sql.exec(
        "INSERT INTO history (ts, payload) VALUES (?, ?)",
        entry.ts,
        JSON.stringify(entry.payload)
      );
    }

    What happens next

    • passed step 5, Repeat the action that triggered the timeout
  4. Step 4 · TestCheck for blockConcurrencyWhile() wrapping slow work

    blockConcurrencyWhile() blocks all concurrency for the object unconditionally and is intended for one-time initialisation, not regular request handling. Using it around a slow or chained set of storage operations on every request can push a single call past the timeout.

    Read-onlysh
    grep -n "blockConcurrencyWhile" src/**/*.ts

    Expected result

    blockConcurrencyWhile() called on the normal request-handling path, not just in the constructor.

    What happens next

    • passed step 6, Stop using blockConcurrencyWhile() outside initialization
    • failed step 7, Not a size or blockConcurrencyWhile issue — escalate
    • unknown step 7, Not a size or blockConcurrencyWhile issue — escalate
  5. Step 5 · Verify the fixRepeat the action that triggered the timeout

    Re-run the same request/operation and confirm the write completes without the object resetting.

    Read-onlysh
    npx wrangler tail --format pretty

    Expected result

    No repeat of the storage operation exceeded timeout error in the trace for this request.

    What happens next

    • passed step 8, Resolved
    • failed step 7, Not a size or blockConcurrencyWhile issue — escalate
    • unknown step 7, Not a size or blockConcurrencyWhile issue — escalate
  6. Step 6 · FixStop using blockConcurrencyWhile() outside initialization

    Reserve blockConcurrencyWhile() for one-time setup (e.g. creating tables). For regular request handling, rely on the Durable Object runtime's input/output gates — they already serialize storage access safely without blocking unrelated requests — and use transaction() for atomic read-modify-write operations instead.

    Read-onlyts
    // ✅ Good: no blockConcurrencyWhile on the hot path
    async sendMessage(content) {
      this.ctx.storage.sql.exec("INSERT INTO messages (content) VALUES (?)", content);
      // Output gate holds the response until this write completes.
    }

    What happens next

    • passed step 5, Repeat the action that triggered the timeout
  7. Step 7 · EndNot a size or blockConcurrencyWhile issue — escalate

    If neither applies, check for many unrelated storage operations chained inside one request with no batching — the same category of problem as an oversized single write, just spread across several calls instead of one.

  8. Step 8 · EndResolved

    Storage writes now complete within the timeout and the object no longer resets under this workload.

Sources

Why this confidence?

What would strengthen it: 6 more independent reproductions. Reproductions from 3 more distinct environments.