Skip to content

feat(client): add OPTIN/OPTOUT tracking modes to client-side caching - #3473

Merged
nkaradzhov merged 6 commits into
redis:masterfrom
PavelPashov:feat/csc-optin-optout
Oct 1, 2026
Merged

nkaradzhov merged 6 commits into
redis:masterfrom
PavelPashov:feat/csc-optin-optout

Conversation

@PavelPashov

@PavelPashov PavelPashov commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds opt-in and opt-out tracking modes to client-side caching. Nothing changes with the defaults.

  • trackingMode: "plain" (default), "optin" or "optout". Also exported as CLIENT_SIDE_CACHE_TRACKING_MODES.
  • cacheable(command, keys): decides caching for reads without a per-call cache option.
  • cache: true | false command option, which overrides cacheable, which overrides the mode default. Ineligible commands are never cached.
  • strict (experimental): cache: true on an ineligible command rejects with ClientSideCacheMarkError before it's sent.
  • Queue prelude (internal): a command can carry a prelude, a command written right in front of it. The two are admitted, cancelled, encoded and written as a single unit, so nothing can land between them, and both fail if the connection drops. CLIENT CACHING YES|NO travels as the read's prelude. It's sent again after MOVED, and skipped on ASK.
  • With client-side caching on, the application's own CLIENT CACHING / CLIENT TRACKING calls reject with ClientSideCacheCommandError.

Discussion

  1. Queue limit. A pair waiting to be written counts as one command against commandsQueueMaxLength, but two once written. Up to 2× the limit can reach the wire. Should we count them properly, or document it?
  2. Non-default type mappings skip the cache without any signal. This happens on master too, and isn't documented. cache: true is ignored in this case with no warning or strict error. Should client-side caching support custom type mappings, or should we document the limit and warn?

Checklist

  • Does npm test pass with this change (including linting)?
  • Is the new or changed code fully tested?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?

Note

Medium Risk
Changes command execution, queue admission, and cache storage semantics for RESP3 client-side caching; defaults are unchanged but OPTIN/OPTOUT and prelude pairing bugs could cause stale or untracked entries.

Overview
Adds OPTIN/OPTOUT client-side caching so apps can control what Redis tracks and what gets stored locally, without changing default "plain" behavior.

New clientSideCache options include trackingMode (plain / optin / optout, exported as CLIENT_SIDE_CACHE_TRACKING_MODES), optional cacheable(command, keys), experimental strict, and per-call cache: true | false via withCommandOptions. Intent resolves as: command option → cacheable → mode default. Ineligible commands never cache; cache: true on them warns or rejects with ClientSideCacheMarkError when strict is on.

The client issues CLIENT CACHING YES/NO through a new command queue prelude so the flag and read are admitted, encoded, and cancelled as one unit (avoiding stray flags when the queue is full). CLIENT TRACKING / CLIENT CACHING from the app are blocked with ClientSideCacheCommandError (including multi, pipelines, and raw sendCommand). OPTIN paths skip storing replies when the caching flag fails (e.g. ACL).

Documentation moves to docs/client-side-caching.md with README and configuration updates; broad integration tests cover standalone, cluster, and queue edge cases.

Reviewed by Cursor Bugbot for commit 066479e. Bugbot is set up for automated code reviews on this repo. Configure here.

Add a `trackingMode` option ("plain" | "optin" | "optout") and a
`cacheable(command, keys)` predicate to the client-side cache config,
and a per-call `cache` command option. Intent resolves as: the per-call
mark, then the predicate, then the mode default. Eligibility still wins.

`CLIENT CACHING YES|NO` is sent as a queue prelude of the read, so the
pair is admitted, cancelled, encoded, written, and failed as one unit.
When the flag fails, the reply is returned but not cached.

`handleCache` keeps its callback contract; tracking confirmation is an
optional trailing `isStorable` parameter, so existing custom providers
are unaffected.
@PavelPashov
PavelPashov marked this pull request as ready for review September 26, 2026 11:52

@nkaradzhov nkaradzhov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 066479e. Configure here.

Comment thread packages/client/lib/client/index.ts
@nkaradzhov
nkaradzhov merged commit 365bbf6 into redis:master Oct 1, 2026
15 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.

2 participants