{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreihsusg4aj4juntocbw6lsolyruwjuhgmp5vnf5wlscdhduwacojte",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3motr3d6nazp2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreibceqlwgum37bnzmdj4h4f67jmzjssrk4i3thkepgcfie6fcxu4qq"
    },
    "mimeType": "image/webp",
    "size": 74268
  },
  "path": "/riversea/400-ad-accounts-x-3-subrequests-silent-data-loss-in-cloudflare-workers-and-how-i-fixed-it-43ie",
  "publishedAt": "2026-06-22T01:07:59.000Z",
  "site": "https://dev.to",
  "tags": [
    "cloudflare",
    "serverless",
    "webdev",
    "tutorial",
    "Full post →"
  ],
  "textContent": "Adding a single KV cache write broke my Worker — and it didn't throw a single error to the caller.\n\nI run a Korean D2C ad-ops pipeline where one Worker fans out performance checks across ~400 ad accounts per request. The math looked fine: 400 accounts × 2 API calls = 800 subrequests, safely under Cloudflare's hard 1000-per-invocation ceiling. Then I added KV writes to cache each result. 400 × 3 = 1200. The Worker silently stopped processing the last ~200 accounts. No 5xx. No exception propagated. Just missing data. It took two days on `wrangler tail` to surface this line:\n\n\n\n    workers/runtime/fetch: subrequest count exceeded limit of 1000\n\n\nThe thing that makes this limit especially tricky for fan-out patterns: it resets _per invocation_ , not per batch or queue message. And the count includes more than you'd expect — every `fetch()`, every KV `get`/`put`, every R2 operation, every D1 query, every Durable Object stub call. The one useful exception: `Queue.sendBatch()` does **not** count against it. That's the escape hatch the fix depends on.\n\nThe architectural answer is splitting the fan-out trigger from the fan-out work. The entry Worker receives the request and immediately calls `env.QUEUE.sendBatch()` with one message per account — zero upstream fetches, zero KV writes, well under any limit. Each queue consumer invocation then handles a small batch of messages (I run `max_batch_size = 10`), doing its 3 subrequests per account in isolation. Ten messages × 3 subrequests = 30 per invocation. The ceiling becomes irrelevant.\n\nThe tradeoff worth knowing before you commit to this pattern: Queues are fire-and-forget. If your use case requires returning all 400 account summaries in a single synchronous HTTP response — like a live dashboard — this approach alone won't get you there. That's a different problem requiring a Durable Object as an aggregation point, which adds its own subrequest considerations.\n\nI wrote up the full breakdown — including the Durable Object aggregation pattern for synchronous fan-out, the exact `wrangler.toml` consumer config, and the complete subrequest reference table — over on dailymanuallab.com.\n\nFull post →",
  "title": "400 ad accounts 3 subrequests = silent data loss in Cloudflare Workers (and how I fixed it)"
}