Repository navigation
fix: evict the local caches by key prefix too, instead of guessing - #2222
Merged
Merged
Conversation
netomi
force-pushed
the
fix/evict-local-by-prefix
branch
2 times, most recently
from
September 16, 2026 06:05
670b9c0 to
712a6a7
Compare
netomi
force-pushed
the
fix/evict-local-by-prefix
branch
from
September 16, 2026 06:15
712a6a7 to
18a38b8
Compare
Third part of #1767. Redis has been dropping an extension's keys in one scan since the first PR in this stack; the local caches still enumerate every key they can imagine - (3 aliases + versions) x 13 target platforms, 2600 of them for an extension with 200 versions, nearly all of which do not exist. They can be asked instead. A Caffeine cache drops the matching keys from its key set; a JCache one - which is what extension.json is kept in when Redis is off - iterates its entries and removes them. Either is bounded by what the cache holds, a few thousand entries at most, rather than by versions times platforms. It is also complete, where guessing is not: the fallback enumerates versions from the extension, so a version that was just deleted is no longer among them and its entry survives until the TTL. Guessing stays for a cache that is neither, which in practice means a test double. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
netomi
force-pushed
the
fix/evict-local-by-prefix
branch
from
September 16, 2026 06:19
18a38b8 to
c91ae46
Compare
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The JCache test currently passes through the fallback path and does not prove the new branch works.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Replaces costly guessed cache-key eviction with prefix-based scanning for local caches.
Changes:
- Adds Caffeine and JCache prefix eviction.
- Shares extension-key prefixes across cache generators.
- Adds real-cache eviction tests.
File summaries
| File | Description |
|---|---|
CacheService.java |
Implements prefix eviction for Redis, Caffeine, and JCache. |
LatestExtensionVersionCacheKeyGenerator.java |
Exposes the shared extension prefix. |
CacheServiceEvictionTest.java |
Tests local cache eviction behavior. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Review follow-up, twice over. Iterating a javax.cache.Cache walks entries rather than keys, and its iterator copies each value and refreshes each entry's access expiry on the way past - work an eviction has no use for, over values as large as an extension's JSON, and over a cache holding up to its maximum size. The Caffeine cache underneath is reached with unwrap instead and its key set scanned, which is what the directly-wrapped caches already did. Both local cache managers end up on the same path. The JCache test did not distinguish the scan from the guessing fallback: its extension carried the version whose key it asserted on, so the fallback named that key too and every assertion held with the scan removed. All three local cache tests now use an extension with no versions loaded, so the cached keys are ones only a scan can reach - and all three fail when the scan is taken out. Co-Authored-By: Claude Opus 5 (1M context) <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.
Third part of #1767, stacked on #2220 (→ #2219) — see the analysis on the issue.
What is left to fix
#2219 made Redis drop an extension's keys in one scan. The local caches — which is what a registry runs with when
ovsx.redis.enabledis off, and whatextension.jsonis kept in throughJCacheCacheManager— still enumerate every key they can imagine:2639 evictions for an extension with 200 versions, nearly all of keys that do not exist.
The change
Ask the cache what it holds instead of guessing what it might. Both local cache managers are Caffeine — one wrapped directly (files, settings), one through the JCache API (
extension.jsonand the latest-version caches, when Redis is off) — so both are reached through the native cache and their key set is scanned. Redis keeps scanning its keyspace, as before.Through the native cache in the JCache case rather than its own iterator:
CacheProxy$EntryIteratorwalks entries rather than keys, and per entry it copies the value and refreshes the access expiry, neither of which an eviction has any use for, over values as large as an extension's JSON.This is about completeness, not speed. Guessing costs
(3 aliases + versions) × 13 target platformslookups and a scan costs the size of the cache, so below roughly 76 versions the guessing does fewer operations; the performance win for Redis was #2219. What a scan buys is that it cannot miss a key nobody thought to name — the fallback enumerates versions fromextension.getVersions(), so a version that was just deleted is no longer among them and its cached JSON survives until the TTL. Deletion is when staleness is most visible.It also means the lazy
versionsassociation is no longer read for any eviction: every cache in use can clear by prefix, so the snapshot #2220 had to take is never needed.Guessing stays for a cache that is neither, which in practice means a test double.
Testing
CacheServiceEvictionTestgrows cases built on real caches rather than mocks — a Caffeine one and a JCache over Caffeine, the same combinationCacheConfigbuilds. Entries go in forfoo.bar,foo.bar2,foo.bar-bazandother.bar, one extension is evicted, and only its own entries are gone.Each of them evicts an extension with no versions loaded, so the cached keys are ones the guessing fallback could not name: with the local scan disabled, all three fail. A further case pins that the JCache API's own iterator is not used.
Note the third of those:
foo.bar-bazsurviving is a consequence of the key terminator from #2219. Before that commit the local scan would have swept it, exactly as the Redis glob did.Full server suite green; pre-commit hooks pass.
Not pursued
Step 4 of the issue — generation-stamped keys, which would remove eviction rather than speed it up — is deliberately left alone: it needs the stamp readable before every cache lookup, which is complexity of a different kind for a problem this stack has already brought down to one scan.
🤖 Generated with Claude Code