Repository navigation
feat(cache): read several remote cache entries per key - #955
Merged
Merged
Conversation
fspy benchmarklinuxmacoswindows |
wan9chi
force-pushed
the
remote-cache-entries-per-key
branch
from
November 7, 2026 10:10
8d71aef to
808d150
Compare
Vite+'s Cloudflare remote cache now keeps a key's newest entries instead of only the last one (voidzero-dev/vite-plus-remote-cache-cloudflare#47). An exact fetch returns them newest first: { kind: "exact", entries: [{ value, blob_id }] } - The client decodes the list. An empty list, or the single entry that earlier servers sent, is a malformed response. - A lookup checks the entries newest first and restores the first one that passes validation. If none does, the miss is the newest entry's: its fingerprint mismatch, or the read failure if it doesn't decode. - The e2e backend is that pull request's head. The tap's request lines list the entries of an exact fetch by blob number, newest first. New e2e cases run against the Worker: - older_entries: a lookup restores the first entry that matches, here the oldest. - newest_entry_explains_miss: when none matches, the miss reason comes from the newest entry. - entries_per_key: a store retires the key's oldest entry past the limit. - entries_per_key_isolation: stores to one key don't retire another key's entries, even when the fetch falls back to it. - expired_entries: expired entries aren't fetched, and a key whose entries have all expired is a miss without a fallback. - expired_entry_takes_no_slot: an expired entry doesn't count against the limit, so a store keeps the older live entry. To set them up, `remote-cache-server start` takes `--entries-per-key N` for the namespace, and `remote-cache-server expire-entry NUMBER` makes the entry with that blob expire now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wan9chi
force-pushed
the
remote-cache-entries-per-key
branch
from
November 7, 2026 10:41
808d150 to
083446a
Compare
wan9chi
marked this pull request as ready for review
November 7, 2026 11:00
wan9chi
added a commit
that referenced
this pull request
Nov 7, 2026
## Motivation An exact fetch now returns up to `entries_per_key` entries, 8 by default and at most 16, and the Worker caps each value at 4 MiB. Uncompressed, values are large: in a replay of five public repositories' CI, the median was 111 KB, the p90 1.66 MB and the largest 3.10 MB. Eight of those make every fetch heavy, and large repositories exceed the cap. RFC 0003 of Vite+'s Cloudflare remote cache asks `vp run` to compress values with zstd. They're opaque bytes to the server, so this changes only the client. ## Changes - **Upload.** A value is `zstd(wincode(CacheEntryValue))`, at zstd's default level, the same as output archives. On 42 real values from local task caches (18.9 MB), level 3 compressed to 2.59 MB in milliseconds. Level 9 was about 22% smaller but about 5 times slower, and a value is compressed as its task finishes. - **Fetch.** A value decompresses through a reader that stops past 32 MiB, whatever size its frame declares. A value that isn't zstd, is too large, or doesn't decode is still a miss with `remote cache value is corrupt`. - **Size limit.** A value larger than 32 MiB before compression isn't uploaded (`cache entry is larger than 32 MiB`), since no client would read it back. - **Key header.** Both keys now carry a remote value format version. Clients that encode values differently never share entries, and so never push out each other's within a key's limit. The local cache's schema version, and so its directory, stays the same. None of the snapshots change. Stacked on #955. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes remote cache wire format and key matching; old uncompressed entries become misses for new clients, with safeguards for bombs and oversized uploads. > > **Overview** > Remote cache **values** are now **`zstd` over the existing wincode payload** on upload, and decompressed with a **32 MiB cap** on fetch so oversized or malformed frames are treated as corrupt misses instead of exhausting memory. > > **Uploads** reject uncompressed values above that limit; new **`UploadError`** variants cover compression failure and **too large**. **Reads** route decode failures through **`DecodeValueError`** (decompress, too large, or wincode decode). > > Cache **keys** gain a **`remote_value_format`** field in the header (version **1**) so clients using different value encodings do not share or evict each other's entries in the same remote namespace. > > Tests cover round-trip encoding, non-zstd/truncated payloads, size limits, and mismatched format versions in fallback keys. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 8bd6a40. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Vite+'s Cloudflare remote cache now keeps each key's newest entries instead of only the last one stored (voidzero-dev/vite-plus-remote-cache-cloudflare#47). Runs built on an older default-branch commit, such as PR runs without a merged result, stacked PRs, and machines that haven't pulled, can then hit an entry stored earlier. An exact fetch now returns a list of entries, which
vp runhas to decode and choose from. Which entry a lookup restores, which ones a store retires, and how expiry affects both are new behaviour, so the e2e cases check the whole path against the Worker.Changes
Client.
vt_remote_cachedecodes{ kind: "exact", entries: [{ value, blob_id }] }, newest first. An empty list, or the single entry earlier servers sent, is a malformed response, whichvp runtreats as a failed read.Lookup. A remote lookup checks the entries newest first and restores the first one that passes validation. If none does, the miss is the newest entry's: its fingerprint mismatch, or its read failure if it doesn't decode or its blob doesn't match its value.
E2E backend. The pinned Worker is that pull request's head. The tap's request lines list an exact fetch's entries by blob number, newest first:
That's the only change to existing snapshots.
E2E cases. New
remote_cache_entriescases, each against its own backend:older_entriesnewest_entry_explains_missentries_per_keyentries_per_key_isolationexpired_entriesexpired_entry_takes_no_slotTo set them up:
remote-cache-server start --entries-per-key Nregisters the namespace with that limit.remote-cache-server expire-entry NUMBERmakes the entry with that blob expire now. As before the Worker's scheduled cleanup, nothing removes it.Stacked on #811.
🤖 Generated with Claude Code
Note
Medium Risk
Changes core remote cache hit/miss selection and tightens fetch response decoding; behavior is heavily covered by new unit and e2e tests but wrong selection could cause unexpected cache misses or restores.
Overview
Remote cache fetches can return multiple entries per key (newest first) instead of a single value/blob pair. The client and session layer are updated end-to-end for that protocol.
vt_remote_cachedecodes exact matches as{ entries: [...] }, rejects empty lists and legacy single-entry shapes as malformed, and exports a publicEntrytype.Remote lookup in
vtwalks entries newest-first inresolve, restores the first that decodes, has a matching blob, and passes fingerprint validation, and when none match the miss is attributed to the newest entry (mismatch or read failure).E2E tooling pins a newer Cloudflare Worker, adds
remote-cache-server start --entries-per-key Nandexpire-entry, and logs fetches asexact [2, 1]. Newremote_cache_entriessnapshot cases cover multi-entry restore, per-key limits, isolation, and expiry; existing remote-cache snapshots only gain the bracketed entry list in request lines.Reviewed by Cursor Bugbot for commit 083446a. Bugbot is set up for automated code reviews on this repo. Configure here.