Repository navigation
fix(provider-redis): let purges find entries when useRedisSets is off - #245
Merged
Merged
Conversation
With useRedisSets off, @keyv/redis keeps no tracking set and names every entry `sets:namespace:keyv:keyv:<key>`. keys() still read the tracking set, got nothing, and so every pattern purge - the plugin's whole invalidation path - matched nothing; entries survived until maxAge. keys() now finds those entries by the name @keyv/redis gives them, with SCAN, on every master when the client is a cluster, since SCAN only sees the node it is sent to. Configurations with redis sets on, the default, are unaffected. scripts/redis-standalone-live.cjs covers both modes against a real Redis; without redis sets both checks fail on main. It runs in e2e_redis.
Enumeration without redis sets has to SCAN every master. The live cluster run now asserts entries spread across masters, keys() finds all of them, and purging what it lists leaves nothing; both checks fail without the fix.
derrickmehaffy
force-pushed
the
fix/redis-keys-without-sets
branch
from
October 13, 2026 00:21
3ababc9 to
e27aabf
Compare
derrickmehaffy
added a commit
that referenced
this pull request
Oct 13, 2026
Brings in the redis provider fixes from #244, #245 and #249. The plugin-redis v2 path is main's as merged: makeClusterSafe, the SREM-before-UNLINK cluster delete, the background tracking-set prune, and keys() scanning every master when useRedisSets is off. The constructor now builds @keyv/redis only when there is no node-redis connection, as this branch did before, so the v3 path never wraps the ioredis shim. The v3 store itself is unchanged here; porting the same fixes to it follows separately so each one can be shown failing first. Until then test:continuity fails its two useRedisSets: false checks, because v2 now enumerates those entries and v3 does not. The lockfile is main's plus this branch's two devDependencies.
derrickmehaffy
added a commit
that referenced
this pull request
Oct 13, 2026
…ecks The merged fixes have live and recording-client checks for the plugin-redis v2 path only. The node-redis store this branch adds has the same three problems, and nothing exercised them: - on a cluster its delete() and unlinkTracked() UNLINK before they SREM, so a purge racing a write leaves a live entry outside the tracking set (#244); - nothing prunes its tracking set, so expired members are listed by keys() and the set grows for as long as the cache runs (#249); - with useRedisSets off, keys() reads the empty tracking set, so pattern purges match nothing (#245). redis-standalone-live and redis-cluster-live now also drive the provider on a real node-redis client (createClient, createCluster), constructed the way init() does on v3. The cluster harness runs every check against both backends. redis-cluster-check's node-redis recorder gains EXISTS, SSCAN, per-master clients and an SREM hook, for the command-order, prune and client-swap assertions. redis-key-continuity now requires keys() to list every entry the other backend wrote, not only to agree with it, and runs against a cluster when REDIS_CLUSTER_NODES is set, which CI's redis_cluster job now does. The new v3 checks fail at this commit; the fixes follow. The prune-race checks also assert that a SREM was issued, since a prune that does nothing passes them otherwise.
derrickmehaffy
added a commit
that referenced
this pull request
Oct 13, 2026
With useRedisSets: false there is no tracking set: entries are named `sets:namespace:keyv:keyv:<key>`, as @keyv/redis 3 names them. #245 made the v2 path's keys() find them by that name, on every master of a cluster. The v3 store's keys() still read the empty tracking set, so every pattern purge matched nothing and entries outlived it until maxAge - and after #245, v2 and v3 listed different keys for the same data, which test:continuity caught. NodeRedisStore.namedKeys() SCANs for the `sets:` names. SCAN only sees the node it is sent to, so on a cluster it walks `masters` through nodeClient(), the @redis/client 5+ API, and refuses rather than scan one node when a client lacks them. clear() uses it too, unlinking key by key on a cluster instead of a multi-key UNLINK, and SCAN instead of KEYS, which blocks the server for the whole keyspace.
derrickmehaffy
added a commit
that referenced
this pull request
Oct 13, 2026
The guide still said that with useRedisSets off the provider falls back to a SCAN that misses entries on a cluster. Since #245 it SCANs every master, so purges are complete; the reason left to keep the tracking set is cost, and the warning now says that. A new section covers plugin-redis v3: how the provider detects it, that it runs on `.native` and follows a client swap, that entries survive an upgrade and a rollback, and that v2 - and with it Node 20 - is still supported.
2 of 7 tasks
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.
What changed and why
With
useRedisSets: falsein the redis provider's options, purges never removed anything.@keyv/redis3 keeps no tracking set in that mode and names each entrysets:namespace:keyv:keyv:<key>.keys()still read the tracking set, got an empty list, and returned[].keys(), so it matched nothing. Entries lived untilmaxAge.Fix: in that mode,
keys()finds entries by the name@keyv/redisgives them, using SCAN, and returns them unqualified, as before.delMany→@keyv/redisdeletenames keys correctly). Only enumeration was broken.With redis sets on, the default, nothing changes.
Tests
scripts/redis-standalone-live.cjs(pnpm run test:redis:live, runs ine2e_redis): writes entries, lists them withkeys(), purges a pattern, and checks what's left, both with and without redis sets.main, both no-sets checks fail (keys()returns[], and the pattern matched nothing).keys()found all 20, and the purge left 0.Locally:
test:lintgreen,test:smoke3/3,test:cluster5/5,test:depsok.test:e2e:redis135/135.Checklist
pnpm run test:lintpasses.test:e2e:redis,test:smoke,test:clusterrun.main.Notes for the reviewer
RedisCacheProvider.tsalongside fix(provider-redis): cache anything at all on a Redis Cluster #244, in different hunks. Whichever merges second needs a trivial rebase.test:cluster:liveshould gain the no-sets case. I'll do that in whichever PR lands last.