Skip to content

feat(response-store): read response metadata from R2 - #3339

Merged
james-elicx merged 14 commits into
mainfrom
codex/response-store-r2-metadata
Sep 21, 2026
Merged

james-elicx merged 14 commits into
mainfrom
codex/response-store-r2-metadata

Conversation

@james-elicx

@james-elicx james-elicx commented Sep 20, 2026

Copy link
Copy Markdown
Member

Summary

  • store response bodies and hot-read metadata together in one version-scoped, layout-versioned R2 active object
  • make fresh hits and misses avoid the metadata Durable Object while preserving bounded isolate-local negative miss caching
  • keep SQLite revisions for write coordination, regeneration, tag invalidation, and anti-resurrection tombstones
  • use bounded R2 ETag CAS, reuse fetch ETags and regenerated bodies, and remove revision-object cleanup operations
  • keep all cache-key query parameters as ordinary hashed input so visitor-controlled URLs cannot select another key
  • stream oversized response metadata from an envelope on the same R2 object when it cannot fit custom metadata
  • durably queue purge tombstones and drain them through bounded, retryable Durable Object invocations

Compatibility

This is API/deployment compatible, not an in-place warm-cache migration. Existing revision-scoped R2 objects are treated as cold misses and refill through the unchanged API. The new r2-v1 R2 and SQLite layout remains scoped by application Worker version ID while isolating it from legacy cache-Worker rollbacks and rolling deployments. Old objects remain available until version cleanup.

Validation

  • npm run check in packages/workers-response-store
  • npm run build:examples in packages/workers-response-store
  • npx vitest run tests/e2e.test.ts tests/service-binding-e2e.test.ts (65 passed)
  • vp test run tests/cloudflare-response-store-worker.test.ts tests/cache-adapters-config.test.ts (31 passed)
  • git diff --check

R2 operations by scenario

The new layout stores the response body and the metadata needed by the fetch path in one object:

runtime-cache/<application-version-id>/r2-v1[/shards-<count>]/<key-hash>/active

The counts below are normal, uncontended operations per cache key or matched entry. They exclude Workers Cache operations, Durable Object RPC and SQLite operations, user regeneration callbacks, and edge-cache purge API calls.

Scenario R2 GET R2 HEAD R2 PUT Context
Workers Cache hit 0 0 0 The Response Store binding, R2, and metadata Durable Object are bypassed.
First uncached Response Store miss 1 0 0 The /active object is absent or tombstoned. An absent GetObject can appear as R2 error 10007 in tracing even though the binding resolves get() to null and returns a normal Response Store miss.
Repeated same-isolate miss within the one-second negative-cache window 0 0 0 The first concurrent request performs one shared GET; followers and later requests in that isolate use the bounded negative cache. Other isolates can still perform their own GET.
Fresh stored read after a Workers Cache miss 1 0 0 The same GET returns response metadata, body, and ETag.
Stale read when another request owns the SWR claim 1 0 0 The stale response is returned without another regeneration write.
Stale read that wins the SWR claim and regenerates successfully 1 0 1 The request GET returns stale content and its ETag; the conditional PUT runs in waitUntil() and reuses that ETag.
Hard-expired or unreadable stored response regenerated successfully 1 0 1 Regeneration is synchronous. The initial GET supplies the ETag, so no HEAD is needed.
Initial put() for a new key 0 0 1 A conditional create uses etagDoesNotMatch: "*".
put() replacing an existing key 0 1 1 HEAD obtains the current ETag, followed by a conditional PUT.
Successful coalesced put() follower 0 0 0 The in-isolate leader performs the R2 write; the follower response body is discarded.
put(..., { purgeExisting: true }) 0 0-1 1 The R2 work is the same as the corresponding new or replacement PUT. Edge invalidation adds no R2 operation.
Manual refresh() 0 1 1 Per successfully regenerated entry. Refresh starts from Durable Object metadata, so it does not already have an R2 ETag.
Tag, path-prefix, or purgeEverything purge 0 1 1 Per matched active entry. The PUT replaces /active with a zero-byte, higher-revision tombstone.
Refresh or purge with no matching entries 0 0 0 Shard fan-out affects Durable Object calls, not R2 operations.
Tombstone retry when the required tombstone is already present 0 1 0 HEAD observes an equal or newer revision and stops.
Tombstone retry when it is still required 0 1 1 Normal retry cost per entry.
Failed response publication compensation +1 +1 Normally adds one HEAD and one tombstone PUT after whatever portion of the response write was attempted.
Pending-reservation cleanup alarm 0 0 0 Expired reservation records are SQLite-only; the new layout has no revision-specific R2 object to delete.
getTagExpiration() 0 0 0 This remains a metadata Durable Object operation.

For N successfully refreshed entries, the normal R2 cost is N HEAD + N PUT. For N purged active entries, it is also N HEAD + N PUT. Sharding changes the metadata fan-out but not the per-entry R2 count.

There are no R2 DELETE operations in the current runtime path. Purge and failed-publication compensation overwrite the same /active object with a higher-revision tombstone. Oversized response headers are stored as a prefix in the same object body and do not add another R2 operation.

Conditional-write contention

Durable Object reservations allocate and fence monotonically increasing revisions, but they are not exclusive locks held across R2 I/O. Two reserved revisions, or a write and a purge, can therefore reach the same /active object concurrently. Conditional ETag writes prevent the older revision from overwriting the newer one.

The bounded CAS loop has these worst-case operation counts before returning a winner or failing:

Conditional write Maximum R2 operations inside the CAS loop
The triggering GET already supplied an ETag, or the write is a known create 3 HEAD + 3 PUT
No ETag is available before the write 4 HEAD + 3 PUT
Tombstone publication 4 HEAD + 3 PUT

These are defensive contention bounds, not normal request costs. A superseded synchronous regeneration can also perform one final GET to load the winning response.

@pkg-pr-new

pkg-pr-new Bot commented Sep 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vinext/cloudflare

npm i https://pkg.pr.new/@vinext/cloudflare@3339

create-vinext-app

npm i https://pkg.pr.new/create-vinext-app@3339

@vinext/types

npm i https://pkg.pr.new/@vinext/types@3339

vinext

npm i https://pkg.pr.new/vinext@3339

@cloudflare/workers-response-store

npm i https://pkg.pr.new/@cloudflare/workers-response-store@3339

commit: a7a3119

@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
response-store-demo preview production
workers-cache preview production
kv preview production
static-export preview production
web preview production

@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared a7a3119 against base 94c5adc using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 142.3 KB 142.3 KB ⚫ +0.0%
Client entry size (gzip) vinext 129.7 KB 129.7 KB ⚫ +0.0%
Dev server cold start vinext 3.43 s 3.40 s ⚫ -0.7%
Production build time vinext 3.91 s 3.92 s ⚫ +0.2%
RSC entry closure size (gzip) vinext 127.5 KB 127.5 KB ⚫ -0.0%
Server bundle size (gzip) vinext 225.5 KB 225.5 KB ⚫ -0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head 8d28e04.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head 41dd24e.

Comment thread packages/workers-response-store/src/binding.ts Outdated
Comment thread packages/workers-response-store/src/binding.ts Outdated
Comment thread packages/workers-response-store/src/metadata-do.ts
@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Posted 3 actionable inline findings on head 8d28e04f.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head 41dd24e.

Comment thread packages/workers-response-store/src/binding.ts Outdated
Comment thread packages/workers-response-store/src/binding.ts Outdated
Comment thread packages/workers-response-store/src/binding.ts
@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Posted 3 actionable inline findings on head 41dd24e1.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head d86e018.

Comment thread packages/workers-response-store/src/metadata-do.ts Outdated
Comment thread packages/workers-response-store/src/binding.ts Outdated
Comment thread packages/workers-response-store/src/metadata-do.ts Outdated
Comment thread packages/workers-response-store/src/binding.ts Outdated
@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Posted 4 actionable inline findings on head d86e0180.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head 5da0b20.

Comment thread packages/workers-response-store/src/metadata-do.ts
@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding on head 5da0b205.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head 47dd81c.

Comment thread packages/workers-response-store/src/binding.ts Outdated
@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding on head 47dd81c.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head 6db8145.

@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

LGTM!

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head 3d135b5.

Comment thread packages/workers-response-store/src/binding.ts
@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding on head 3d135b5a. Targeted checks passed.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head b0f0d57.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head d5e05a6.

Comment thread packages/workers-response-store/src/binding.ts Outdated
Comment thread packages/workers-response-store/src/binding.ts
@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Posted 2 actionable inline findings on head b0f0d573.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head b15d3dd.

Comment thread packages/workers-response-store/src/metadata-do.ts Outdated
@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding on head b15d3dd9. Package checks passed; E2E bundles were not built.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Time limit: 10 minutes. Complete the review and post all findings within that limit. Review exact head 896807a.

@ask-bonk

ask-bonk Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

LGTM!

github run

@james-elicx
james-elicx force-pushed the codex/response-store-r2-metadata branch from 896807a to 53f7c9c Compare September 21, 2026 08:55
@james-elicx
james-elicx force-pushed the codex/response-store-r2-metadata branch from 53f7c9c to a7a3119 Compare September 21, 2026 12:04
@james-elicx
james-elicx marked this pull request as ready for review September 21, 2026 16:38
@james-elicx
james-elicx merged commit ccc7c20 into main Sep 21, 2026
101 of 103 checks passed
@james-elicx
james-elicx deleted the codex/response-store-r2-metadata branch September 21, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant