RELEASE-NOTES.md
This is a minor release introducing two flagship (experimental) features — client-side caching and automatic pipelining — alongside support for Redis 8.10, new commands, and a large batch of stability and parser-robustness fixes. It consolidates everything shipped in 9.22.0-beta.1, so the notes below cover the full 9.21.0 → 9.22.0 upgrade.
⚠️ Two changes to be aware of when upgrading from 9.21.0:
WaitAOF return type corrected (#3888): WaitAOF now returns *IntSliceCmd, matching the two-integer reply of WAITAOF (previously *IntCmd, which failed to parse the reply at runtime). Code referencing the old return type needs a one-line update.The standalone Client gains server-assisted client-side caching built on RESP3 CLIENT TRACKING. Enable it by setting ClientSideCacheConfig in Options (or supply your own cache via ClientSideCache — e.g. to share one cache across clients). Cacheable read results are served from a local in-process cache and invalidated automatically when the server reports a change, cutting round trips for read-heavy workloads.
The invalidation architecture is selected by ClientSideCacheStrategy; the default (and currently only) strategy is CSCStrategySharedTracking: one shared cache, every pool connection runs plain CLIENT TRACKING ON, and a background drainer applies buffered invalidations — portable (no BCAST) and consistent with the other Redis client libraries. Requirements and guardrails: RESP3 (Protocol: 3), standalone client, DB 0 only; commands that would change the connection identity (SELECT, AUTH, ...) are rejected while caching is enabled, and CSC is disabled when a credentials provider is set (fixed Username/Password work and are namespaced). See the README's client-side caching section and the runnable example.
Experimental: the API may change in a minor release.
(#3941) by @ofekshenawa
AutoPipeliner is a background batcher that coalesces commands from many concurrent goroutines into Redis pipelines, multiplying throughput without any manual pipeline management. It comes in two faces, available on Client and ClusterClient (and configurable via Options.AutoPipelineOptions / UniversalOptions.AutoPipelineOptions):
AutoPipeline() — the blocking face: a drop-in Cmdable where each call blocks until executed, exactly like a plain client, while concurrent callers' commands batch together under the hood (measured locally over loopback: ~1M+ SET/sec vs ~100k unpipelined; indicative, not a guarantee). Per-goroutine command order is preserved.AsyncAutoPipeline() — the deferred face: command calls return immediately and every typed result accessor (Val/Result/Err/...) blocks until the command has executed. Submit a window of commands, then read the results, to keep pipelines deep (~2–3M SET/sec locally; indicative).AutoPipelineOptions controls batching: MaxBatchSize (soft target, default 200; the blocking face's preset uses 300), MaxBatchBytes (approximate payload cap so huge values flush as several bounded writes), MaxFlushDelay with optional AdaptiveDelay (delay scales down as the queue fills), and MaxConcurrentBatches (default 1 = a single ordered batch stream; raising it requires Unordered: true, so ordering is never lost by accident — Validate() rejects the combination otherwise). A usage tour and throughput comparison live in example/autopipeline.
Experimental: the API may change in a future release — pin your go-redis version if you adopt it.
(#3942) by @ndyakov, with help from @cxljs
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 as HImportPrepare, HImportSet, HImportDiscard, and HImportDiscardAll. 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 the PREPARE on whichever pooled connection executes a SET that needs it, at most once per connection, with no extra round trip (the PREPARE is injected into the same write as the SET).LMOVEM / BLMOVEM (#3913) — move multiple elements between lists in one call.SUNIONCARD / SDIFFCARD (#3897) — cardinality of set union/difference without materializing the result.XREAD / XREADGROUP MAXCOUNT and MAXSIZE (#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), and EXCLUDEEMPTY on TS.MRANGE / TS.MREVRANGE (#3912) — new time-series query surface.FT.ALIASLIST (#3925), COLLECT reducer for FT.AGGREGATE (#3886), RERANK on HNSW vector fields in FT.CREATE (#3927), and FT.HYBRID timeout warnings (#3911) — search coverage.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.
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).
Receive Hang FixPeekPushNotificationName 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.
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.
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.
ClientSideCacheConfig / ClientSideCache, with the CSCStrategySharedTracking invalidation strategy (#3941) by @ofekshenawaAutoPipeline() (blocking) and AsyncAutoPipeline() (deferred results) on Client and ClusterClient, configured via AutoPipelineOptions (#3942) by @ndyakov, with help from @cxljsHIMPORT command family: HImportPrepare / HImportSet / HImportDiscard / HImportDiscardAll with lazy per-connection fieldset prepare replay (#3919) by @ndyakovLMOVEM / BLMOVEM: move multiple list elements in one call, with COUNT (up to N) or EXACTLY (all-or-nothing) semantics via LMoveMArgs (#3913) by @ofekshenawaSUnionCard / SDiffCard: cardinality of set union/difference (#3897) by @ofekshenawaXRead / XReadGroup MAXCOUNT / 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, with TSReadEarliest (-), TSReadLatest (+), and TSReadNew ($) 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.MREVRANGE EXCLUDEEMPTY: skip series with no samples in the result (#3912) by @ofekshenawaFT.ALIASLIST: list all index aliases (#3925) by @ndyakovFT.AGGREGATE COLLECT reducer: collect grouped values into an array (#3886) by @ndyakovFT.CREATE RERANK: RERANK parameter on HNSW vector field definitions (#3927) by @ofekshenawaFT.HYBRID timeout warnings: timeout warnings are now populated in hybrid search results (#3911) by @ofekshenawaFT.HYBRID KNN SHARD_K_RATIO (Redis 8.8+): per-shard K ratio for KNN clauses (#3841) by @ndyakovReceive hang: 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 @ndyakovrediscmd.AppendCmd redacts credential arguments (AUTH, HELLO ... AUTH, CONFIG SET secret params, ACL SETUSER password rules, MIGRATE AUTH/AUTH2), so redisotel / rediscensus span attributes no longer contain passwords (#3939) by @saddamr3eWaitAOF return type: returns *IntSliceCmd matching the two-integer WAITAOF reply (#3888) by @CipherN9Ring.Publish routing: publish to the shard that owns the topic instead of a round-robined one (#3893) by @dkindelOnRemove hooks: fire OnRemove on putConn eviction paths so removal hooks see every evicted connection (#3932) by @cxljsUniversalClient InfoMap: added InfoMap to the Cmdable interface (#3904) by @nazarli-shabnamSlowLogGet context: pass the caller's context instead of a background one (#3915) by @sonnemuskModuleLoadex nil config: return an error instead of panicking on nil config (#3916) by @sonnemuskParseURL IPv6 hosts: keep single brackets for IPv6 hosts without a port (#3882) by @sueun-devParseURL durations: treat unit durations <= 0 as disabled (#3866) by @sueun-dev*uint8 encoding: encode nil *uint8 as "0" like other numeric pointers (#3869) by @sueun-devJSONSliceCmd read errors: return the read error from readReply instead of swallowing it (#3903) by @saddamr3eReader.Discard (#3874) by @saddamr3e; reject unhashable keys in RESP3 map parsing (#3873) by @iabdullah215AddHook (#3868), onNewNode during Ring.SetAddrs (#3862), shared masters/slaves slices in cluster (#3861), shared opt.Addr during pub/sub reconnect (#3906), clusterStateReloadCallback in maintnotifications (#3894), conn reader in isHealthyConn during handoff (#3876) by @saddamr3e; handoff race window in maintnotifications (#3872) by @ndyakovredisotel: use ObservableCounter for cumulative pool stats (#3914) by @Solaris-star; avoid a data race on shared attributes during MinIdleConns warmup (#3881) by @ndyakovredislabs/client-libs-test:8.10.0 image and 8.8 was dropped from the CI matrix (#3940)sync/atomic value types (#3860) and remove the dead assertUnstableCommand RESP3 path (#3928) by @cxljsExpireTime / PExpireTime return Unix timestamps (#3917) by @sonnemusk; remove a duplicate example step (#3875) by @andy-stark-redisWe'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
Full Changelog: https://github.com/redis/go-redis/compare/v9.21.0...v9.22.0
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:
WaitAOF return type corrected (#3888): WaitAOF now returns *IntSliceCmd, matching the two-integer reply of WAITAOF (previously *IntCmd, which failed to parse the reply at runtime). Code referencing the old return type needs a one-line update.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 as HImportPrepare, HImportSet, HImportDiscard, and HImportDiscardAll. 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 the PREPARE on whichever pooled connection executes a SET that needs it, at most once per connection, with no extra round trip (the PREPARE is injected into the same write as the SET).LMOVEM / BLMOVEM (#3913) — move multiple elements between lists in one call.SUNIONCARD / SDIFFCARD (#3897) — cardinality of set union/difference without materializing the result.XREAD / XREADGROUP MAXCOUNT and MAXSIZE (#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), and EXCLUDEEMPTY on TS.MRANGE / TS.MREVRANGE (#3912) — new time-series query surface.FT.ALIASLIST (#3925), COLLECT reducer for FT.AGGREGATE (#3886), RERANK on HNSW vector fields in FT.CREATE (#3927), and FT.HYBRID timeout warnings (#3911) — search coverage.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.
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).
Receive Hang FixPeekPushNotificationName 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.
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.
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.
HIMPORT command family: HImportPrepare / HImportSet / HImportDiscard / HImportDiscardAll with lazy per-connection fieldset prepare replay (#3919) by @ndyakovLMOVEM / BLMOVEM: move multiple list elements in one call, with COUNT (up to N) or EXACTLY (all-or-nothing) semantics via LMoveMArgs (#3913) by @ofekshenawaSUnionCard / SDiffCard: cardinality of set union/difference (#3897) by @ofekshenawaXRead / XReadGroup MAXCOUNT / 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, with TSReadEarliest (-), TSReadLatest (+), and TSReadNew ($) 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.MREVRANGE EXCLUDEEMPTY: skip series with no samples in the result (#3912) by @ofekshenawaFT.ALIASLIST: list all index aliases (#3925) by @ndyakovFT.AGGREGATE COLLECT reducer: collect grouped values into an array (#3886) by @ndyakovFT.CREATE RERANK: RERANK parameter on HNSW vector field definitions (#3927) by @ofekshenawaFT.HYBRID timeout warnings: timeout warnings are now populated in hybrid search results (#3911) by @ofekshenawaFT.HYBRID KNN SHARD_K_RATIO (Redis 8.8+): per-shard K ratio for KNN clauses (#3841) by @ndyakovReceive hang: 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 @ndyakovrediscmd.AppendCmd redacts credential arguments (AUTH, HELLO ... AUTH, CONFIG SET secret params, ACL SETUSER password rules, MIGRATE AUTH/AUTH2), so redisotel / rediscensus span attributes no longer contain passwords (#3939) by @saddamr3eWaitAOF return type: returns *IntSliceCmd matching the two-integer WAITAOF reply (#3888) by @CipherN9Ring.Publish routing: publish to the shard that owns the topic instead of a round-robined one (#3893) by @dkindelOnRemove hooks: fire OnRemove on putConn eviction paths so removal hooks see every evicted connection (#3932) by @cxljsUniversalClient InfoMap: added InfoMap to the Cmdable interface (#3904) by @nazarli-shabnamSlowLogGet context: pass the caller's context instead of a background one (#3915) by @sonnemuskModuleLoadex nil config: return an error instead of panicking on nil config (#3916) by @sonnemuskParseURL IPv6 hosts: keep single brackets for IPv6 hosts without a port (#3882) by @sueun-devParseURL durations: treat unit durations <= 0 as disabled (#3866) by @sueun-dev*uint8 encoding: encode nil *uint8 as "0" like other numeric pointers (#3869) by @sueun-devJSONSliceCmd read errors: return the read error from readReply instead of swallowing it (#3903) by @saddamr3eReader.Discard (#3874) by @saddamr3e; reject unhashable keys in RESP3 map parsing (#3873) by @iabdullah215AddHook (#3868), onNewNode during Ring.SetAddrs (#3862), shared masters/slaves slices in cluster (#3861), shared opt.Addr during pub/sub reconnect (#3906), clusterStateReloadCallback in maintnotifications (#3894), conn reader in isHealthyConn during handoff (#3876) by @saddamr3e; handoff race window in maintnotifications (#3872) by @ndyakovredisotel: use ObservableCounter for cumulative pool stats (#3914) by @Solaris-star; avoid a data race on shared attributes during MinIdleConns warmup (#3881) by @ndyakovredislabs/client-libs-test:8.10.0 image and 8.8 was dropped from the CI matrix (#3940)sync/atomic value types (#3860) and remove the dead assertUnstableCommand RESP3 path (#3928) by @cxljsExpireTime / PExpireTime return Unix timestamps (#3917) by @sonnemusk; remove a duplicate example step (#3875) by @andy-stark-redisWe'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
Full Changelog: https://github.com/redis/go-redis/compare/v9.21.0...v9.22.0-beta.1
This is a minor release adding new features and bug fixes. There are no breaking changes; upgrading from 9.20.x is a drop-in replacement.
GetToBuffer / SetFromBufferTwo new StringCmdable methods let callers read and write Redis string values directly into and from pre-allocated byte buffers, eliminating the per-call payload allocation that Get/Set incur:
GetToBuffer(ctx, key, buf) *ZeroCopyStringCmd // reads into buf; ZeroCopyStringCmd { Val() int; Bytes() []byte; Result() (int, error) }
SetFromBuffer(ctx, key, buf) *StatusCmd
GetToBuffer decodes the bulk reply straight into the caller-owned buf (no intermediate allocation); a buffer that is too small returns an error after draining the payload, so the connection stays aligned for the next reply. SetFromBuffer is provided for API symmetry — it dispatches to the same []byte writer path as Set(ctx, key, buf, 0) and produces byte-identical output on the wire. Available on *Client, *ClusterClient, *Ring, *Conn and Pipeliner.
LIMIT 0 for stream trimmingRedis treats XTRIM/XADD approximate-trim (~) LIMIT 0 as "disable the trimming effort cap entirely", which differs from omitting LIMIT (the implicit 100 * stream-node-max-entries default). The command builders previously only emitted LIMIT when limit > 0, so callers could never send an explicit LIMIT 0. Following the KeepTTL = -1 precedent, the new XTrimLimitDisabled = -1 sentinel now emits an explicit LIMIT 0; limit == 0 keeps the historical no-LIMIT behavior, so existing callers produce byte-identical commands.
(#3848) by @TheRealMal
GetToBuffer / SetFromBuffer on StringCmdable and the ZeroCopyStringCmd result type, reading/writing string values into caller-owned buffers without per-call payload allocation (#3834) by @ndyakovXTrimLimitDisabled sentinel: XTRIM/XADD approximate trimming can now send an explicit LIMIT 0 to disable the trim effort cap, via the new XTrimLimitDisabled = -1 sentinel (#3848) by @TheRealMalchannel.initHealthCheck now bounds the Ping it issues with a fresh per-check timeout context (the exported pingTimeout / reconnectTimeout) instead of context.TODO(), so a stuck health-check Ping can no longer block indefinitely (#3819) by @abdellaniUNWATCH in Tx.Close: a transaction now tracks whether a WATCH is still active (watchArmed) and only issues UNWATCH on Close when it is, removing an extra round trip on the common WATCH/.../EXEC and no-key Watch paths while never returning a connection to the pool with an active watch (#3854) by @fcostaoliveiramaintnotifications ModeAuto fail-open: ModeAuto now stays fail-open when the server does not support maintenance notifications — connections are retired and tracking is guarded during downgrade so the client keeps working instead of erroring (#3853) by @terrorobeWe'd like to thank all the contributors who worked on this release!
@abdellani, @fcostaoliveira, @ndyakov, @terrorobe, @TheRealMal
Full Changelog: https://github.com/redis/go-redis/compare/v9.20.1...v9.21.0
This is a patch release containing bug fixes only. There are no new features or breaking changes; upgrading from 9.20.0 is a drop-in replacement.
PeekPushNotificationName previously inspected only the bytes already buffered by bufio, so when a push frame header straddled a buffer fill boundary it could return a truncated notification name (e.g. "messa" instead of "message"). The push processor then mis-routed the frame and ReadReply silently dropped it, causing intermittent RESP3 pub/sub message loss. The peek now grows its window (36 bytes → up to 4 KiB) and reads more from the connection until the header is complete, cleanly separating incomplete prefixes from corrupt frames (including overflow-safe bulk-length handling). Fixes #3839.
PeekPushNotificationName no longer returns a truncated notification name when a push frame header spans a buffer boundary, preventing silent RESP3 pub/sub message loss (fixes #3839) (#3842) by @ndyakovFT.HYBRID vector params: Vector data is now always sent via PARAMS with auto-generated param names (__vector_param_N, with collision avoidance) when VectorParamName is omitted, since Redis no longer accepts inline vector blobs; the FTHybridOptions.Params map is no longer mutated, so the same options struct can be reused across calls (#3844) by @ndyakovCLUSTER SHARDS forward compatibility: Unknown shard- and node-level attributes in the CLUSTER SHARDS reply are now skipped via DiscardNext() instead of erroring, so clients keep working when the server introduces new fields (#3843) by @madolsonPubSub.releaseConn no longer reconnects twice when a connection is both unusable (or pending handoff) and reports a bad-connection error, avoiding a wasted connection establish-then-close cycle (#3833) by @cxljsWe'd like to thank all the contributors who worked on this release!
Full Changelog: https://github.com/redis/go-redis/compare/v9.20.0...v9.20.1
This release adds support for Redis 8.8. The README's supported-versions list now includes Redis 8.8 alongside 8.0/8.2/8.4, and CI exercises the 8.8-rc1 client-libs-test image across the full suite (Makefile, build workflow, doctests, run-tests action, and docker-compose).
Coverage for the new commands that ship in the 8.x line, rounded out in this release:
AR* array data type (#3813) — new array data structure, exposed via the ArrayCmdable interface (see the experimental-features highlight below).INCREX (#3816) — atomic increment with expiration in a single round-trip.XNACK (#3790) — explicit negative-acknowledge of pending stream entries.XAUTOCLAIM PEL deletes (#3798) — XAUTOCLAIM/XAUTOCLAIMJUSTID now return the list of deleted message IDs from the pending entries list.TS.RANGE multiple aggregators (#3791) — TS.RANGE/TS.REVRANGE/TS.MRANGE/TS.MREVRANGE accept multiple aggregators in a single call.Z(UNION|INTER|DIFF) COUNT aggregator (#3802) — COUNT reducer for sorted-set set operations.JSON.SET FPHA (#3797) — new FPHA argument that specifies the floating-point type for homogeneous FP arrays.CI image bump (#3814) by @ofekshenawa. Command coverage contributions by @cxljs, @elena-kolevska, @Khukharr, @ndyakov, and @ofekshenawa.
UnstableResp3 deprecated)FT.SEARCH, FT.AGGREGATE, FT.INFO, FT.SPELLCHECK, and FT.SYNDUMP now parse RESP3 (map) responses into the same typed result objects as RESP2 — Val() and Result() work uniformly on both protocols, no flag required. Previously, RESP3 search responses required UnstableResp3: true and were returned as opaque maps accessible only via RawResult() / RawVal().
As a result, the UnstableResp3 option is now a no-op across every options struct (Options, ClusterOptions, UniversalOptions, FailoverOptions, RingOptions) and has been marked // Deprecated:. The field is retained for backwards compatibility — existing code that sets UnstableResp3: true will continue to compile and behave identically — but it will be removed in a future release and new code should not set it. RawResult() / RawVal() continue to work for callers that prefer the raw RESP payload.
Adds an experimental ArrayCmdable interface with the AR* command family (ARSet, ARGet, ARGetRange, ARMSet, ARMGet, ARDel, ARDelRange, ARScan, ARSeek, ARNext, ARLastItems, ARGrep, ARGrepWithValues, ARInfo/ARInfoFull, and typed reducers AROpSum/AROpMin/AROpMax/AROpAnd/AROpOr/AROpXor/AROpMatch/AROpUsed) for working with Redis 8.8's new array data type. API is experimental and may change in a future release.
FT.SEARCH/FT.AGGREGATE/FT.INFO/FT.SPELLCHECK/FT.SYNDUMP responses with backwards compatibility for RESP2 (#3741) by @ndyakovINCREX command support — atomic increment with expiration (#3816) by @ndyakovXNACK stream command for explicitly negative-acknowledging pending entries (#3790) by @elena-kolevskaTS.RANGE/TS.REVRANGE/TS.MRANGE/TS.MREVRANGE now accept multiple aggregators in a single call (#3791) by @elena-kolevskaXAutoClaim deleted IDs: XAUTOCLAIM/XAUTOCLAIMJUSTID now return the list of deleted message IDs from the PEL (#3798) by @KhukharrJSON.SET FPHA: JSON.SET accepts a new FPHA argument that specifies the floating-point type for homogeneous floating-point arrays (#3797) by @ndyakovZUNION/ZINTER/ZDIFF aggregator now supports COUNT (#3802) by @ofekshenawaFT.HYBRID vector validation: Validates hybrid-search vector input types and adds proper typed vector parameters (#3756) by @DengY11ClusterClient.PoolStats() now accumulates WaitCount and WaitDurationNs across all node pools (previously always zero) (#3809) by @LINKIWICLUSTER SLOTS port-0 entries now fall back to the origin endpoint's port, fixing dial tcp <ip>:0: connection refused on TLS-only clusters started with --port 0 --tls-port <port> (fixes #3726) (#3828) by @ndyakovPubSub.conn() now passes both regular (c.channels) and sharded (c.schannels) channels into the per-PubSub newConn closure. Previously, ClusterClient.SSubscribe-only PubSubs reconnected to a random node (because the routing closure saw an empty channel list), the SSUBSCRIBE was sent to the wrong shard, and the resulting MOVED reply was silently dropped (#3829) by @ndyakovWatch retry: User errors returned from a Watch callback are no longer subjected to cluster-retry classification; transient cluster errors still retry, but a callback returning e.g. net.ErrClosed short-circuits immediately (#3821) by @obiyangMasterAddr's concurrent sentinel probe now closes the non-winning sentinel clients instead of leaking them (#3827) by @cxljsreplicaAddrs no longer tears down the cached sentinel client when the replica list is empty, eliminating a continuous rediscovery loop on master-only Sentinel deployments that flooded logs and added per-operation latency (#3795) by @shahyash2609CloseConn hooks: Pool.CloseConn now triggers registered hooks, fixing a memory leak when connections are closed explicitly rather than via the normal removal path (#3818) by @ndyakovdial tcp errors are now correctly classified as redirectable so cluster routing can recover from a single unreachable node (#3810) by @vladisa88Close health checks: ConnPool.Close now only runs health checks against idle connections, avoiding spurious activity on connections still in use (#3805) by @ndyakovVLINKS/VLINKSWITHSCORES vector-set replies (#3820) by @romanpovolwaitForSentinelClusterStable post-conditions: The sentinel test harness now waits for replicas to be fully connected (not just present in the count) and is robust to randomized spec ordering after failover specs, eliminating an intermittent Expected master to equal slave flake (#3830) by @ndyakovgovulncheck workflow: New scheduled GitHub Actions workflow runs govulncheck on every push, PR, and weekly, surfacing newly disclosed Go vulnerabilities even when no code changes (#3779) by @solardomeCmd.Slot() lookup refactor: Caches the per-command CommandInfo and short-circuits keyless commands before the switch dispatch, removing redundant Peek calls (#3804) by @retr0-kernelmath/rand: Replaced internal/rand with math/rand from the standard library now that the minimum Go version is 1.24 (#3823) by @cxljsConnPool, trimming the pool's footprint (#3826) by @cxljsextra/* package (#3817) by @ndyakovWe'd like to thank all the contributors who worked on this release!
@cxljs, @DengY11, @elena-kolevska, @Khukharr, @LINKIWI, @ndyakov, @obiyang, @ofekshenawa, @retr0-kernel, @romanpovol, @shahyash2609, @solardome, @vladisa88
Full Changelog: https://github.com/redis/go-redis/compare/v9.19.0...v9.20.0
Script now supports a FIPS-safe execution mode that avoids client-side SHA-1 computation, which is blocked in strict FIPS environments. A new NewScriptServerSHA constructor uses SCRIPT LOAD to obtain and cache the digest from the server, then runs commands via EVALSHA/EVALSHA_RO. Falls back to EVAL/EVALRO if loading fails, and transparently retries once on NOSCRIPT. The default behavior is unchanged for existing users.
(#3700) by @chaitanyabodlapati
Added a new step-based FT.AGGREGATE pipeline API via FTAggregateOptions.Steps, allowing LOAD, APPLY, GROUPBY, and SORTBY (with per-step MAX) to be repeated and interleaved in arbitrary order — matching Redis's native multi-stage aggregation semantics. The legacy Load/Apply/GroupBy/SortBy/SortByMax fields are now deprecated.
Added DoRaw and DoRawWriteTo methods for executing arbitrary commands and reading the raw RESP response. Useful for proxying, custom protocol inspection, and working with commands not yet wrapped by go-redis.
(#3713) by @ofekshenawa
Added DialerRetryBackoff option (plumbed through Options, ClusterOptions, RingOptions, FailoverOptions) to let callers customize the delay between failed dial attempts. Helpers DialRetryBackoffConstant and DialRetryBackoffExponential (with jitter and cap) are provided out of the box. Dial timeout is now also applied per attempt rather than across all retries.
FT.AGGREGATE with support for repeated/interleaved LOAD, APPLY, GROUPBY, and SORTBY stages (#3782) by @ndyakovVISMEMBER and WITHATTRIBS support (#3753) by @romanpovolNewScriptServerSHA uses SCRIPT LOAD to obtain the digest from the server, avoiding client-side SHA-1 (#3700) by @chaitanyabodlapatiDoRaw and DoRawWriteTo for raw RESP protocol access (#3713) by @ofekshenawaDialerRetryBackoff function option with constant and exponential helpers (#3706) by @mwhookerNOSCRIPT replies are now surfaced as a typed error for easier handling (#3738) by @LINKIWIClientSetName method to PubSub (#3727) by @Flack74ReplicaOf method replaces the deprecated SlaveOf (#3720) by @CopilotHScan now supports types implementing encoding.BinaryUnmarshaler (#3768) by @Aaditya-dubey1CLIENT MAINT_NOTIFICATIONS handshake when HELLO fails and connection falls back to RESP2; fail fast when explicitly enabled with RESP3 (#3788) by @ndyakovShouldRetry now treats net.OpError with Op == "dial" timeout errors as safe to retry since no command was sent (#3787) by @vladisa88baseClient close logic; replaced with a bounded, concurrency-safe named-hook registry (#3785) by @ndyakovcloseNotify timeouts) for connections already dropped by the server due to idle timeout (#3778) by @ofekshenawaConnStateMachine.notifyWaiters that could wake multiple waiters under a single mutex hold and violate FIFO ordering (#3777) by @0x48coreREADONLY errors embedded in Lua script error messages on read-only replicas so commands are correctly retried (#3769) by @zhengjileiVSimWithScores, VSimWithArgsWithScores, and VLinksWithScores which were broken on RESP2 connections returning flat arrays instead of maps (#3767) by @CopilotZRangeArgs with Rev + ByScore/ByLex incorrectly swapping Start/Stop, breaking ZRANGESTORE (#3751) by @Copilotredisotel-native (#3743) by @ofekshenawaOptions (#3739) by @rubensayshiredisotel-native (#3735) by @ofekshenawaotel/semconv/v1.38.0 in redisotel-native (#3731) by @wzy9607SET ... NX instead of the deprecated SETNX command (#3723) by @ndyakovTIME as a keyless command for correct cluster routing (#3722) by @fatal10110pool.name being appended per node, which corrupted and dropped user-provided custom attributes (#3699) by @Jesse-Bonfire*baseClient.initConn(); added explicit nil option guards to client constructors (#3676) by @olde-duckegithub.com/dgryski/go-rendezvous dependency with an in-repo implementation in internal/hashtag, reducing the dependency graph while preserving algorithm parity (#3762) by @bigsk05repository, ref, and client-libs-test-image-tag inputs to the run-tests composite action; redis-version is now optional so unstable builds use REDIS_VERSION from the Makefile (#3749) by @dariaguy-compat=1.24 in release scripts (#3714, #3754) by @ndyakov, @cxljsConn.closed atomic field in favor of the state machine's StateClosed (#3783) by @cxljsredisotel/redisotel-native (#3770) by @ndyakovmaps.Keys, slices.Collect, slices.Contains, clear(), and slices.SortFunc instead of custom helpers (#3758, #3746) by @cxljsHGetAll describing behavior and complexity (#3776) by @0x48coreWe'd like to thank all the contributors who worked on this release!
@0x48core, @Aaditya-dubey1, @Copilot, @Flack74, @Jesse-Bonfire, @LINKIWI, @bigsk05, @chaitanyabodlapati, @cxljs, @dariaguy, @fatal10110, @mwhooker, @ndyakov, @ofekshenawa, @olde-ducke, @olzhas-sabiyev, @romanpovol, @rubensayshi, @vladisa88, @wzy9607, @zhengjilei
Full Changelog: https://github.com/redis/go-redis/compare/v9.18.0...v9.19.0
Added support for Redis 8.6, including new commands and features for streams idempotent production and HOTKEYS.
This release introduces comprehensive support for Redis Cluster maintenance notifications via SMIGRATING/SMIGRATED push notifications. The client now automatically handles slot migrations by:
Added comprehensive OpenTelemetry metrics support following the OpenTelemetry Database Client Semantic Conventions. The implementation uses a Bridge Pattern to keep the core library dependency-free while providing optional metrics instrumentation through the new extra/redisotel-native package.
Metric groups include:
(#3637) by @ofekshenawa
ProducerID, IdempotentID, IdempotentAuto in XAddArgs and new XCFGSET command (#3693) by @ofekshenawaDialerRetries and DialerRetryTimeout to ClusterOptions, RingOptions, and FailoverOptions (#3686) by @naveenchander30DigestString and DigestBytes helper functions for client-side xxh3 hashing compatible with Redis DIGEST command (#3679) by @ofekshenawaWithTimeout() - pubSubPool is now properly cloned (#3710) by @CopilotMaintNotificationsConfig in initConn (#3707) by @veeceeywantConn elements accumulation in wantConnQueue (#3680) by @cyningsun= when approx is false (#3684) by @ndyakoverrors.Join() (#3653) by @cxljsMaxActiveConns (#3674) by @codykaupWe'd like to thank all the contributors who worked on this release!
@12ya, @Copilot, @codykaup, @cxljs, @cyningsun, @feelshu, @feiguoL, @iamamirsalehi, @naveenchander30, @ndyakov, @ofekshenawa, @veeceey
Full Changelog: https://github.com/redis/go-redis/compare/v9.17.0...v9.18.0
This release updates the minimum required Go version to 1.21. This is part of a gradual migration strategy where the minimum supported Go version will be three versions behind the latest release. With each new Go version release, we will bump the minimum version by one, ensuring compatibility while staying current with the Go ecosystem.
This release includes several important stability fixes:
We'd like to thank all the contributors who worked on this release!
@justinhwang, @ndyakov, @kiryazovi-redis, @fengve, @ccoVeille, @ofekshenawa
Full Changelog: https://github.com/redis/go-redis/compare/v9.18.0-beta.1...v9.18.0-beta.2
This beta release introduces comprehensive support for Redis COMMAND-based request and response policy routing for cluster clients. This feature enables intelligent command routing and response aggregation based on Redis command metadata.
Key Features:
default(keyless) - Commands without keysdefault(hashslot) - Commands with hash slot routingall_shards - Commands that need to run on all shardsall_nodes - Commands that need to run on all nodesmulti_shard - Commands that span multiple shardsspecial - Commands with custom routing logicall_succeeded - All shards must succeedone_succeeded - At least one shard must succeedagg_sum - Aggregate numeric responsesspecial - Custom aggregation logic (e.g., FT.CURSOR)Client.Do(ctx, args...)This feature is particularly useful for Redis Stack commands like RediSearch that need to operate across multiple shards in a cluster.
Fixed a critical defect in the connection pool's turn management mechanism that could lead to connection leaks under certain conditions. The fix ensures proper 1:1 correspondence between turns and connections.
We'd like to thank all the contributors who worked on this release!
@cyningsun, @ofekshenawa, @ndyakov
Full Changelog: https://github.com/redis/go-redis/compare/v9.17.1...v9.18.0-beta.1
We'd like to thank all the contributors who worked on this release!
@marcoferrer and @ndyakov
Full Changelog: https://github.com/redis/go-redis/compare/v9.17.0...v9.17.1
Added support for Redis 8.4, including new commands and features (#3572)
Introduced typed errors for better error handling using errors.As instead of string checks. Errors can now be wrapped and set to commands in hooks without breaking library functionality (#3602)
IFEQ, IFNE, IFDEQ, IFDNE) (#3583, #3595)ACLGenPass, ACLUsers, and ACLWhoAmI (#3576)SLOWLOG LEN and SLOWLOG RESET (#3585)LATENCY LATEST and LATENCY RESET (#3584)FT.HYBRID command (#3573)VRANGE command for vector sets (#3543)joinErrors to prevent panic (#3577) by @manisharmaWe'd like to thank all the contributors who worked on this release!
@12ya, @ajax16384, @cxljs, @cyningsun, @destinyoooo, @dragneelfps, @htemelski-redis, @manisharma, @ndyakov, @ofekshenawa, @pvragov
Full Changelog: https://github.com/redis/go-redis/compare/v9.16.0...v9.17.0
This release introduces comprehensive support for Redis maintenance notifications, enabling applications to handle server maintenance events gracefully. The new maintnotifications package provides:
For detailed usage examples and configuration options, see the maintenance notifications documentation.
TraceCmdFilter option to selectively trace commandsmetric.WithAttributeSet to avoid unnecessary attribute copying in redisotel (#3552)MaxRetries is disabled for ClusterClient (#3551)rojopolis/spellcheck-github-actions from 0.51.0 to 0.52.0 (#3520)github/codeql-action from 3 to 4 (#3544)We'd like to thank all the contributors who worked on this release!
@ndyakov, @htemelski-redis, @Sovietaced, @Udhayarajan, @boekkooi-impossiblecloud, @Pika-Gopher, @cxljs, @huiyifyj, @omid-h70
Full Changelog: https://github.com/redis/go-redis/compare/v9.14.0...v9.16.0
This beta release includes a pre-production version of processing push notifications and hitless upgrades.
We'd like to thank all the contributors who worked on this release!
@cxljs, @ndyakov, @htemelski-redis, and @omid-h70
This beta release includes a pre-production version of processing push notifications and hitless upgrades.
Hitless upgrades is a major new feature that allows for zero-downtime upgrades in Redis clusters. You can find more information in the Hitless Upgrades documentation.
We'd like to thank all the contributors who worked on this release!
@ndyakov, @htemelski-redis, @ofekshenawa
We'd like to thank all the contributors who worked on this release!
@elena-kolevska, @htemelski-redis and @ndyakov
We'd like to thank all the contributors who worked on this release!
@LINKIWI, @cxljs, @cybersmeashish, @elena-kolevska, @htemelski-redis, @mwhooker, @ndyakov, @ofekshenawa, @suever
In the last version (9.12.0) the client introduced bigger write and read buffer sized. The default value we set was 512KiB.
However, users reported that this is too big for most use cases and can lead to high memory usage.
In this version the default value is changed to 256KiB. The README.md was updated to reflect the
correct default value and include a note that the default value can be changed.
We'd like to thank all the contributors who worked on this release!
@ndyakov and @vmihailenco
FTSearch, FTAggregate and other search commands.EPSILON option in FT.VSIM.errors.Join requires Go 1.20 or later (#3442)EPSILON option (#3454)We'd like to thank all the contributors who worked on this release!
@andy-stark-redis, @cxljs, @elena-kolevska, @htemelski-redis, @jouir, @monkey92t, @ndyakov, @ofekshenawa, @rokn, @smnvdev, @strobil and @wzy9607
Fixes TxPipeline to work correctly in cluster scenarios, allowing execution of commands only in the same slot.
scan commands, rather than random (#2623)Ring, Client and ClusterClient (#3401)We'd like to thank all the contributors who worked on this release!
@andy-stark-redis, @boekkooi-impossiblecloud, @cxljs, @dcherubini, @dependabot[bot], @iamamirsalehi, @ndyakov, @pete-woods, @twz915 and dependabot[bot]
go-redis now supports vector sets. This data type is marked
as "in preview" in Redis and its support in go-redis is marked as experimental. You can find examples in the documentation and
in the doctests folder.
We'd like to thank all the contributors who worked on this release!
@AndBobsYourUncle, @andy-stark-redis, @fukua95 and @ndyakov
StreamingCredentialsProvider for dynamic credential updates (experimental)
ParseFailoverURL for easier failover configurationStreamingCredentialsProvider for token-based authentication (#3320)
ParseFailoverURL for parsing failover URLs (#3362)GetShardClients() to retrieve all active shard clientsGetShardClientForKey(key string) to get the shard client for a specific key (#3388)ReplaceSpaces function (#3383)Options.Protocol in init() (#3387)We would like to thank all the contributors who made this release possible:
@ndyakov, @ofekshenawa, @LINKIWI, @iamamirsalehi, @fukua95, @lzakharov, @DengY11
For a complete list of changes, see the full changelog.
HGETDEL, HGETEX, HSETEX) and HSTRLEN commandCountOnly argument for FT.SearchHGETDEL, HGETEX, HSETEX (#3305)HSTRLEN command for hash operations (#2843)Do method for raw query by single connection from pool.Conn() (#3182)IsClusterMode config parameter (#3255)HELLO RESP handshake (#3294)CountOnly argument for FT.Search to use LIMIT 0 0 (#3338)DB option support in NewFailoverClusterClient (#3342)nil check for the options when creating a client (#3363)PubSub concurrency safety issues (#3360)nil (#3353)MASTERDOWN a retriable error (#3164)FT.Search Limit argument and added CountOnly argument for limit 0 0 (#3338)COUNTKEYSINSLOT command (#3327)CountOnly search example (#3345)LLEN, LPOP, LPUSH, LRANGE, RPOP, RPUSH (#3234)SADD and SMEMBERS command examples (#3242)README.md to use Redis Discord guild (#3331)HExpire command documentation (#3355)README.md with additional information (#310ce55)We would like to thank all the contributors who made this release possible:
@alexander-menshchikov, @EXPEbdodla, @afti, @dmaier-redislabs, @four_leaf_clover, @alohaglenn, @gh73962, @justinmir, @LINKIWI, @liushuangbill, @golang88, @gnpaone, @ndyakov, @nikolaydubina, @oleglacto, @andy-stark-redis, @rodneyosodo, @dependabot, @rfyiamcool, @frankxjkuang, @fukua95, @soleymani-milad, @ofekshenawa, @khasanovbi
go-redis won't skip span creation if the parent spans is not recording. (#2980)
Users can use the OpenTelemetry sampler to control the sampling behavior.
For instance, you can use the ParentBased(NeverSample()) sampler from go.opentelemetry.io/otel/sdk/trace to keep
a similar behavior (drop orphan spans) of go-redis as before.FUNCTION group of commands (#2475)ContextTimeoutEnabled option that controls whether the client respects context timeouts
and deadlines. See
Redis Timeouts for details.ParseClusterURL to parse URLs into ClusterOptions, for example,
redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791.redisotel.IstrumentMetrics. See
documentationredis.HasErrorPrefix to help working with errors.DialHook.redisotel.NewTracingHook with redisotel.InstrumentTracing. See
example and
documentation.*redis.Z with redis.Z since it is small enough to be passed as value without making
an allocation.MaxConnAge to ConnMaxLifetime.IdleTimeout to ConnMaxIdleTime.MaxIdleConns.WithContext since context.Context can be passed directly as an arg.Pipeline.Close since there is no real need to explicitly manage pipeline resources and
it can be safely reused via sync.Pool etc. Pipeline.Discard is still available if you want to
reset commands for some reason.