This is a beta release adding support for Redis 8.10, new commands, and a large batch of stability and parser-robustness fixes. The 9.22.0 GA release will follow once client-side caching and auto-pipelining are merged.
⚠️ Two changes to be aware of when upgrading from 9.21.0:
- Default configuration values changed (#3918): read/write timeouts, retry backoff, cluster state reload interval, and TCP keep-alive defaults are now aligned with the cross-SDK configuration proposal (see the highlight below). Explicitly configured values are unaffected.
WaitAOFreturn type corrected (#3888):WaitAOFnow returns*IntSliceCmd, matching the two-integer reply ofWAITAOF(previously*IntCmd, which failed to parse the reply at runtime). Code referencing the old return type needs a one-line update.
🚀 Highlights
Redis 8.10 Support
This release adds support for Redis 8.10. The README's supported-versions list now includes Redis 8.10, and CI runs the full suite against the redislabs/client-libs-test:8.10.0 image by default (#3920, #3940).
Coverage for the new commands and options that ship with Redis 8.10:
HIMPORT(#3919) — bulk hash import via server-side fieldsets, exposed asHImportPrepare,HImportSet,HImportDiscard, andHImportDiscardAll. Fieldsets are session state scoped to a single physical connection, which does not mix well with connection pooling — so the client keeps a versioned fieldset registry and lazily replays thePREPAREon whichever pooled connection executes aSETthat needs it, at most once per connection, with no extra round trip (thePREPAREis injected into the same write as theSET).LMOVEM/BLMOVEM(#3913) — move multiple elements between lists in one call.SUNIONCARD/SDIFFCARD(#3897) — cardinality of set union/difference without materializing the result.XREAD/XREADGROUPMAXCOUNTandMAXSIZE(#3898) — bound how much data a stream read returns.TS.READ(#3896),TS.QUERYLABELS(#3926),TS.NRANGE/TS.NREVRANGE(#3870) with multiple aggregators per key (#3937), andEXCLUDEEMPTYonTS.MRANGE/TS.MREVRANGE(#3912) — new time-series query surface.FT.ALIASLIST(#3925),COLLECTreducer forFT.AGGREGATE(#3886),RERANKon HNSW vector fields inFT.CREATE(#3927), andFT.HYBRIDtimeout warnings (#3911) — search coverage.
Cross-SDK Aligned Defaults
Default configuration values now follow the cross-SDK configuration proposal shared by all Redis client libraries (#3918):
| Setting | Old default | New default |
|---|---|---|
ReadTimeout / WriteTimeout
| 3s | 5s |
| Retry backoff (min/max) | 8ms / 512ms | 10ms / 1s |
| Cluster state reload interval | 10s | 60s |
| TCP keep-alive | 5min period | 30s idle / 5s interval / 3 probes (net.KeepAliveConfig)
|
Applications that set these values explicitly are unaffected; applications relying on the old defaults inherit the new ones.
Data-Race and Parser Hardening Sweep
A systematic audit fixed data races across the client — hooks (AddHook, #3868), Ring.SetAddrs (#3862), cluster node slices (#3861), pub/sub reconnect (#3906), maintenance notifications (#3894, #3872), pool handoff (#3876), and redisotel (#3881) — and hardened the RESP parsers against malformed or unexpected replies: over-reads on nil replies (#3874), integer overflow when skipping map/attribute bodies (#3877), unhashable RESP3 map keys (#3873), odd-length flat replies (#3900), mismatched declared array lengths (#3907), unexpected extra reply frames (#3884), and nil elements in numeric/bool slice replies (#3922).
PubSub Receive Hang Fix
PeekPushNotificationName blocked until 36 bytes were buffered, so a short subscribe confirmation (channel name of six or fewer characters) on an otherwise idle connection hung PubSub.Receive forever — a regression introduced in 9.20.1 by #3842. The peek now parses whatever is already buffered and only waits for one more byte when the frame prefix is valid but incomplete. Fixes #3935.
Correct Cluster Transaction Retries
The cluster transaction pipeline treated a MULTI...EXEC block as independently retryable commands, which could scatter a transaction across nodes or send malformed transactions on retry. Redirects (MOVED/ASK/TRYAGAIN) and aborts are now handled at the whole-transaction level, matching Redis transaction semantics: the transaction is re-routed and retried as a unit, never partially (#3909) by @cxljs.
Credential Redaction in Command Tracing
rediscmd.AppendCmd — used by redisotel and rediscensus to render commands into span attributes — now redacts credential arguments as <redacted>: AUTH, HELLO ... AUTH, CONFIG SET of requirepass / masterauth / TLS key passphrases, ACL SETUSER password rules, and MIGRATE ... AUTH/AUTH2. The client sends HELLO ... AUTH on every handshake and AUTH on every streaming-credentials rotation through the regular hook chain, so tracing hooks previously captured credentials even when the application never issued an auth command itself (#3939) by @saddamr3e.
✨ New Features
HIMPORTcommand family:HImportPrepare/HImportSet/HImportDiscard/HImportDiscardAllwith lazy per-connection fieldset prepare replay (#3919) by @ndyakovLMOVEM/BLMOVEM: move multiple list elements in one call, withCOUNT(up to N) orEXACTLY(all-or-nothing) semantics viaLMoveMArgs(#3913) by @ofekshenawaSUnionCard/SDiffCard: cardinality of set union/difference (#3897) by @ofekshenawaXRead/XReadGroupMAXCOUNT/MAXSIZE: bound stream read responses by entry count or payload size (#3898) by @ofekshenawaTS.READ: read samples from a series starting at a given timestamp, withTSReadEarliest(-),TSReadLatest(+), andTSReadNew($) sentinels (#3896) by @ofekshenawaTS.QUERYLABELS: query label names/values across time series (#3926) by @ndyakovTS.NRANGE/TS.NREVRANGE: range queries across multiple series (#3870) by @ofekshenawa, with multiple aggregators per key (#3937) by @ndyakovTS.MRANGE/TS.MREVRANGEEXCLUDEEMPTY: skip series with no samples in the result (#3912) by @ofekshenawaFT.ALIASLIST: list all index aliases (#3925) by @ndyakovFT.AGGREGATECOLLECTreducer: collect grouped values into an array (#3886) by @ndyakovFT.CREATERERANK:RERANKparameter on HNSW vector field definitions (#3927) by @ofekshenawaFT.HYBRIDtimeout warnings: timeout warnings are now populated in hybrid search results (#3911) by @ofekshenawaFT.HYBRIDKNNSHARD_K_RATIO(Redis 8.8+): per-shard K ratio for KNN clauses (#3841) by @ndyakov
🐛 Bug Fixes
- PubSub
Receivehang: peek push-notification names without demanding 36 buffered bytes, fixing a hang on short subscribe confirmations (fixes #3935, regression from 9.20.1) (#3936) by @ndyakov - Cluster transactions: re-route the whole tx pipeline on redirect/abort instead of per-command (#3909) by @cxljs
- Credential leak in traces:
rediscmd.AppendCmdredacts credential arguments (AUTH,HELLO ... AUTH,CONFIG SETsecret params,ACL SETUSERpassword rules,MIGRATE AUTH/AUTH2), soredisotel/rediscensusspan attributes no longer contain passwords (#3939) by @saddamr3e WaitAOFreturn type: returns*IntSliceCmdmatching the two-integerWAITAOFreply (#3888) by @CipherN9Ring.Publishrouting: publish to the shard that owns the topic instead of a round-robined one (#3893) by @dkindel- Pool
OnRemovehooks: fireOnRemoveonputConneviction paths so removal hooks see every evicted connection (#3932) by @cxljs UniversalClientInfoMap: addedInfoMapto theCmdableinterface (#3904) by @nazarli-shabnamSlowLogGetcontext: pass the caller's context instead of a background one (#3915) by @sonnemuskModuleLoadexnil config: return an error instead of panicking on nil config (#3916) by @sonnemuskParseURLIPv6 hosts: keep single brackets for IPv6 hosts without a port (#3882) by @sueun-devParseURLdurations: treat unit durations<= 0as disabled (#3866) by @sueun-dev- Nil
*uint8encoding: encode nil*uint8as"0"like other numeric pointers (#3869) by @sueun-dev JSONSliceCmdread errors: return the read error fromreadReplyinstead of swallowing it (#3903) by @saddamr3e- RESP parser hardening: reconcile declared entry-array lengths (#3907), handle nil elements in int/uint/bool slice parsers (#3922), drain unexpected reply frames (#3884), reject odd-length flat replies in Z/KeyValue parsers (#3900), avoid int overflow when skipping map/attr bodies (#3877), don't over-read nil replies in
Reader.Discard(#3874) by @saddamr3e; reject unhashable keys in RESP3 map parsing (#3873) by @iabdullah215 - Data races: hook state during
AddHook(#3868),onNewNodeduringRing.SetAddrs(#3862), shared masters/slaves slices in cluster (#3861), sharedopt.Addrduring pub/sub reconnect (#3906),clusterStateReloadCallbackin maintnotifications (#3894), conn reader inisHealthyConnduring handoff (#3876) by @saddamr3e; handoff race window in maintnotifications (#3872) by @ndyakov redisotel: useObservableCounterfor cumulative pool stats (#3914) by @Solaris-star; avoid a data race on shared attributes duringMinIdleConnswarmup (#3881) by @ndyakov
🧰 Maintenance
- Cross-SDK default alignment: new defaults for timeouts, retry backoff, cluster state reload, and TCP keep-alive (#3918) by @ndyakov
- CI on Redis 8.10: 8.10 made the default test version (#3920) with version gating by major.minor (#3908) by @ofekshenawa; the test stack now runs the GA
redislabs/client-libs-test:8.10.0image and 8.8 was dropped from the CI matrix (#3940) - Type-safe atomics: use typed
sync/atomicvalue types (#3860) and remove the deadassertUnstableCommandRESP3 path (#3928) by @cxljs - Docs: clarify that
ExpireTime/PExpireTimereturn Unix timestamps (#3917) by @sonnemusk; remove a duplicate example step (#3875) by @andy-stark-redis
👥 Contributors
We'd like to thank all the contributors who worked on this release!
@andy-stark-redis, @CipherN9, @cxljs, @dkindel, @iabdullah215, @nazarli-shabnam, @ndyakov, @ofekshenawa, @saddamr3e, @Solaris-star, @sonnemusk, @sueun-dev