9.23.0
This is a minor release. It contains everything from 9.23.0-beta.1, so these notes cover the full 9.22.0 → 9.23.0 upgrade.
- Go 1.26 is now the minimum Go version (#4060). The
godirective of the root module and of the submodules moved from1.24to1.26, as part of the dependency bumps forgovulncheckfindings. Projects that build with Go 1.24 or 1.25 must upgrade the toolchain. - Each
Clienthas a dedicated pipeline connection pool (#4002). This includes failover clients and cluster node clients.Pipeline,TxPipeline, and autopipeline operations use this pool, so they do not compete with regular commands for main-pool connections. The pool is for burst capacity only: it dials only when necessary, an unused pool holds zero connections, and if the pool is full an operation uses the main pool. SetPipelinePoolSize: -1to get the previous single-pool behavior. AutoPipelineOptions.MaxBatchByteshas a default of 128 KiB (before, there was no limit). The default prevents a write/reply deadlock; it is not a throughput control. The buffers of the pipeline pool also have a default of 128 KiB.
AutoPipelineOptions.FullDuplexFastSubmitis removed (#4018). The submit channel is replaced by a queue that takes a full wave of commands in one lock, so the separate fast path is not necessary. Remove the field from your options.Options.ClientSideCacheRefreshRecencyWindowis removed (#4034). Refresh-on-invalidate now reads again every cached entry of an invalidated key and does not look at how recently the entry was read. On a write-heavy keyspace this means refresh traffic across the full resident cache. Remove the field from your options.LocalCache.LRUClock()is removed (#4034). It returned the recency token that the removed recency window used, and has no replacement. Remove calls to it.ClientSideCacheInvalidationBatchWindowserved stale values in beta.1 (#4033). Under load the batcher applied only ~15% of invalidations, and the cache served invalidated values with a ~100% hit rate. 9.23.0 applies all of them. If you used this option on beta.1, upgrade.
Set AutoPipelineOptions.FullDuplex to enable the full-duplex mode of the automatic pipeliner. The default mode sends one batch per round trip. In full-duplex mode the engine holds one pipeline-pool connection, and a writer goroutine and a reader goroutine move the ordered command stream in both directions at the same time. On a high-latency link each command completes in approximately one round-trip time (RTT) on a single connection. On a 50 ms WAN profile: ~389k ops/s at 52 ms p50, against ~207k ops/s at 116 ms on the half-duplex ordered path.
The mode works on both faces of a standalone Client (AutoPipeline() and AsyncAutoPipeline()), and natively on a ClusterClient when routing goes to masters only. On a cluster, one child engine per master sends each command to the node that owns its slot, and follows MOVED / ASK redirects and retryable replies (LOADING, READONLY, ...) through the usual cluster redirect path. With replica routing (ReadOnly, RouteByLatency, or RouteRandomly), the autopipeliner uses the half-duplex shard flushers, which obey the shard picker. Config().FullDuplex reports the mode in effect.
Options:
FullDuplexWindow— the maximum number of commands in flight (backpressure).FullDuplexIdleTimeoutandFullDuplexMaxHold— when the engine returns the held connection to the pool. The pool hooks then do re-authentication and maintenance-notification handoffs.MaxFlushDelay— now also used in full-duplex mode (#4014). The writer waits only when enough commands are in flight, so low-concurrency callers keep the 1×RTT behavior. WithMaxFlushDelay=250µsat 1024 callers: +22% ops/s and −26% p99. The default is 0, so this is opt-in.NumShards— on a standaloneClient, the number of full-duplex engines (#4022). See below.
Pipeline() on the full-duplex connection (#4021). Standalone Client only. Before, ap.Pipeline() used a pooled connection for each Exec. It now submits the batch to the engine as one contiguous run (FDPipelined), so replies come back in submit order, as on a dedicated connection. At 256–1024 callers with 10 commands per Exec: +49% to +52% throughput on one socket instead of ~150. Errors, retries, hooks, and metrics behave like an ordinary pipeline. A batch that cannot use the stream (a blocking command, a per-command read timeout, a diverted command, or a batch larger than the queue) falls back to the ordinary pipeline. TxPipeline stays on a pooled connection, because MULTI/EXEC needs connection affinity. On a ClusterClient, ap.Pipeline() is still the ordinary cluster pipeline. A batch that the queue cannot admit now reserves its slots and waits in a first-come line, so single commands cannot starve a long pipeline (#4057, merged as part of #4021) by @vlady-kotsev.
Several engines on one client (#4022). With FullDuplex, NumShards > 1 now means N engines on one client: one pool, one hook chain, N held connections. Commands route by a hash of their first key, so commands on the same key stay on one connection and keep their order without Unordered. Keyless commands are distributed round-robin. An FDPipelined batch stays on one engine. The pipeline pool must hold at least NumShards connections for each full-duplex autopipeliner. More engines help only under high concurrency: with 10-command pipelines, 8 engines were 30% slower than 1 at 8 callers, equal at about 128 callers, and 2.2× faster at 1024 callers.
The full-duplex path supports blocking commands, Options.Limiter (one admission per written batch), per-command and pipeline hooks, OTel metrics, retry budgets (a NoRetry command is never sent again after it is on the wire), and seamless maintenance handoffs. Limits of the stream model:
- A hook can observe a command, but cannot stop it. A
ProcessHookthat returns without callingnextdoes not cancel the command on the full-duplex path; the command is already queued on the held connection. Run policy or kill-switch hooks on a plain client or on the half-duplex autopipeliner. - Diverted commands are not ordered with the stream. The engine diverts blocking commands, connection-hostile commands, and managed
HIMPORTcommands. On the async face, wait for the result of a diverted command before you submit a command that depends on it. - Single commands on the full-duplex stream do not use the client-side cache. If CSC is enabled, a cacheable command on the stream does not read or write the cache.
- Duration metrics are recorded per reply. A command that fails before it reaches the reader (lease failure, limiter denial, retry exhaustion, close) calls the error callback but records no operation-duration sample.
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
AutoPipelineOptions: &redis.AutoPipelineOptions{
FullDuplex: true, // stream commands on one held connection, ~1 RTT each
},
})
defer rdb.Close()
// Deferred face: calls return immediately; result accessors block until executed.
ap, err := rdb.AsyncAutoPipeline()
if err != nil {
panic(err)
}
cmds := make([]*redis.StatusCmd, 0, 1000)
for i := 0; i < 1000; i++ {
cmds = append(cmds, ap.Set(ctx, fmt.Sprintf("key:%d", i), i, 0))
}
for _, cmd := range cmds {
if err := cmd.Err(); err != nil {
// handle error
}
}
// Or the blocking face — a drop-in Cmdable where each call blocks like a
// plain client while concurrent callers share the full-duplex pipe:
// ap, err := rdb.AutoPipeline()
// val, err := ap.Get(ctx, "key").Result()
Runnable examples: example/autopipeline (all faces), example/autopipeline-fullduplex (blocking face, Pipeline() and FDPipelined), and example/autopipeline-fullduplex-async (async face, Submit, NumShards) (#4061).
Experimental: the auto-pipelining APIs can change in a minor release.
(#4002, #4014, #4018, #4021, #4022) by @ndyakov
Two new functions for the experimental shared-tracking client-side cache. They have no effect unless CSC is enabled (#3989) by @ndyakov:
- Refresh-on-invalidate (
Options.ClientSideCacheRefreshOnInvalidate): when an invalidation push arrives, the client reads every cached entry of that key again in the background, so the next reader does not pay the miss. Each invalidation of a cached key costs one background read.ClientSideCacheInvalidationBatchWindowcollects invalidation-driven deletes into background batches; without it, the connection reader applies each delete inline. - Miss coalescing (
Options.ClientSideCacheCoalesceMisses): concurrent cache misses are pipelined on one tracked connection. Each miss keeps its own per-key command (no rewrite toMGET), so it is safe on a cluster. Each reply is written to the cache with the connection's tracking generation, so the server can invalidate it.
Both options need the built-in cache (ClientSideCacheConfig, or ClientSideCache set to a *LocalCache). With a custom Cache implementation they are ignored.
The cache read path is also faster (#4034, #4035, #4036, #4037) by @ndyakov. A hit no longer writes a timestamp to the entry; a second-chance bit marks reads, and is set only under eviction pressure. The cache key is built in pooled scratch memory, the key list is built only on a miss, and nil/status/bulk-string hits are decoded without allocation. Against 9.23.0-beta.1 on a warm cache: +18% to +57% reads/s from 4 to 1024 callers, p50 from 2.6–5.4 µs to 1.0–1.5 µs, with identical hit rates and 100% of invalidations applied.
RouteByLatency selects the node with the strictly minimum latency. The measurement has noise (the mean of ten pings, refreshed at most every 10 s), so all clients can select the same node from a set of nodes with almost equal latency. In one production system, GET rates across five replicas in one availability zone had a 590× spread.
The new ClusterOptions.RouteByLatencyTolerance widens the selection to every node within the tolerance of the fastest node, and distributes reads across them round-robin with the ShardPicker. A node in a different availability zone stays outside a sensible tolerance, so zone locality is kept. The default is zero (strict minimum). The option is also on FailoverOptions, for clients from NewFailoverClusterClient; the plain NewFailoverClient does not support latency routing. (#3973) by @jozenstar
The same work fixed a routing defect: the client recorded the nearest healthy node only when that node was also the fastest overall. A node that fails fast (for example, a refused connection) hid every healthy node, and reads went to the failing node. The client now records the healthy minimum separately. (#3994) by @jozenstar
Client.Conn() returns its connection to the parent pool on Close. If you ran AUTH or SELECT on it, later commands on the pooled client run as that ACL user and in that DB. The new Client.EphemeralConn() returns the same sticky connection, but Close removes it from the pool instead. The cost is one dial per EphemeralConn. Client.Conn() is unchanged. (#4039) by @saddamr3e
- Full-duplex auto-pipelining:
AutoPipelineOptions.FullDuplex, withFullDuplexWindow/FullDuplexIdleTimeout/FullDuplexMaxHold, on standalone and cluster clients (#4002) by @ndyakov Pipeline()on the full-duplex connection:ap.Pipeline()andFDPipelinedsubmit a batch to the engine as one contiguous run, on a standaloneClient(#4021) by @ndyakov- Several full-duplex engines on one client:
NumShards > 1withFullDuplexon a standaloneClient, with key-hash routing (#4022) by @ndyakov AutoPipelineOptions.MaxQueuedCommands: an opt-in hard limit on accepted, not-yet-completed autopipeline commands. At the limit a command fails immediately withErrAutoPipelineQueueFull, so memory does not grow without limit when the server is slow. WithFullDuplex, the ordered stream is bounded byFullDuplexWindowinstead (a full window blocks the submitter, it does not reject), andMaxQueuedCommandslimits only the commands that run outside the stream (blocking, connection-hostile,Do). The default0means no limit (#4070) by @ndyakov- Dedicated pipeline pool by default:
PipelinePoolSizehas a default ofDefaultPipelinePoolSize(10) on each client. If the pool is full, an operation uses the main pool. Set-1to disable the pool (#4002, #3959) by @ndyakov - CSC refresh-on-invalidate and miss coalescing:
Options.ClientSideCacheRefreshOnInvalidate(withClientSideCacheInvalidationBatchWindow) andOptions.ClientSideCacheCoalesceMisses(#3989) by @ndyakov Client.EphemeralConn: a sticky connection that is removed from the pool onClose, for session-scopedAUTH/SELECTwork (#4039) by @saddamr3eRouteByLatencyTolerance: distributes reads across nodes with almost equal latency, on cluster clients and onNewFailoverClusterClient(#3973) by @jozenstarAutoPipeliner.WaitClosed: blocks until the drain of accepted commands completes, and returns the drain result. Use it in a wrapper that must not close shared pools during a flush (#3998) by @ndyakovredisotel-nativeWithRecordNilErrors: opt in to countredis.Nilreplies as client errors (see the fix below) (#4025) by @lazergCMSInfo.CellSize: the cell-size field ofCMS.INFOin Redis 8.12 (#4010) by @elena-kolevska
- CSC invalidation batching:
ClientSideCacheInvalidationBatchWindowapplied only ~15% of invalidations under load, so the cache served stale values with a ~100% hit rate. Each batch now takes each shard lock once, and 100% of invalidations are applied (#4033) by @ndyakov - Pool waiter wake-ups:
Putdid not wake a worker that waited for a connection to becomeIDLE, so a streaming-credentials re-auth could stall untilPoolTimeoutand then close the connection (#4028) by @avitenzer. Two related waiter-queue races are fixed: a failed transition left a dead waiter queued, which later moved a connection toUNUSABLEfor nobody (#4072) by @ndyakov - Command key positions: 16 typed commands (
BitOp*,MIGRATE,OBJECT ENCODING/FREQ/IDLETIME/REFCOUNT,XINFO STREAM/GROUPS/CONSUMERS,LMPOP,BLMPOP,SINTERCARD,ZINTERCARD,ZMPOP,BZMPOP) routed by the wrong argument. On aRingthey went to the wrong shard; on a cluster they cost an extraMOVEDround trip. A rawFCALL/FCALL_ROhashed the function name. All now route by their key (#4048) by @ndyakov. RawXGROUP,ZUNION/ZINTER/ZDIFF,SDIFFCARD/SUNIONCARD,TS.NRANGE/TS.NREVRANGE, andHIMPORT SETforms are also fixed (#4022) - Millisecond expirations:
PExpireAt,HPExpireAt, andHPExpireAtWithArgsoverflowed for dates outside the nanosecond range (for example, year 2270), so Redis deleted the key or rejected the command (#4026) by @jakezwang.SetArgs.ExpireAtnow sendsPXATwhen the deadline has millisecond precision; before,EXATdropped the milliseconds (#4053) by @LindseyZ1205 - Cluster
-1timeouts: a-1read/write timeout onClusterOptionsgave node clients the 5 s default instead of no timeout (fixes #4049) (#4050) by @lazerg - OTel
redis.Nilas error: a cache miss (redis.Nil) incrementedredis.client.errorsand was tagged as a server error ondb.client.operation.duration. Nil replies are now classifiedNILand not counted, unlessWithRecordNilErrors(true)is set (fixes #4024) (#4025) by @lazerg - Credential redaction:
SENTINEL SET ... auth-passis now redacted in command rendering (used byredisotel/rediscensusspan attributes), and aMIGRATEpassword with the literal valueauth/auth2is redacted at the correct position (#4011) by @saddamr3e - Min/max aggregator panic: a non-numeric shard reply made
AggMinAggregator/AggMaxAggregatorpanic instead of returning an error fromResult()(#4056) by @Suselz ParseURLskip_verify: an invalid value (for example,skip_verify=yes) was silently treated asfalse.ParseURLandParseFailoverURLnow return an error, like for the other boolean options (#4064) by @orinnzuintptrarguments:uintptrand*uintptrare now encoded like the other unsigned integers; a nil*uintptris encoded as0(fixes #3100) (#4054) by @yamaankhan20- Cluster read routing: the client records the nearest healthy node separately from the overall minimum, so a node that fails fast does not hide the healthy nodes (#3994) by @jozenstar
- Probabilistic
*.INFOforward compatibility: theBF.INFO/CF.INFO/CMS.INFO/TOPK.INFO/TDIGEST.INFOparsers skip unknown fields instead of returning an error. Redis 8.12 addscell sizetoCMS.INFO(#4010) by @elena-kolevska NewClientpanic leak: a panic during construction (for example, a maintnotifications failure inModeEnabled) does not leak the connection pools that already exist, and a typed-nil pool cannot hide the initial panic (#4003) by @ndyakov. The same guards apply toNewFailoverClient(#4002)- File-descriptor leak on rejected connections:
Conn.Closedoes the socket teardown and the unsubscribe/CSC callbacks when the connection is alreadyCLOSED. Before, init and auth failures leaked open descriptors (fixes #3982) (#3985) by @ndyakov - Global logger races: atomics protect the global
LoggerandLogLevel, and call-site attribution is correct again (#3988) by @saddamr3e Conn.onClosedata race: the close hooks that init installs (onCloseandonCscClose) are atomic against a concurrentClose(#3966) by @saddamr3e- Reply-parser hardening: the reply parsers accept zero-length entry arrays (#3995), and
FTHybridCmdreads the full RESP3 map reply without desynchronizing the connection (#3956) by @saddamr3e CLIENT INFOforward compatibility: the parser skips unknown client-flag characters instead of failing the reply (#3977) by @ndyakovGEOSEARCHduplicate args: the command no longer sends duplicate arguments (#3955) by @mehmettokgozMSetEXcluster routing: the constructor sets the first-key position, so typed calls go to the correct slot (#3984) by @shivamrustagi- Maintenance notifications: no endpoint DNS detection when the mode is disabled (#3969) by @Phalanyx
- Autopipeliner
Close: a concurrentClosedoes not block (no re-entrant deadlock), andWaitClosedreturns the drain result (#3998) by @ndyakov - Buffered-push log noise: the buffered-push-data notice in
isHealthyConnis logged only at debug level, so CSC invalidations do not fill the log (#3948) by @ndyakov - Sentinel teardown order: close hooks run in LIFO order, so an autopipeliner drain completes before Sentinel discovery stops, and a closed failover client cannot recreate its Sentinel resources from a late dial (#4002) by @ndyakov
- Pipeline desync containment: if a pre-write push-notification drain fails or a command encoder panics, the
Pipeline/TxPipelinepath removes the connection instead of returning a desynchronized connection to the pool (#4002) by @ndyakov
- CSC read path: second-chance read bit, pooled cache-key scratch, key list built only on a miss, allocation-free decode of simple hits. +18% to +57% reads/s against 9.23.0-beta.1 on a warm cache (#4034, #4035, #4036, #4037) by @ndyakov
- Full-duplex reads and submit queue: replies already in the buffer are read as one group (one read deadline per group, not per reply), and a mutex+slice queue replaces the submit channel, so the writer takes a whole wave in one lock (#4018) by @ndyakov
- Full-duplex
MaxFlushDelay: opt-in write coalescing under load. With 250 µs at 1024–2048 callers: +22% to +25% ops/s and −26% to −29% p99; 30% less CPU at 256 callers (#4014) by @ndyakov - Full-duplex allocations: a ring buffer holds the in-flight queue, and the blocking face uses a pool of batches. 770 B/op → 353 B/op at 2048 concurrent callers (#3970, part of #4002) by @ndyakov
- Zero-copy scan:
Scangets zero-copy semantics, and the RESP reader skips unnecessary data conversions (#3972) by @vlady-kotsev - Autopipeline straggler hold: the engine limits the hold on queued commands when the pipeline pool has a free connection. Uncached p95 on a 50 ms link: 111 ms → 65 ms; real-WAN uncached p99: 314 ms → 177 ms (#3962) by @ndyakov
- Go 1.26 and
govulncheckdependency bumps: the minimum Go version is 1.26, CI defaults to 1.26, and a newgo-versionsjob builds the test matrix fromgo.modplusoldstable/stable(#4060) by @vlady-kotsev - Fast skip gates: tests do a TCP probe of each address before the
Pinggate, which removes ~1.6 min of dial-retry waits when the full stack is not running (#4001) by @ndyakov - Redis Enterprise coverage: the autopipeline suites connect to the RE database and use the suite DB (#3976, #3975), timing assertions scale to the measured RTT (#3978), and the
CLIENT INFOtracking-flag assertion is skipped behind the RE proxy (#3981) by @ndyakov - Docs:
UniversalOptionsdocumentsReadTimeout/WriteTimeout/ContextTimeoutEnabled(#4047) by @lazerg; the README describesDisableIdentityas client identification (CLIENT SETINFO), not identity verification (#4059) by @FanWu-ai - Security policy: send vulnerability reports to the Redis VDP (#3949) by @ndyakov
We thank all the contributors who worked on this release!
@avitenzer, @elena-kolevska, @FanWu-ai, @jakezwang, @jozenstar, @lazerg, @LindseyZ1205, @mehmettokgoz, @ndyakov, @orinnz, @Phalanyx, @saddamr3e, @shivamrustagi, @Suselz, @vlady-kotsev, @yamaankhan20
Full Changelog: https://github.com/redis/go-redis/compare/v9.22.0...v9.23.0
9.23.0-beta.1
This is a beta release. You can upgrade without changes to your code. The release adds three primary features:
- An experimental full-duplex mode for the automatic pipeliner.
- Refresh and miss-coalescing functions for client-side caching.
- Better distribution of cluster reads under latency-based routing.
The release also contains many stability fixes and robustness fixes.
Client (this includes failover clients and cluster node clients) now creates a small, dedicated pipeline connection pool (#4002). Pipeline, TxPipeline, and autopipeline operations use this pool. These operations do not compete with regular commands for main-pool connections. The pool supplies burst capacity only:
- The pool does not dial a connection before the connection is necessary.
- An unused pool holds zero connections. The idle cost is zero.
- If the pool is full, an operation immediately uses the main pool.
Set PipelinePoolSize: -1 to get the previous single-pool behavior.
Set AutoPipelineOptions.FullDuplex to enable the full-duplex mode of the automatic pipeliner. The default mode sends one batch for each round trip. The full-duplex mode is different: the engine holds one pipeline-pool connection, and a writer goroutine and a reader goroutine move the ordered command stream in the two directions at the same time. On a link with high latency, each command completes in approximately one round-trip time (RTT) on a single connection. A test on a 50 ms WAN profile measured ~389k operations/s at 52 ms p50. The half-duplex ordered path measured ~207k operations/s at 116 ms.
The mode operates on the two faces of a standalone Client: AutoPipeline() and AsyncAutoPipeline(). The mode also operates natively on a ClusterClient if the routing goes to masters only. A child engine for each master sends each command to the node that owns the command's slot. The engine obeys MOVED and ASK redirects and retryable replies (LOADING, READONLY, ...) through the usual cluster redirect procedure. If replica routing is set (ReadOnly, RouteByLatency, or RouteRandomly), the autopipeliner uses the half-duplex shard flushers, which obey the shard picker. Config().FullDuplex reports the mode that is in effect.
These options tune the mode:
FullDuplexWindow— the maximum number of commands in flight (backpressure).FullDuplexIdleTimeoutandFullDuplexMaxHold— control when the engine returns the held connection to the pool. The pool hooks then do the re-authentication and the maintenance-notification handoffs.FullDuplexFastSubmit— an optional fast submit path for many producers on low-RTT links.
The full-duplex path supports blocking commands, Options.Limiter (one admission for each written batch), per-command hooks, OTel metrics, retry budgets (the client never sends a NoRetry command again after the command is on the wire), and seamless maintenance handoffs. The stream model causes these limits:
- A hook can monitor a command. A hook cannot stop a command. A
ProcessHookthat returns without a call tonextdoes not cancel the command on the full-duplex path. The command is already in the queue of the held connection, and the client sends it. Run policy hooks and kill-switch hooks on a plain client or on the half-duplex autopipeliner. - The order guarantee does not include diverted commands. The commands of one caller keep their order on the shared stream. The engine diverts some commands from the stream: blocking commands, connection-hostile commands, and managed
HIMPORTcommands. A diverted command has no order relation to the stream. On the async face, wait for the result of a diverted command before you submit a command that depends on it. - The fast path does not use the client-side cache. If CSC is enabled, a cacheable command on the full-duplex pipe does not read the cache and does not write to the cache. A subsequent change will correct this.
- The engine records a duration metric for each reply. Some commands fail before they get to the reader: a lease failure, a limiter denial, retry exhaustion, or a close. These commands cause the error callback, but they do not cause an operation-duration sample.
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
AutoPipelineOptions: &redis.AutoPipelineOptions{
FullDuplex: true, // stream commands on one held connection, ~1 RTT each
},
})
defer rdb.Close()
// Deferred face: calls return immediately; result accessors block until executed.
ap, err := rdb.AsyncAutoPipeline()
if err != nil {
panic(err)
}
cmds := make([]*redis.StatusCmd, 0, 1000)
for i := 0; i < 1000; i++ {
cmds = append(cmds, ap.Set(ctx, fmt.Sprintf("key:%d", i), i, 0))
}
for _, cmd := range cmds {
if err := cmd.Err(); err != nil {
// handle error
}
}
// Or the blocking face — a drop-in Cmdable where each call blocks like a
// plain client while concurrent callers share the full-duplex pipe:
// ap, err := rdb.AutoPipeline()
// val, err := ap.Get(ctx, "key").Result()
The example/autopipeline directory contains a runnable tour of all the autopipeliner faces.
Two related defaults changed. AutoPipelineOptions.MaxBatchBytes now has a default of 128 KiB (before, the size had no limit). This default prevents a write/reply deadlock. It is not a throughput control. The buffers of the pipeline pool also have a default of 128 KiB.
Experimental: the auto-pipelining APIs can change in a minor release.
The release adds two functions to the experimental shared-tracking client-side cache. The functions have no effect unless CSC is enabled (#3989) by @ndyakov:
- Refresh-on-invalidate (
Options.ClientSideCacheRefreshOnInvalidate): the client reads recently-read keys again in the background immediately after their invalidation push arrives. The next reader does not pay the cache miss. The client collects invalidated hot keys in a short window and reads them again on a pipelined connection.ClientSideCacheRefreshRecencyWindowsets which entries count as recently read.ClientSideCacheInvalidationBatchWindowcollects invalidation-driven cache deletes into background batches; without it, the connection reader applies each delete inline. - Miss coalescing (
Options.ClientSideCacheCoalesceMisses): the client pipelines concurrent cache-miss reads onto one tracked connection. Each miss keeps the caller's own per-key command. The client does not rewrite the commands toMGET, so the function is safe on a cluster. The client writes each reply to the cache with the tracking generation of the connection. The server can thus invalidate each entry.
These options have the same requirement as the other CSC options: the built-in cache (ClientSideCacheConfig, or ClientSideCache set to a *LocalCache). The client ignores the options if a custom Cache implementation is set.
RouteByLatency selects the node with the strictly minimum latency. The latency measurement has noise: the value is the mean of ten pings, and the client refreshes it at most each 10 s. Thus all clients can select the same node from a set of nodes that have almost equal latency. A production system showed this problem: the GET rates across a five-replica set in one availability zone had a 590x spread.
The new ClusterOptions.RouteByLatencyTolerance widens the selection to each node with a latency in the tolerance above the fastest node. The client distributes reads across these nodes with the round-robin procedure of the ShardPicker. A node in a different availability zone stays outside a sensible tolerance, so zone locality is kept. The default is zero, which keeps the strict-minimum behavior. The option is also on FailoverOptions, where it applies to clients from NewFailoverClusterClient. The plain NewFailoverClient does not support latency routing. (#3973) by @jozenstar
The same work corrected a routing defect. The client recorded the nearest healthy node only when that node was also the fastest node overall. A node that fails fast (a refused connection fails fast, so this is frequent) thus hid each healthy node, and the client sent reads to the node that failed. The client now records the healthy minimum separately. (#3994) by @jozenstar
- Full-duplex auto-pipelining:
AutoPipelineOptions.FullDuplex, withFullDuplexWindow/FullDuplexIdleTimeout/FullDuplexMaxHold/FullDuplexFastSubmit. Available on standalone clients and cluster clients (#4002) by @ndyakov - Dedicated pipeline pool by default:
PipelinePoolSizehas a default ofDefaultPipelinePoolSize(10) on each client. If the pool is full, an operation immediately uses the main pool. Set-1to disable the pool (#4002, #3959) by @ndyakov - CSC refresh-on-invalidate and miss coalescing:
Options.ClientSideCacheRefreshOnInvalidate(withClientSideCacheRefreshRecencyWindow/ClientSideCacheInvalidationBatchWindow) andOptions.ClientSideCacheCoalesceMisses(#3989) by @ndyakov RouteByLatencyTolerance: distributes reads across nodes that have almost equal latency, on cluster clients and onNewFailoverClusterClient(#3973) by @jozenstarAutoPipeliner.WaitClosed: blocks until the drain of the accepted commands completes, and returns the drain result. Use it in a wrapper that must not close shared pools while a flush is in progress (#3998) by @ndyakovCMSInfo.CellSize: contains the cell-size field ofCMS.INFOin Redis 8.12 (#4010) by @elena-kolevska
- Cluster read routing: the client records the nearest healthy node separately from the overall minimum. A node that fails fast does not hide the healthy nodes (#3994) by @jozenstar
- Probabilistic
*.INFOforward compatibility: the parsers forBF.INFO/CF.INFO/CMS.INFO/TOPK.INFO/TDIGEST.INFOskip unknown fields and do not return an error. Redis 8.12 addscell sizetoCMS.INFO(#4010) by @elena-kolevska NewClientpanic leak: a panic during construction (for example, a maintnotifications failure inModeEnabled) does not leak the connection pools that already exist. A typed-nil pool cannot hide the initial panic (#4003) by @ndyakov. The same guards are applied toNewFailoverClient(#4002)- File-descriptor leak on rejected connections:
Conn.Closedoes the socket teardown and the unsubscribe/CSC callbacks when the connection is alreadyCLOSED. Before, init and auth failures collected open descriptors. The transport now closes exactly one time for each socket generation (fixes #3982) (#3985) by @ndyakov - Global logger races: atomics protect the global
LoggerandLogLevel. The call-site attribution is correct again (#3988) by @saddamr3e Conn.onClosedata race: the close hooks that init installs (onCloseandonCscClose) are now atomic against a concurrentClose(#3966) by @saddamr3e- Reply-parser hardening: the reply parsers accept zero-length entry arrays (#3995) by @saddamr3e.
FTHybridCmdreads the full RESP3 map reply and does not desynchronize the connection (#3956) by @saddamr3e CLIENT INFOforward compatibility: the parser skips unknown client-flag characters. The full reply does not fail (#3977) by @ndyakovGEOSEARCHduplicate args: the command does not send duplicate arguments (#3955) by @mehmettokgozMSetEXcluster routing: the constructor sets the first-key position. Typed calls thus go to the correct slot (#3984) by @shivamrustagi- Maintenance notifications: the client does not do the endpoint DNS detection when the mode is disabled (#3969) by @Phalanyx
- Autopipeliner
Close: a concurrentClosedoes not block (no re-entrant deadlock).WaitClosedsupplies the drain result (#3998) by @ndyakov - Buffered-push log noise: the buffered-push-data notice in
isHealthyConnshows only at the debug level. CSC invalidations do not fill the log (#3948) by @ndyakov - Sentinel teardown order: close hooks run in LIFO order. An autopipeliner drain thus completes before the Sentinel discovery stops. A closed failover client cannot create its Sentinel resources again from a late dial (#4002) by @ndyakov
- Pipeline desync containment: if a pre-write push-notification drain fails, or if a command encoder panics, the client removes the connection. The client does not return a desynchronized connection to the pool. This applies to the shared
Pipeline/TxPipelinepath (#4002) by @ndyakov
- Zero-copy scan:
Scangets zero-copy semantics, and the RESP reader does not do unnecessary data conversions (#3972) by @vlady-kotsev - Autopipeline straggler hold: the engine limits the hold on queued commands when the pipeline pool has a free connection. Uncached p95 decreased from 111 ms to 65 ms on a 50 ms link. Real-WAN uncached p99 decreased from 314 ms to 177 ms (#3962) by @ndyakov
- Full-duplex allocations: a ring buffer holds the in-flight queue, and the blocking face uses a pool of batches. Allocations decreased from 770 B/op to 353 B/op at 2048 concurrent callers (#3970, part of #4002) by @ndyakov
- Fast skip gates: the tests do a TCP probe of each address before the
Pinggate. This removes ~1.6 min of dial-retry waits in environments without the full stack (#4001) by @ndyakov - Redis Enterprise coverage: the autopipeline suites connect to the RE database and use the suite DB (#3976, #3975). The timing assertions scale to the measured RTT (#3978). The
CLIENT INFOtracking-flag assertion does not run behind the RE proxy (#3981) by @ndyakov - Security policy: send vulnerability reports to the Redis VDP (#3949) by @ndyakov
We thank all the contributors who worked on this release!
@elena-kolevska, @jozenstar, @mehmettokgoz, @ndyakov, @Phalanyx, @saddamr3e, @shivamrustagi, @vlady-kotsev
Full Changelog: https://github.com/redis/go-redis/compare/v9.22.0...v9.23.0-beta.1
9.22.0
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.
- 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.
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-inCmdablewhere 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 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.
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).
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.
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.
- Client-side caching: server-assisted caching for the standalone client via
ClientSideCacheConfig/ClientSideCache, with theCSCStrategySharedTrackinginvalidation strategy (#3941) by @ofekshenawa - Automatic pipelining:
AutoPipeline()(blocking) andAsyncAutoPipeline()(deferred results) onClientandClusterClient, configured viaAutoPipelineOptions(#3942) by @ndyakov, with help from @cxljs 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
- 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
- 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
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
Full Changelog: https://github.com/redis/go-redis/compare/v9.21.0...v9.22.0
9.22.0-beta.1
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.
- 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.
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.
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).
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.
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.
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
- 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
- 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
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
9.21.0
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.
Two 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.
Redis 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
- Zero-copy buffer string commands: new
GetToBuffer/SetFromBufferonStringCmdableand theZeroCopyStringCmdresult type, reading/writing string values into caller-owned buffers without per-call payload allocation (#3834) by @ndyakov XTrimLimitDisabledsentinel:XTRIM/XADDapproximate trimming can now send an explicitLIMIT 0to disable the trim effort cap, via the newXTrimLimitDisabled = -1sentinel (#3848) by @TheRealMal- PubSub health-check timeouts:
channel.initHealthChecknow bounds thePingit issues with a fresh per-check timeout context (the exportedpingTimeout/reconnectTimeout) instead ofcontext.TODO(), so a stuck health-check Ping can no longer block indefinitely (#3819) by @abdellani - Skip redundant
UNWATCHinTx.Close: a transaction now tracks whether aWATCHis still active (watchArmed) and only issuesUNWATCHonClosewhen it is, removing an extra round trip on the commonWATCH/.../EXECand no-keyWatchpaths while never returning a connection to the pool with an active watch (#3854) by @fcostaoliveira
maintnotificationsModeAutofail-open:ModeAutonow 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 @terrorobe
We'd like to thank all the contributors who worked on this release!
@abdellani, @fcostaoliveira, @ndyakov, @terrorobe, @TheRealMal
9.20.1
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.
- RESP3 push peeking:
PeekPushNotificationNameno 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 @ndyakov FT.HYBRIDvector params: Vector data is now always sent viaPARAMSwith auto-generated param names (__vector_param_N, with collision avoidance) whenVectorParamNameis omitted, since Redis no longer accepts inline vector blobs; theFTHybridOptions.Paramsmap is no longer mutated, so the same options struct can be reused across calls (#3844) by @ndyakovCLUSTER SHARDSforward compatibility: Unknown shard- and node-level attributes in theCLUSTER SHARDSreply are now skipped viaDiscardNext()instead of erroring, so clients keep working when the server introduces new fields (#3843) by @madolson- PubSub double reconnect:
PubSub.releaseConnno 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 @cxljs
We'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
9.20.0
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 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 theArrayCmdableinterface (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.XAUTOCLAIMPEL deletes (#3798) —XAUTOCLAIM/XAUTOCLAIMJUSTIDnow return the list of deleted message IDs from the pending entries list.TS.RANGEmultiple aggregators (#3791) —TS.RANGE/TS.REVRANGE/TS.MRANGE/TS.MREVRANGEaccept multiple aggregators in a single call.Z(UNION|INTER|DIFF)COUNTaggregator (#3802) —COUNTreducer for sorted-set set operations.JSON.SET FPHA(#3797) — newFPHAargument 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.
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.
- RESP3 search parser: First-class RESP3 parsing for
FT.SEARCH/FT.AGGREGATE/FT.INFO/FT.SPELLCHECK/FT.SYNDUMPresponses with backwards compatibility for RESP2 (#3741) by @ndyakov - INCREX: New
INCREXcommand support — atomic increment with expiration (#3816) by @ndyakov - XNACK: Client support for the
XNACKstream command for explicitly negative-acknowledging pending entries (#3790) by @elena-kolevska - TS range multiple aggregators:
TS.RANGE/TS.REVRANGE/TS.MRANGE/TS.MREVRANGEnow accept multiple aggregators in a single call (#3791) by @elena-kolevska XAutoClaimdeleted IDs:XAUTOCLAIM/XAUTOCLAIMJUSTIDnow return the list of deleted message IDs from the PEL (#3798) by @KhukharrJSON.SET FPHA:JSON.SETaccepts a newFPHAargument that specifies the floating-point type for homogeneous floating-point arrays (#3797) by @ndyakov- Sorted-set union/intersection COUNT:
ZUNION/ZINTER/ZDIFFaggregator now supportsCOUNT(#3802) by @ofekshenawa FT.HYBRIDvector validation: Validates hybrid-search vector input types and adds proper typed vector parameters (#3756) by @DengY11- Cluster pool wait stats:
ClusterClient.PoolStats()now accumulatesWaitCountandWaitDurationNsacross all node pools (previously always zero) (#3809) by @LINKIWI
- TLS-only Cluster PubSub:
CLUSTER SLOTSport-0 entries now fall back to the origin endpoint's port, fixingdial tcp <ip>:0: connection refusedon TLS-only clusters started with--port 0 --tls-port <port>(fixes #3726) (#3828) by @ndyakov - Sharded PubSub reconnect routing:
PubSub.conn()now passes both regular (c.channels) and sharded (c.schannels) channels into the per-PubSubnewConnclosure. Previously,ClusterClient.SSubscribe-only PubSubs reconnected to a random node (because the routing closure saw an empty channel list), theSSUBSCRIBEwas sent to the wrong shard, and the resultingMOVEDreply was silently dropped (#3829) by @ndyakov - ClusterClient
Watchretry: User errors returned from aWatchcallback are no longer subjected to cluster-retry classification; transient cluster errors still retry, but a callback returning e.g.net.ErrClosedshort-circuits immediately (#3821) by @obiyang - Sentinel concurrent-probe leak:
MasterAddr's concurrent sentinel probe now closes the non-winning sentinel clients instead of leaking them (#3827) by @cxljs - Sentinel rediscovery loop on master-only setups:
replicaAddrsno 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 @shahyash2609 - Pool
CloseConnhooks:Pool.CloseConnnow triggers registered hooks, fixing a memory leak when connections are closed explicitly rather than via the normal removal path (#3818) by @ndyakov - Dial TCP error redirection: Wrapped
dial tcperrors are now correctly classified as redirectable so cluster routing can recover from a single unreachable node (#3810) by @vladisa88 - Pool
Closehealth checks:ConnPool.Closenow only runs health checks against idle connections, avoiding spurious activity on connections still in use (#3805) by @ndyakov - VLinks return type: Fixed the return type of
VLINKS/VLINKSWITHSCORESvector-set replies (#3820) by @romanpovol
- Flaky tests: Stabilized several flaky tests in the sentinel and pool suites (#3815) by @ndyakov
- Sentinel failover metric race: Fixed a data race in the sentinel failover metric test (#3824) by @cxljs
waitForSentinelClusterStablepost-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 intermittentExpected master to equal slaveflake (#3830) by @ndyakovgovulncheckworkflow: New scheduled GitHub Actions workflow runsgovulncheckon every push, PR, and weekly, surfacing newly disclosed Go vulnerabilities even when no code changes (#3779) by @solardome- CI Redis 8.8-rc1: CI now exercises the 8.8-rc1 Redis image (#3814) by @ofekshenawa
Cmd.Slot()lookup refactor: Caches the per-commandCommandInfoand short-circuits keyless commands before the switch dispatch, removing redundantPeekcalls (#3804) by @retr0-kernel- stdlib
math/rand: Replacedinternal/randwithmath/randfrom the standard library now that the minimum Go version is 1.24 (#3823) by @cxljs - ConnPool queue channel: Removed the unused queue channel from
ConnPool, trimming the pool's footprint (#3826) by @cxljs - Extra packages LICENSE: Added a LICENSE file to each
extra/*package (#3817) by @ndyakov - README & CI image: Documentation refresh and bumped the default CI image tag (#3822) by @ndyakov
We'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
9.19.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 Steps: Step-based pipeline builder for
FT.AGGREGATEwith support for repeated/interleavedLOAD,APPLY,GROUPBY, andSORTBYstages (#3782) by @ndyakov - VectorSet commands: Added
VISMEMBERandWITHATTRIBSsupport (#3753) by @romanpovol - FIPS-safe Script:
NewScriptServerSHAusesSCRIPT LOADto obtain the digest from the server, avoiding client-side SHA-1 (#3700) by @chaitanyabodlapati - Raw RESP access:
DoRawandDoRawWriteTofor raw RESP protocol access (#3713) by @ofekshenawa - Dial retry backoff:
DialerRetryBackofffunction option with constant and exponential helpers (#3706) by @mwhooker - Typed NOSCRIPT error: Redis
NOSCRIPTreplies are now surfaced as a typed error for easier handling (#3738) by @LINKIWI - PubSub ClientSetName: Added
ClientSetNamemethod toPubSub(#3727) by @Flack74 - ReplicaOf: New
ReplicaOfmethod replaces the deprecatedSlaveOf(#3720) by @Copilot - HSCAN BinaryUnmarshaler:
HScannow supports types implementingencoding.BinaryUnmarshaler(#3768) by @Aaditya-dubey1
- Auto hostname type detection: Improved endpoint type detection for maintenance notifications using DNS-based classification; handles empty hosts and expanded private-IP ranges (#3789) by @ndyakov
- HELLO fallback: Don't send
CLIENT MAINT_NOTIFICATIONShandshake whenHELLOfails and connection falls back to RESP2; fail fast when explicitly enabled with RESP3 (#3788) by @ndyakov - Dial TCP retry:
ShouldRetrynow treatsnet.OpErrorwithOp == "dial"timeout errors as safe to retry since no command was sent (#3787) by @vladisa88 - wrappedOnClose leak: Fixed resource leak caused by repeatedly wrapping
baseClientclose logic; replaced with a bounded, concurrency-safe named-hook registry (#3785) by @ndyakov - Pool Close() on stale connections: Suppress close errors (e.g., TLS
closeNotifytimeouts) for connections already dropped by the server due to idle timeout (#3778) by @ofekshenawa - FIFO waiter ordering: Fixed race in
ConnStateMachine.notifyWaitersthat could wake multiple waiters under a single mutex hold and violate FIFO ordering (#3777) by @0x48core - Lua READONLY detection: Detect
READONLYerrors embedded in Lua script error messages on read-only replicas so commands are correctly retried (#3769) by @zhengjilei - VectorScoreSliceCmd RESP2: Fixed
VSimWithScores,VSimWithArgsWithScores, andVLinksWithScoreswhich were broken on RESP2 connections returning flat arrays instead of maps (#3767) by @Copilot - Closed connection handling: Two fixes for closed connection handling in the pool (#3764) by @cxljs
- ZRangeArgs Rev: Fixed
ZRangeArgswithRev+ByScore/ByLexincorrectly swappingStart/Stop, breakingZRANGESTORE(#3751) by @Copilot - OTel metric instrument types: Fixed metric instrument types in
redisotel-native(#3743) by @ofekshenawa - Options.clone() data race: Fixed data race when cloning
Options(#3739) by @rubensayshi - Connection closure metrics: Fixed connection closure metrics and enabled all metric groups by default in
redisotel-native(#3735) by @ofekshenawa - OTel semconv v1.38.0: Use metric definition from
otel/semconv/v1.38.0inredisotel-native(#3731) by @wzy9607 - SETNX semantics: Use
SET ... NXinstead of the deprecatedSETNXcommand (#3723) by @ndyakov - TIME keyless routing: Mark
TIMEas a keyless command for correct cluster routing (#3722) by @fatal10110 - Dial timeout per retry: Dial timeout now applies per attempt instead of across all retry attempts combined (#3705) by @mwhooker
- Cluster metrics attributes: Fixed
pool.namebeing appended per node, which corrupted and dropped user-provided custom attributes (#3699) by @Jesse-Bonfire - initConn nil dereference: Fixed nil pointer dereference and potential deadlock in
*baseClient.initConn(); added explicit nil option guards to client constructors (#3676) by @olde-ducke
- RESP reader: Optimized RESP reader by eliminating intermediate string allocations (#3774) by @Aaditya-dubey1
- Inline rendezvous hashing: Replaced
github.com/dgryski/go-rendezvousdependency with an in-repo implementation ininternal/hashtag, reducing the dependency graph while preserving algorithm parity (#3762) by @bigsk05
- Release automation: Added
repository,ref, andclient-libs-test-image-taginputs to therun-testscomposite action;redis-versionis now optional so unstable builds useREDIS_VERSIONfrom the Makefile (#3749) by @dariaguy - Go 1.24: Updated minimum Go version to 1.24 and use
-compat=1.24in release scripts (#3714, #3754) by @ndyakov, @cxljs
- Pool state machine: Removed redundant
Conn.closedatomic field in favor of the state machine'sStateClosed(#3783) by @cxljs - OTel SDK: Updated OpenTelemetry SDK dependencies in
redisotel/redisotel-native(#3770) by @ndyakov - Go 1.21+ built-ins: Use
maps.Keys,slices.Collect,slices.Contains,clear(), andslices.SortFuncinstead of custom helpers (#3758, #3746) by @cxljs - HGetAll docs: Added Go doc comment to
HGetAlldescribing behavior and complexity (#3776) by @0x48core - Docs links: Fixed irrelevant docs links (#3724) by @olzhas-sabiyev
- Examples cleanup: Removed throughput binary from examples (#3733) by @ndyakov
We'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
9.18.0
Added support for Redis 8.6, including new commands and features for streams idempotent production and HOTKEYS.
note: Pending RS version release
This release introduces comprehensive support for Redis Enterprise Cluster maintenance notifications via SMIGRATING/SMIGRATED push notifications. The client now automatically handles slot migrations by:
- Relaxing timeouts during migration (SMIGRATING) to prevent false failures
- Triggering lazy cluster state reloads upon completion (SMIGRATED)
- Enabling seamless operations during Redis Enterprise maintenance windows
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:
- Command metrics: Operation duration with retry tracking
- Connection basic: Connection count and creation time
- Resiliency: Errors, handoffs, timeout relaxation
- Connection advanced: Wait time and use time
- Pubsub metrics: Published and received messages
- Stream metrics: Processing duration and maintenance notifications
(#3637) by @ofekshenawa
- HOTKEYS Commands: Added support for Redis HOTKEYS feature for identifying hot keys based on CPU consumption and network utilization (#3695) by @ofekshenawa
- Streams Idempotent Production: Added support for Redis 8.6+ Streams Idempotent Production with
ProducerID,IdempotentID,IdempotentAutoinXAddArgsand newXCFGSETcommand (#3693) by @ofekshenawa - NaN Values for TimeSeries: Added support for NaN (Not a Number) values in Redis time series commands (#3687) by @ofekshenawa
- DialerRetries Options: Added
DialerRetriesandDialerRetryTimeouttoClusterOptions,RingOptions, andFailoverOptions(#3686) by @naveenchander30 - ConnMaxLifetimeJitter: Added jitter configuration to distribute connection expiration times and prevent thundering herd (#3666) by @cyningsun
- Digest Helper Functions: Added
DigestStringandDigestByteshelper functions for client-side xxh3 hashing compatible with Redis DIGEST command (#3679) by @ofekshenawa - SMIGRATED New Format: Updated SMIGRATED parser to support new format and remember original host:port (#3697) by @ndyakov
- Cluster State Reload Interval: Added cluster state reload interval option for maintenance notifications (#3663) by @ndyakov
- PubSub nil pointer dereference: Fixed nil pointer dereference in PubSub after
WithTimeout()-pubSubPoolis now properly cloned (#3710) by @Copilot - MaintNotificationsConfig nil check: Guard against nil
MaintNotificationsConfigininitConn(#3707) by @veeceey - wantConnQueue zombie elements: Fixed zombie
wantConnelements accumulation inwantConnQueue(#3680) by @cyningsun - XADD/XTRIM approx flag: Fixed XADD and XTRIM to use
=when approx is false (#3684) by @ndyakov - Sentinel timeout retry: When connection to a sentinel times out, attempt to connect to other sentinels (#3654) by @cxljs
- Fuzz test optimization: Eliminated repeated string conversions, used functional approach for cleaner operation selection (#3692) by @feiguoL
- Pre-allocate capacity: Pre-allocate slice capacity to prevent multiple capacity expansions (#3689) by @feelshu
- Comprehensive TLS tests: Added comprehensive TLS tests and example for standalone, cluster, and certificate authentication (#3681) by @ndyakov
- Redis 8.6: Updated CI to use Redis 8.6-pre (#3685) by @ndyakov
- Deprecation warnings: Added deprecation warnings for commands based on Redis documentation (#3673) by @ndyakov
- Use errors.Join(): Replaced custom error join function with standard library
errors.Join()(#3653) by @cxljs - Use Go 1.21 min/max: Use Go 1.21's built-in min/max functions (#3656) by @cxljs
- Proper formatting: Code formatting improvements (#3670) by @12ya
- Set commands documentation: Added comprehensive documentation to all set command methods (#3642) by @iamamirsalehi
- MaxActiveConns docs: Added default value documentation for
MaxActiveConns(#3674) by @codykaup - README example update: Updated README example (#3657) by @cxljs
- Cluster maintnotif example: Added example application for cluster maintenance notifications (#3651) by @ndyakov
We'd like to thank all the contributors who worked on this release!
@12ya, @Copilot, @codykaup, @cxljs, @cyningsun, @feelshu, @feiguoL, @iamamirsalehi, @naveenchander30, @ndyakov, @ofekshenawa, @veeceey
9.17.3
- Connection Pool: Fixed zombie
wantConnelements accumulation inwantConnQueuethat could cause resource leaks in high concurrency scenarios with dial failures (#3680) by @cyningsun - Stream Commands: Fixed
XADDandXTRIMcommands to use exact threshold (=) whenApproxis false, ensuring precise stream trimming behavior (#3684) by @ndyakov - Connection Pool: Added
ConnMaxLifetimeJitterconfiguration to distribute connection expiration times and prevent the thundering herd problem when many connections expire simultaneously (#3666) by @cyningsun - Client Options: Added
DialerRetriesandDialerRetryTimeoutfields toClusterOptions,RingOptions, andFailoverOptionsto allow configuring connection retry behavior for cluster, ring, and sentinel clients (#3686) by @naveenchander30
We'd like to thank all the contributors who worked on this release!
@cyningsun, @naveenchander30, and @ndyakov
Full Changelog: https://github.com/redis/go-redis/compare/v9.17.2...v9.17.3