Skip to content

client cache: group-level configuration (MultiGroupOptions.ClientCache) and Prefixes as RedisKey - #3304

Merged
mgravell merged 2 commits into
v4from
v4-group-cache
Oct 27, 2026
Merged

mgravell merged 2 commits into
v4from
v4-group-cache

Conversation

@mgravell

Copy link
Copy Markdown
Collaborator

Two related changes to how a client-side cache is configured.

Group-level ClientCache

On a connection group the cache is now set once, on MultiGroupOptions.ClientCache (and Builder.ClientCache), and every member inherits it. A member whose own ConfigurationOptions.ClientCache is set is refused with an ArgumentException naming the member and pointing at the group option, both at ConnectGroupAsync and at AddAsync.

Why group-level only: each member keeps a cache of its own (the invalidations that keep it honest arrive on that member's connections), and the group serves reads from whichever member is active. Members that cached differently, or one that did not cache at all, would change what callers get on every failover. One definition inherited by all is how they agree; a per-member override would be a way to disagree.

Plumbing mirrors the group circuit breaker: the options travel alongside the configuration into the multiplexer constructor and are never written into the caller's ConfigurationOptions. Two decisions taken on the way:

  • a sentinel member gets a clone of its configuration carrying the cache, because the cache is built with the connection and cannot be applied afterwards as the breaker is;
  • a sentinel connection itself never builds a cache (it holds no data, and CLIENT TRACKING is not something to negotiate with a sentinel), which closes a latent hazard for anyone who set ClientCache on sentinel options.

The GetClientCache() remark now says what a held view means on a group: the active member's cache at the time of the call.

CacheOptions.Prefixes as IReadOnlyList<RedisKey>

Keys are bytes; a prefix that is not valid UTF-8 had no string spelling, and a key made with WithKeyPrefix can now be given as it is (taken whole, context prefix plus value, which is what the wire sees and what is matched). Existing Prefixes = ["app:"] call sites compile unchanged via the implicit conversion.

The list is rendered once into a private byte[][] snapshot that both the matcher and the handshake's PREFIX arguments read, so the local and server-side filters cannot disagree and nothing is re-encoded on the way out. Nothing of the caller's survives: not the list, not the array behind a byte key, and the getter hands out fresh keys over fresh copies, since a RedisKey over a byte[] returns that array through its explicit conversion. Validation is unchanged: empty and overlapping prefixes are rejected at init.

Tests and docs

  • GroupCacheTests: configuration moved to group level; new tests that every member including a late joiner runs the group's cache and becomes a peer while the callers' options stay untouched, and that a member bringing its own cache is refused at connect and at AddAsync.
  • RespClientCacheTests: prefix snapshot (list, byte array, and getter result all mutated after the fact), arbitrary-byte prefix, prefixed-key rendering.
  • docs/Failover.md (table row and section), docs/ClientSideCaching.md ("Connection groups"), design ledger entry.

Build green with CI settings. Main suite net10.0: 7898 passed, 0 failed, 167 skipped.

….ClientCache, inherited; a member's own is refused), and Prefixes as RedisKey over a private byte snapshot

- MultiGroupOptions.ClientCache / Builder.ClientCache: the one place a group's cache is set. Each member keeps a cache of its own and the group answers from the active one, so members must cache alike or a failover would change what callers get; a member whose ConfigurationOptions.ClientCache is set is refused (at connect and at AddAsync). Plumbed like the group circuit breaker: alongside the configuration, never written into it; a sentinel member gets a clone, since its cache is built with the connection, and a sentinel connection itself never builds a cache.
- CacheOptions.Prefixes is IReadOnlyList<RedisKey>: keys are bytes, and a prefix that is not valid UTF-8 had no spelling; a key made with WithKeyPrefix is taken whole. Rendered once to a private byte[][] that both the matcher and the handshake's PREFIX arguments read (no re-encoding on the way out); nothing of the caller's list or arrays is kept, and the getter hands out copies. Existing string call sites compile unchanged.
- GetClientCache remark: on a group it is the active member's cache at the time of the call.
- Tests: GroupCacheTests (group-level configuration, late joiner, member cache refused), prefix snapshot/bytes/prefixed-key tests. Docs: Failover.md row and section, ClientSideCaching.md "Connection groups", design ledger.

Main suite net10.0: 7898 passed, 0 failed, 167 skipped.
…ation.md, and describe Prefixes as RedisKey values copied when set
@mgravell
mgravell merged commit 23a8334 into v4 Oct 27, 2026
4 checks passed
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