Files
tech-log-backend/docs/redis/support-matrix.md
T

10 KiB
Raw Blame History

Redis SDK support matrix

This file is a gate, not a summary. RedisSupportMatrixTest parses the tables below and fails when the SDK grows a package or a capability that is not listed, so a module cannot ship without someone stating its minimum version, its topology support, and what it does not do.

Design: docs/superpowers/specs/2026-08-07-redis-wrapper-typed-api-design.md. Delivery status and the decisions behind each module: docs/superpowers/plans/2026-08-07-redis-wrapper-typed-api-status.md.

Modules

Module Minimum Redis Topology Risk exposure Sync Reactive Known limitations
api 7.2 all none n/a n/a contract only; no driver types
api/key 7.2 all none n/a n/a slot tags must be low-cardinality
api/codec 7.2 all none n/a n/a no Java native serialization
api/command 7.2 all none n/a n/a permits never widen the ACL account
api/error 7.2 all none n/a n/a failure metadata carries no key or value
api/operations 7.2 all none n/a n/a contract only
api/reactive 7.2 all none n/a n/a Reactor confined to this package
lettuce 7.2 all R1R2 yes yes pinned to Lettuce 6.8.2
lettuce/codec 7.2 all none yes yes UTF-8 and byte array codecs only
lettuce/command 7.2 all R1R2 yes yes policy catalog is the only command authority
lettuce/connection 7.2 all none yes yes five lanes; blocking work never shares the regular lane
lettuce/observability 7.2 all none yes yes command family only, never a key
lettuce/operations 7.2 all R1R2 yes yes hash field TTL needs 7.4; sharded pub/sub needs 7.0; stream deletion needs 8.2
config 7.2 all none n/a n/a permit provenance is HMAC-signed per process
cluster 7.2 cluster none n/a n/a slot arithmetic only; no redirect following
programmability 7.2 all R2 yes no transactions never roll back; scripts return one bulk reply; FUNCTION LOAD is admin-plane
raw 7.2 all R2 yes no RAW_ONLY commands only; movable key specs unapprovable
admin 7.2 all R3 read-only yes no replies are projected; no destructive command exists
extensions 8.0 all none yes no shared command runner; every extension declares its key
extensions/json 8.0 all R1R2 yes no narrow JSONPath grammar; documents exchanged as text
extensions/search 8.0 all R2 yes no index names namespaced by the SDK; no drop index
extensions/timeseries 8.0 all R1R2 yes no retention mandatory at creation
extensions/probabilistic 8.0 all R1R2 yes no every answer is approximate by construction

Capabilities

Capability Minimum Redis Gate Bean when absent
SHARDED_PUBSUB 7.0 probe and catalog minimum none
FUNCTIONS 7.0 probe and catalog minimum none
HASH_FIELD_EXPIRATION 7.4 probe and catalog minimum none
HASH_FIELD_EXPIRATION_COMBINED 8.0 probe and catalog minimum none
STREAM_ACKNOWLEDGE_DELETE 8.2 probe and catalog minimum none
STREAM_NEGATIVE_ACKNOWLEDGE 8.8 probe and catalog minimum none, and no bean exists yet
JSON 8.0 probe is authoritative none
SEARCH 8.0 probe is authoritative none
TIME_SERIES 8.0 probe is authoritative none
PROBABILISTIC 8.0 probe is authoritative none

Certified versions

A version is certified by its lane producing evidence, not by the version number being newer. An evidence claim here must name the test class that produced it; RedisSupportMatrixTest fails the build on a row that claims anything else, so "verified" cannot be written into this table without a test behind it.

All three lanes have now run on 7.4. The other declared versions are declared, not certified: nothing in this repository has executed against 7.2 or 8.2.

Topology Versions declared Evidence status
Standalone 7.2, 7.4, 8.2 RedisTopologyContractTest, LiveRedisGuardrailTest on 7.4
Sentinel 7.4, 8.2 RedisTopologyContractTest, LiveRedisSentinelPromotionTest on 7.4
Cluster 7.4, 8.2 RedisTopologyContractTest, LiveRedisClusterTest on 7.4

What the standalone ACL run established

RedisTopologyContractTest runs the four accounts in infra/redis-sdk/acl against a live server and asserts that each CommandAccess level grants exactly what the command policy catalog says it may issue. Writing it found five defects that no amount of reading the files would have surfaced:

  1. A Redis ACL file accepts neither comments nor line continuations — the original files did not load at all, and the server refused to start.

  2. The advanced account granted SMEMBERS and SORT, both RAW_ONLY and therefore the raw gateway account's alone.

  3. The ordinary account granted SORT_RO for the same reason.

  4. The ordinary account could not run PUBLISH, SUBSCRIBE, or PING, all classified TYPED.

  5. The ordinary account could not run MULTI, EXEC, UNWATCH, or DISCARD, also TYPED.

  6. The admin account was missing twelve read-only diagnostics the catalog exposes — the OBJECT, PUBSUB, XINFO, FUNCTION LIST/STATS, and CLUSTER KEYSLOT subcommands.

  7. The cursor-scan reply budget was sized to the requested COUNT, which Redis treats as a hint — a real HSCAN COUNT 500 came back with 501 entries and the SDK refused a correct reply.

Points 2 and 3 are the ones that matter: the account is the last enforcement boundary, so an account wider than the catalog silently removes the second control the design relies on.

What the standalone guardrail run established

LiveRedisGuardrailTest wires the real guard, catalog, and typed operations to a live server — the first time LettuceRedisCommandGateway, the one class that encodes commands, runs under the SDK's own contracts rather than against the in-memory stand-in. It carries the plan's datasets: a value at the 1 MiB ceiling, a hundred-thousand-field hash, hundred-thousand-member set and sorted set, a twenty-thousand-element list, a stream trimmed to 1,000 while twenty thousand entries are appended, and a five-hundred-command batch.

The assertions are about limits holding, not throughput. A guardrail test that measured absolute speed would fail on a loaded laptop and teach nobody anything.

What the Sentinel promotion run established

LiveRedisSentinelPromotionTest forces one real promotion and asserts several independent claims about it. Every write carries a token unique to the run, so the list on the promoted primary is a verbatim record of what happened and each per-call verdict can be checked against it.

It found the most serious defect in this delivery, and it is not in the SDK's code:

A superseded primary keeps acknowledging writes. Sentinel promoted the replica at 05:56:12.503 and did not demote the old primary until 05:56:23.529 — eleven seconds in which the client, still connected, wrote and was told +OK 2,086 times. Every one of those writes was discarded when the old primary resynced from the new one. Exactly one command failed. No client-side signal exists for this: the server answered, so the driver, the SDK, and the caller all correctly recorded a success.

SentinelFailoverObserver counts ambiguous writes, and its documentation used to call those "the ones an operator has to reconcile". That was wrong by three orders of magnitude, and the class now says so.

What closes the window is on the server, not the client. Re-running the identical promotion with min-replicas-to-write 1 and min-replicas-max-lag 1 configured cut acknowledged-and-discarded writes from 2,086 to 1: the orphaned primary refused 2,020 writes with NOREPLICAS, which the SDK translates to a definite, non-ambiguous failure the caller can act on. Both settings are now in the lane, and acknowledgedWriteLossIsBounded ties the tolerated loss to the configured lag window, so removing them makes the count jump by an order of magnitude and fails the test.

That assertion then caught a second version of the same mistake within a day of being written. The first guarded run passed; the second failed with 2,099 lost writes, because the setting had been written into the lane's primary service only. The two data nodes swap roles on every failover, so a guardrail applied to whichever one happens to start as primary stops applying the moment the lane does the thing it exists to do. Both nodes now take their whole configuration from one definition. Three consecutive promotions in both directions since: 0, 0, and 1 acknowledged write lost.

The run also found a translator defect. A promotion closed the channel under an in-flight RPUSH and the driver raised a bare RedisException, which matched no branch and fell through to a generic failure reported as definitely did not run. Nothing about an unrecognised failure supports that claim, and a caller who believes it retries a non-idempotent write. The fallback now treats an unclassified write failure as ambiguous.

What the Cluster run established

LiveRedisClusterTest checks the part of sdk.cluster that is pure client-side arithmetic against the server that has the last word. The calculator agreed with CLUSTER KEYSLOT on every entry of a corpus built from the brace rules a hand-written implementation gets wrong — an empty tag {}, foo{}{bar}, foo{{bar}}zap, an unclosed brace, }{, the empty key, and non-ASCII keys — and the rendered-key invariant holds: the slot the SDK computes from a tag alone equals the slot the server computes from the whole rendered key.

Cross-slot refusal was checked in both directions, because a guard stricter than the cluster costs availability for no reason and a looser one sends requests that cannot succeed. The same key pair the guard refuses is the pair the server answers CROSSSLOT for.

Redirects were observed rather than assumed: a MOVED names the slot the client computed, and a slot put into a real MIGRATING/IMPORTING state answers ASK for an absent key and TRYAGAIN for a multi-key request that straddles the migration. The lane restores the slot to STABLE afterwards, so a run leaves the cluster as it found it.