Skip to content

fix(deps): update all non-major dependencies - #29

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/all-minor-patch
Open

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/all-minor-patch

Conversation

@renovate

@renovate renovate Bot commented Feb 1, 2026 •

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence Type Update
github.com/redis/go-redis/v9 v9.17.1 → v9.23.0 age confidence require minor
github.com/redis/rueidis v1.0.68 → v1.0.78 age confidence require patch
github.com/samber/lo v1.52.0 → v1.53.0 age confidence require minor
github.com/sirupsen/logrus v1.9.3 → v1.10.2 age confidence require minor
github.com/stretchr/testify v1.11.1 → v1.12.1 age confidence require minor
golang 1.25.4 → 1.27.2 age confidence minor
golang.org/x/net v0.47.0 → v0.61.0 age confidence require minor
golangci/golangci-lint-action v9.2.0 → v9.3.0 age confidence action minor

Release Notes

redis/go-redis (github.com/redis/go-redis/v9)

v9.23.0: 9.23.0

Compare Source

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.

⚠️ Changes to check when you upgrade from 9.22.0:

  • Go 1.26 is now the minimum Go version (#​4060). The go directive of the root module and of the submodules moved from 1.24 to 1.26, as part of the dependency bumps for govulncheck findings. Projects that build with Go 1.24 or 1.25 must upgrade the toolchain.
  • Each Client has 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. Set PipelinePoolSize: -1 to get the previous single-pool behavior.
  • AutoPipelineOptions.MaxBatchBytes has 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.

⚠️ Changes to experimental APIs since 9.23.0-beta.1 (no effect if you upgrade from 9.22.0):

  • AutoPipelineOptions.FullDuplexFastSubmit is 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.ClientSideCacheRefreshRecencyWindow is 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.
  • ClientSideCacheInvalidationBatchWindow served 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.

🚀 Highlights

Full-Duplex Auto-Pipelining (Experimental)

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).
  • FullDuplexIdleTimeout and FullDuplexMaxHold — 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. With MaxFlushDelay=250µs at 1024 callers: +22% ops/s and −26% p99. The default is 0, so this is opt-in.
  • NumShards — on a standalone Client, 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 ProcessHook that returns without calling next does 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 HIMPORT commands. 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

Client-Side Caching: Refresh-on-Invalidate, Miss Coalescing, Faster Reads

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. ClientSideCacheInvalidationBatchWindow collects 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 to MGET), 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.

Better Distribution for Latency-Based Cluster Read Routing

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

EphemeralConn for Session-Scoped Work

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

✨ New Features

  • Full-duplex auto-pipelining: AutoPipelineOptions.FullDuplex, with FullDuplexWindow / FullDuplexIdleTimeout / FullDuplexMaxHold, on standalone and cluster clients (#​4002) by @​ndyakov
  • Pipeline() on the full-duplex connection: ap.Pipeline() and FDPipelined submit a batch to the engine as one contiguous run, on a standalone Client (#​4021) by @​ndyakov
  • Several full-duplex engines on one client: NumShards > 1 with FullDuplex on a standalone Client, 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 with ErrAutoPipelineQueueFull, so memory does not grow without limit when the server is slow. With FullDuplex, the ordered stream is bounded by FullDuplexWindow instead (a full window blocks the submitter, it does not reject), and MaxQueuedCommands limits only the commands that run outside the stream (blocking, connection-hostile, Do). The default 0 means no limit (#​4070) by @​ndyakov
  • Dedicated pipeline pool by default: PipelinePoolSize has a default of DefaultPipelinePoolSize (10) on each client. If the pool is full, an operation uses the main pool. Set -1 to disable the pool (#​4002, #​3959) by @​ndyakov
  • CSC refresh-on-invalidate and miss coalescing: Options.ClientSideCacheRefreshOnInvalidate (with ClientSideCacheInvalidationBatchWindow) and Options.ClientSideCacheCoalesceMisses (#​3989) by @​ndyakov
  • Client.EphemeralConn: a sticky connection that is removed from the pool on Close, for session-scoped AUTH / SELECT work (#​4039) by @​saddamr3e
  • RouteByLatencyTolerance: distributes reads across nodes with almost equal latency, on cluster clients and on NewFailoverClusterClient (#​3973) by @​jozenstar
  • AutoPipeliner.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 @​ndyakov
  • redisotel-native WithRecordNilErrors: opt in to count redis.Nil replies as client errors (see the fix below) (#​4025) by @​lazerg
  • CMSInfo.CellSize: the cell-size field of CMS.INFO in Redis 8.12 (#​4010) by @​elena-kolevska

🐛 Bug Fixes

  • CSC invalidation batching: ClientSideCacheInvalidationBatchWindow applied 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: Put did not wake a worker that waited for a connection to become IDLE, so a streaming-credentials re-auth could stall until PoolTimeout and 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 to UNUSABLE for 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 a Ring they went to the wrong shard; on a cluster they cost an extra MOVED round trip. A raw FCALL / FCALL_RO hashed the function name. All now route by their key (#​4048) by @​ndyakov. Raw XGROUP, ZUNION / ZINTER / ZDIFF, SDIFFCARD / SUNIONCARD, TS.NRANGE / TS.NREVRANGE, and HIMPORT SET forms are also fixed (#​4022)
  • Millisecond expirations: PExpireAt, HPExpireAt, and HPExpireAtWithArgs overflowed for dates outside the nanosecond range (for example, year 2270), so Redis deleted the key or rejected the command (#​4026) by @​jakezwang. SetArgs.ExpireAt now sends PXAT when the deadline has millisecond precision; before, EXAT dropped the milliseconds (#​4053) by @​LindseyZ1205
  • Cluster -1 timeouts: a -1 read/write timeout on ClusterOptions gave node clients the 5 s default instead of no timeout (fixes #​4049) (#​4050) by @​lazerg
  • OTel redis.Nil as error: a cache miss (redis.Nil) incremented redis.client.errors and was tagged as a server error on db.client.operation.duration. Nil replies are now classified NIL and not counted, unless WithRecordNilErrors(true) is set (fixes #​4024) (#​4025) by @​lazerg
  • Credential redaction: SENTINEL SET ... auth-pass is now redacted in command rendering (used by redisotel / rediscensus span attributes), and a MIGRATE password with the literal value auth / auth2 is redacted at the correct position (#​4011) by @​saddamr3e
  • Min/max aggregator panic: a non-numeric shard reply made AggMinAggregator / AggMaxAggregator panic instead of returning an error from Result() (#​4056) by @​Suselz
  • ParseURL skip_verify: an invalid value (for example, skip_verify=yes) was silently treated as false. ParseURL and ParseFailoverURL now return an error, like for the other boolean options (#​4064) by @​orinnz
  • uintptr arguments: uintptr and *uintptr are now encoded like the other unsigned integers; a nil *uintptr is encoded as 0 (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 *.INFO forward compatibility: the BF.INFO / CF.INFO / CMS.INFO / TOPK.INFO / TDIGEST.INFO parsers skip unknown fields instead of returning an error. Redis 8.12 adds cell size to CMS.INFO (#​4010) by @​elena-kolevska
  • NewClient panic leak: a panic during construction (for example, a maintnotifications failure in ModeEnabled) 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 to NewFailoverClient (#​4002)
  • File-descriptor leak on rejected connections: Conn.Close does the socket teardown and the unsubscribe/CSC callbacks when the connection is already CLOSED. Before, init and auth failures leaked open descriptors (fixes #​3982) (#​3985) by @​ndyakov
  • Global logger races: atomics protect the global Logger and LogLevel, and call-site attribution is correct again (#​3988) by @​saddamr3e
  • Conn.onClose data race: the close hooks that init installs (onClose and onCscClose) are atomic against a concurrent Close (#​3966) by @​saddamr3e
  • Reply-parser hardening: the reply parsers accept zero-length entry arrays (#​3995), and FTHybridCmd reads the full RESP3 map reply without desynchronizing the connection (#​3956) by @​saddamr3e
  • CLIENT INFO forward compatibility: the parser skips unknown client-flag characters instead of failing the reply (#​3977) by @​ndyakov
  • GEOSEARCH duplicate args: the command no longer sends duplicate arguments (#​3955) by @​mehmettokgoz
  • MSetEX cluster 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 concurrent Close does not block (no re-entrant deadlock), and WaitClosed returns the drain result (#​3998) by @​ndyakov
  • Buffered-push log noise: the buffered-push-data notice in isHealthyConn is 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 / TxPipeline path removes the connection instead of returning a desynchronized connection to the pool (#​4002) by @​ndyakov

⚡ Performance

  • 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: Scan gets 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

🧪 Testing & Infrastructure

  • Go 1.26 and govulncheck dependency bumps: the minimum Go version is 1.26, CI defaults to 1.26, and a new go-versions job builds the test matrix from go.mod plus oldstable / stable (#​4060) by @​vlady-kotsev
  • Fast skip gates: tests do a TCP probe of each address before the Ping gate, 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 INFO tracking-flag assertion is skipped behind the RE proxy (#​3981) by @​ndyakov
  • Docs: UniversalOptions documents ReadTimeout / WriteTimeout / ContextTimeoutEnabled (#​4047) by @​lazerg; the README describes DisableIdentity as client identification (CLIENT SETINFO), not identity verification (#​4059) by @​FanWu-ai
  • Security policy: send vulnerability reports to the Redis VDP (#​3949) by @​ndyakov

👥 Contributors

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: redis/go-redis@v9.22.0...v9.23.0

v9.22.0: 9.22.0

Compare Source

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:

  • 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.
  • 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.

🚀 Highlights

Client-Side Caching (Experimental)

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

Automatic Pipelining (Experimental)

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

Redis 8.10 Support

This release adds support for Redis 8.10. The README's supported-versions list now includes Redis 8.10, and CI runs the full suite against the redislabs/client-libs-test:8.10.0 image by default (#​3920, #​3940).

Coverage for the new commands and options that ship with Redis 8.10:

  • HIMPORT (#​3919) — bulk hash import via server-side fieldsets, exposed 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.
Cross-SDK Aligned Defaults

Default configuration values now follow the cross-SDK configuration proposal shared by all Redis client libraries (#​3918):

Setting Old default New default
ReadTimeout / WriteTimeout 3s 5s
Retry backoff (min/max) 8ms / 512ms 10ms / 1s
Cluster state reload interval 10s 60s
TCP keep-alive 5min period 30s idle / 5s interval / 3 probes (net.KeepAliveConfig)

Applications that set these values explicitly are unaffected; applications relying on the old defaults inherit the new ones.

Data-Race and Parser Hardening Sweep

A systematic audit fixed data races across the client — hooks (AddHook, #​3868), Ring.SetAddrs (#​3862), cluster node slices (#​3861), pub/sub reconnect (#​3906), maintenance notifications (#​3894, #​3872), pool handoff (#​3876), and redisotel (#​3881) — and hardened the RESP parsers against malformed or unexpected replies: over-reads on nil replies (#​3874), integer overflow when skipping map/attribute bodies (#​3877), unhashable RESP3 map keys (#​3873), odd-length flat replies (#​3900), mismatched declared array lengths (#​3907), unexpected extra reply frames (#​3884), and nil elements in numeric/bool slice replies (#​3922).

PubSub Receive Hang Fix

PeekPushNotificationName blocked until 36 bytes were buffered, so a short subscribe confirmation (channel name of six or fewer characters) on an otherwise idle connection hung PubSub.Receive forever — a regression introduced in 9.20.1 by #​3842. The peek now parses whatever is already buffered and only waits for one more byte when the frame prefix is valid but incomplete. Fixes #​3935.

(#​3936) by @​ndyakov

Correct Cluster Transaction Retries

The cluster transaction pipeline treated a MULTI...EXEC block as independently retryable commands, which could scatter a transaction across nodes or send malformed transactions on retry. Redirects (MOVED/ASK/TRYAGAIN) and aborts are now handled at the whole-transaction level, matching Redis transaction semantics: the transaction is re-routed and retried as a unit, never partially (#​3909) by @​cxljs.

Credential Redaction in Command Tracing

rediscmd.AppendCmd — used by redisotel and rediscensus to render commands into span attributes — now redacts credential arguments as <redacted>: AUTH, HELLO ... AUTH, CONFIG SET of requirepass / masterauth / TLS key passphrases, ACL SETUSER password rules, and MIGRATE ... AUTH/AUTH2. The client sends HELLO ... AUTH on every handshake and AUTH on every streaming-credentials rotation through the regular hook chain, so tracing hooks previously captured credentials even when the application never issued an auth command itself (#​3939) by @​saddamr3e.

✨ New Features

  • Client-side caching: server-assisted caching for the standalone client via ClientSideCacheConfig / ClientSideCache, with the CSCStrategySharedTracking invalidation strategy (#​3941) by @​ofekshenawa
  • Automatic pipelining: AutoPipeline() (blocking) and AsyncAutoPipeline() (deferred results) on Client and ClusterClient, configured via AutoPipelineOptions (#​3942) by @​ndyakov, with help from @​cxljs
  • HIMPORT command family: HImportPrepare / HImportSet / HImportDiscard / HImportDiscardAll with lazy per-connection fieldset prepare replay (#​3919) by @​ndyakov
  • LMOVEM / BLMOVEM: move multiple list elements in one call, with COUNT (up to N) or EXACTLY (all-or-nothing) semantics via LMoveMArgs (#​3913) by @​ofekshenawa
  • SUnionCard / SDiffCard: cardinality of set union/difference (#​3897) by @​ofekshenawa
  • XRead / XReadGroup MAXCOUNT / MAXSIZE: bound stream read responses by entry count or payload size (#​3898) by @​ofekshenawa
  • TS.READ: read samples from a series starting at a given timestamp, with TSReadEarliest (-), TSReadLatest (+), and TSReadNew ($) sentinels (#​3896) by @​ofekshenawa
  • TS.QUERYLABELS: query label names/values across time series (#​3926) by @​ndyakov
  • TS.NRANGE / TS.NREVRANGE: range queries across multiple series (#​3870) by @​ofekshenawa, with multiple aggregators per key (#​3937) by @​ndyakov
  • TS.MRANGE / TS.MREVRANGE EXCLUDEEMPTY: skip series with no samples in the result (#​3912) by @​ofekshenawa
  • FT.ALIASLIST: list all index aliases (#​3925) by @​ndyakov
  • FT.AGGREGATE COLLECT reducer: collect grouped values into an array (#​3886) by @​ndyakov
  • FT.CREATE RERANK: RERANK parameter on HNSW vector field definitions (#​3927) by @​ofekshenawa
  • FT.HYBRID timeout warnings: timeout warnings are now populated in hybrid search results (#​3911) by @​ofekshenawa
  • FT.HYBRID KNN SHARD_K_RATIO (Redis 8.8+): per-shard K ratio for KNN clauses (#​3841) by @​ndyakov

🐛 Bug Fixes

  • PubSub Receive 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 @​ndyakov
  • Cluster transactions: re-route the whole tx pipeline on redirect/abort instead of per-command (#​3909) by @​cxljs
  • Credential leak in traces: rediscmd.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 @​saddamr3e
  • WaitAOF return type: returns *IntSliceCmd matching the two-integer WAITAOF reply (#​3888) by @​CipherN9
  • Ring.Publish routing: publish to the shard that owns the topic instead of a round-robined one (#​3893) by @​dkindel
  • Pool OnRemove hooks: fire OnRemove on putConn eviction paths so removal hooks see every evicted connection (#​3932) by @​cxljs
  • UniversalClient InfoMap: added InfoMap to the Cmdable interface (#​3904) by @​nazarli-shabnam
  • SlowLogGet context: pass the caller's context instead of a background one (#​3915) by @​sonnemusk
  • ModuleLoadex nil config: return an error instead of panicking on nil config (#​3916) by @​sonnemusk
  • ParseURL IPv6 hosts: keep single brackets for IPv6 hosts without a port (#​3882) by @​sueun-dev
  • ParseURL durations: treat unit durations <= 0 as disabled (#​3866) by @​sueun-dev
  • Nil *uint8 encoding: encode nil *uint8 as "0" like other numeric pointers (#​3869) by @​sueun-dev
  • JSONSliceCmd read errors: return the read error from readReply instead 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), 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 @​ndyakov
  • redisotel: use ObservableCounter for cumulative pool stats (#​3914) by @​Solaris-star; avoid a data race on shared attributes during MinIdleConns warmup (#​3881) by @​ndyakov

🧰 Maintenance

❗ Important

✂ PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • Between 12:00 AM and 03:59 AM, on day 1 of the month (* 0-3 1 * *)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot enabled auto-merge (squash) February 1, 2026 02:16
@renovate

renovate Bot commented Feb 1, 2026 •

Copy link
Copy Markdown
Contributor Author

ℹ️ Artifact update notice

File name: go.mod

In order to perform the update(s) described in the table above, Renovate ran the go get command, which resulted in the following additional change(s):

  • 2 additional dependencies were updated
  • The go directive was updated for compatibility reasons

Details:

Package Change
go 1.24.0 -> 1.26.0
golang.org/x/sys v0.38.0 -> v0.49.0
golang.org/x/text v0.31.0 -> v0.43.0

@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 3 times, most recently from b956a3a to 5989b17 Compare February 10, 2026 21:13
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 2 times, most recently from 2ce1310 to 222bd2a Compare February 17, 2026 06:03
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 4 times, most recently from db57d39 to 17467d4 Compare March 2, 2026 17:01
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 2 times, most recently from cfff61d to 5fff2ee Compare March 12, 2026 01:54
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch from 5fff2ee to de00e4b Compare March 16, 2026 09:26
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 3 times, most recently from 07ae4dd to e333938 Compare April 12, 2026 01:18
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch from e333938 to 96f344f Compare April 28, 2026 15:35
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 3 times, most recently from 6378906 to 987c87a Compare May 12, 2026 17:26
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 2 times, most recently from e43494b to 41d6ec7 Compare May 28, 2026 10:33
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch from 41d6ec7 to 57ffd72 Compare July 26, 2026 01:55
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 5 times, most recently from b448393 to b06275c Compare August 10, 2026 08:33
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch from b06275c to 7d7ddfe Compare August 12, 2026 19:37
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 4 times, most recently from bd3207e to 9fa7b73 Compare August 24, 2026 00:43
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 16 times, most recently from 7b4dc6f to 6443906 Compare September 1, 2026 20:04
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 5 times, most recently from 801626b to 920bc19 Compare September 8, 2026 22:39
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch from 920bc19 to 2aecaed Compare September 15, 2026 16:01
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 2 times, most recently from 9af8721 to b26ca17 Compare October 8, 2026 18:53
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch from b26ca17 to 8ce594e Compare October 10, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants