From 396986f9c957db580a595f4041af8bb67b805d48 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 10 Jul 2026 15:03:43 +0100 Subject: [PATCH 1/4] DOC-6831 Document go-redis client-side caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a "Connect using client-side caching" section to the go-redis connect page covering the multi-strategy CSC being added in redis/go-redis#3851: enabling via ClientSideCacheConfig on a RESP3 client, the three ClientSideCacheStrategy options (SharedTracking default, Broadcast, PerConnection), the tuning options (MaxEntries, MaxMemoryBytes, DrainInterval, MaxStaleness), and monitoring via CSCStats plus the process-wide stats functions. Register go-redis in the support table and relatedPages of the CSC introduction page. Written against the unmerged upstream PR, so the section carries a pre-release warning and the version is a TBD placeholder. Parked pending the upstream merge and release. Experience: Rejected /park's page-level bannerText in favour of a section-scoped {{< note >}} warning — the CSC section lives in an otherwise-released Connect page, so a page banner would wrongly flag basic/TLS/cluster/SCH as unreleased. Don't "correct" the missing bannerText on pickup; the section-scoped warning is intentional. Recheck: fill go-redis version once #3851 merges and is tagged (connect.md note + client-side-caching.md support table both use a "v9.TBD" placeholder) Recheck: confirm final exported names against merged source — ClientSideCacheConfig, ClientSideCacheStrategy, CSCStrategy{SharedTracking,Broadcast,PerConnection}, ClientSideCacheConfig fields, (*Client).CSCStats, redis.CommandStats/CacheAdmissionRejects/RESPInvalidationBytesRead Recheck: verify DrainInterval default (5ms) / floor (1ms) and MaxEntries default (10000) survive to release Recheck: base of #3851 is the feature branch csc-standalone-connection-support, not master — confirm the whole CSC stack lands on master before treating the trigger as fired Co-Authored-By: Claude Opus 4.8 (1M context) --- .../develop/clients/client-side-caching.md | 2 + content/develop/clients/go/connect.md | 113 ++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/content/develop/clients/client-side-caching.md b/content/develop/clients/client-side-caching.md index 3fe8490031..8f625bf85b 100644 --- a/content/develop/clients/client-side-caching.md +++ b/content/develop/clients/client-side-caching.md @@ -20,6 +20,7 @@ relatedPages: - /develop/clients/redis-py/connect#connect-using-client-side-caching - /develop/clients/nodejs/connect#connect-using-client-side-caching - /develop/clients/jedis/connect#connect-using-client-side-caching +- /develop/clients/go/connect#connect-using-client-side-caching topics: - client-side-caching - performance @@ -92,6 +93,7 @@ The following client libraries support CSC from the stated version onwards: | [`redis-py`]({{< relref "/develop/clients/redis-py/connect#connect-using-client-side-caching" >}}) | v5.1.0 | | [`Jedis`]({{< relref "/develop/clients/jedis/connect#connect-using-client-side-caching" >}}) | v5.2.0 | | [`node-redis`]({{< relref "/develop/clients/nodejs/connect#connect-using-client-side-caching" >}}) | v5.1.0 | +| [`go-redis`]({{< relref "/develop/clients/go/connect#connect-using-client-side-caching" >}}) | v9.TBD | Note that some other clients support the [`CLIENT TRACKING`]({{< relref "/commands/client-tracking" >}}) command to configure CSC on the server, but this does not mean they support the features required for CSC themselves. diff --git a/content/develop/clients/go/connect.md b/content/develop/clients/go/connect.md index bafc743e94..2337e80875 100644 --- a/content/develop/clients/go/connect.md +++ b/content/develop/clients/go/connect.md @@ -183,3 +183,116 @@ either [AWS PrivateLink]({{< relref "/operate/rc/security/aws-privatelink" >}}) To use relaxed timeouts with these services, you should set `EndpointType: maintnotifications.EndpointTypeNone` when you connect. All other configurations have full support for both relaxed timeouts and pre-handoffs. {{< /note >}} + +## Connect using client-side caching + +Client-side caching is a technique to reduce network traffic between +the client and server, resulting in better performance. See +[Client-side caching introduction]({{< relref "/develop/clients/client-side-caching" >}}) +for more information about how client-side caching works and how to use it effectively. + +{{< note >}}This feature is not yet released and its API is subject to change. +It is being added in [go-redis PR #3851](https://github.com/redis/go-redis/pull/3851). + +Client-side caching requires go-redis v9.TBD or later. +To maximize compatibility with all Redis products, client-side caching +is supported by Redis v7.4 or later. + +Client-side caching requires the [RESP3]({{< relref "/develop/reference/protocol-spec#resp-versions" >}}) +protocol, so you must set `Protocol: 3` explicitly when you connect. On a RESP2 +connection, client-side caching silently does nothing. It also works on logical +database 0 only; on any other database it is disabled with a log warning. +{{< /note >}} + +To enable client-side caching, pass a `ClientSideCacheConfig` object when you +connect on a `Protocol: 3` client. Passing an empty `ClientSideCacheConfig{}` +enables caching with the default settings: + +```go +import ( + "context" + "fmt" + "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + + client := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Protocol: 3, // RESP3 required for client-side caching + ClientSideCacheConfig: &redis.ClientSideCacheConfig{}, + }) + + client.Set(ctx, "city", "New York", 0) + client.Get(ctx, "city") // Retrieved from the server and cached + client.Get(ctx, "city") // Retrieved from the cache +} +``` + +You can see the cache working if you connect to the same Redis database +with [`redis-cli`]({{< relref "/develop/tools/cli" >}}) and run the +[`MONITOR`]({{< relref "/commands/monitor" >}}) command. With caching enabled, +the server sees the first `Get("city")` call but not the second, which the +client satisfies from the cache. + +### Caching strategies + +go-redis supports three client-side caching strategies, selected with the +`ClientSideCacheStrategy` option. All three share the same cache interface, +the same cacheable-command allow-list, and the same RESP3 and database-0 +requirements; they differ in how invalidation messages reach the cache. + +| Strategy | Cache | Tracking | Best for | +| :-- | :-- | :-- | :-- | +| `CSCStrategySharedTracking` (default) | One shared, sharded cache | Every pool connection issues `CLIENT TRACKING ON`; a background drainer applies invalidations | General use. Works wherever RESP3 does (including managed or proxied environments) and needs no extra connection. | +| `CSCStrategyBroadcast` | One shared, sharded cache | A dedicated out-of-pool "sidecar" connection issues `CLIENT TRACKING ON BCAST` and owns all invalidation traffic | Highest throughput and lowest tail latency, where broadcasting mode is available. Uses one extra connection and receives invalidations for every write in the database. | +| `CSCStrategyPerConnection` | One private cache per pool connection | Every pool connection issues `CLIENT TRACKING ON` and owns its own cache | Small, long-lived pools (≲10 connections) that want hard isolation between connections. Cache memory multiplies by pool size, so avoid it at high concurrency. | + +If you don't set `ClientSideCacheStrategy`, the zero value +`CSCStrategySharedTracking` is used. The example below opts into broadcasting +mode instead: + +```go +client := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Protocol: 3, + ClientSideCacheConfig: &redis.ClientSideCacheConfig{}, + ClientSideCacheStrategy: redis.CSCStrategyBroadcast, +}) +``` + +### Configuration options + +The `ClientSideCacheConfig` object accepts the following options to tune the +cache: + +| Name | Description | +| :-- | :-- | +| `MaxEntries` | The maximum number of entries the cache can hold. Zero or negative means unlimited. If both `MaxEntries` and `MaxMemoryBytes` are unlimited, `MaxEntries` defaults to 10,000 so the cache cannot grow without bound. | +| `MaxMemoryBytes` | An approximate memory limit for the cache. Zero means unlimited. | +| `DrainInterval` | (`CSCStrategySharedTracking` only) How often the background drainer scans idle connections and applies buffered invalidations to the shared cache. The default is 5ms and the minimum is 1ms. | +| `MaxStaleness` | The hard upper bound on how long a cached entry can be served after the underlying data has changed. Zero means no explicit bound (the drain interval still applies). | + +### Monitoring the cache + +Use the `CSCStats()` method to read the cumulative cache hit and miss counts +for a client: + +```go +hits, misses := client.CSCStats() +fmt.Printf("Cache hits: %d, misses: %d\n", hits, misses) +``` + +Process-wide totals are also available via the package-level functions +`redis.CommandStats()` (served-command hits and misses), +`redis.CacheAdmissionRejects()` (entries rejected on admission), and +`redis.RESPInvalidationBytesRead()` (bytes of invalidation key names read). + +{{< note >}}To supply your own cache implementation, set the `ClientSideCache` +option instead of `ClientSideCacheConfig`. An explicit `ClientSideCache` is +honoured by the `CSCStrategySharedTracking` and `CSCStrategyBroadcast` +strategies. `CSCStrategyPerConnection` always builds a private cache per +connection from `ClientSideCacheConfig` and ignores an explicit +`ClientSideCache` (with a log warning). +{{< /note >}} From 8df32d10145c83d2c301b70a8a446c07fc3593cb Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Tue, 4 Aug 2026 13:38:40 +0100 Subject: [PATCH 2/4] DOC-6831 Reconcile go-redis client-side caching with v9.22.0 Unpark reconciliation. go-redis v9.22.0 shipped CSC on 2026-08-03, so the version placeholders become v9.22.0 and the "not yet released" warning goes. The park snapshot was tagged LOW confidence and earned it. The upstream PR the page was written against, #3851, was superseded by #3941, and the shape that actually shipped is materially smaller than the one predicted. Three things the page documented do not exist in the release: the CSCStrategyBroadcast and CSCStrategyPerConnection strategies were never implemented (only CSCStrategySharedTracking exists, and Options.init clamps anything else to it with a log warning), CSCStats returns a struct rather than two uint64s, and none of the three package-level stats functions were ever exported. So the strategy table, its worked example, and the per-strategy cache note were all describing an API that no user could call. I proved this rather than eyeballing it by building the documented calls against the released module: the new code compiles, the old code fails with three errors that map exactly to the three findings. Because there is now one strategy and no choice to make, the strategy section became a plain explanation of how invalidation reaches the cache. That keeps the genuinely useful part -- invalidation is asynchronous, which is a correctness fact a reader needs -- while documenting an option with a single legal value would have been noise. The snapshot also under-recorded the restrictions, which is the more dangerous direction to be wrong in. Enabling CSC makes go-redis reject SELECT, AUTH, HELLO with arguments, RESET, CLIENT TRACKING and the raw subscribe commands, and one rejected command fails its whole pipeline; a credentials provider silently disables caching. Those can break working code, so they now have their own section instead of being absent. Learned: a merged-and-tagged upstream PR is not the PR the docs were written against; verify the released module by compiling against it, not by reading the diff that was parked Constraint: only CSCStrategySharedTracking exists in go-redis v9.22.0 -- do not document Broadcast or PerConnection strategies unless a release adds them Constraint: CSCStats() returns a CSCStats struct (Hits, Misses, Entries, MemoryUsageBytes), not a two-value (hits, misses) tuple Rejected: documenting ClientSideCacheStrategy as a tunable option | it has exactly one legal value and unknown values are clamped, so presenting it as a choice misleads Directive: go-redis CSC is Experimental and may change in a minor release; re-verify the exported names against the released module on any go-redis version bump touching this page Gaps: the enable and monitoring snippets are compile-verified against v9.22.0 but not run against a live server, so the MONITOR-based cache-hit demonstration is reasoned from the upstream example rather than observed Recheck: when go-redis adds a second CSC strategy or promotes the feature out of Experimental Ticket: DOC-6831 --- .../develop/clients/client-side-caching.md | 2 +- content/develop/clients/go/connect.md | 91 +++++++++---------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/content/develop/clients/client-side-caching.md b/content/develop/clients/client-side-caching.md index 8f625bf85b..946a2fd799 100644 --- a/content/develop/clients/client-side-caching.md +++ b/content/develop/clients/client-side-caching.md @@ -93,7 +93,7 @@ The following client libraries support CSC from the stated version onwards: | [`redis-py`]({{< relref "/develop/clients/redis-py/connect#connect-using-client-side-caching" >}}) | v5.1.0 | | [`Jedis`]({{< relref "/develop/clients/jedis/connect#connect-using-client-side-caching" >}}) | v5.2.0 | | [`node-redis`]({{< relref "/develop/clients/nodejs/connect#connect-using-client-side-caching" >}}) | v5.1.0 | -| [`go-redis`]({{< relref "/develop/clients/go/connect#connect-using-client-side-caching" >}}) | v9.TBD | +| [`go-redis`]({{< relref "/develop/clients/go/connect#connect-using-client-side-caching" >}}) | v9.22.0 | Note that some other clients support the [`CLIENT TRACKING`]({{< relref "/commands/client-tracking" >}}) command to configure CSC on the server, but this does not mean they support the features required for CSC themselves. diff --git a/content/develop/clients/go/connect.md b/content/develop/clients/go/connect.md index 2337e80875..0bf578e259 100644 --- a/content/develop/clients/go/connect.md +++ b/content/develop/clients/go/connect.md @@ -191,17 +191,18 @@ the client and server, resulting in better performance. See [Client-side caching introduction]({{< relref "/develop/clients/client-side-caching" >}}) for more information about how client-side caching works and how to use it effectively. -{{< note >}}This feature is not yet released and its API is subject to change. -It is being added in [go-redis PR #3851](https://github.com/redis/go-redis/pull/3851). +{{< note >}}Client-side caching is an experimental feature of go-redis and its +API may change in a minor release. -Client-side caching requires go-redis v9.TBD or later. +Client-side caching requires go-redis v9.22.0 or later. To maximize compatibility with all Redis products, client-side caching is supported by Redis v7.4 or later. Client-side caching requires the [RESP3]({{< relref "/develop/reference/protocol-spec#resp-versions" >}}) protocol, so you must set `Protocol: 3` explicitly when you connect. On a RESP2 -connection, client-side caching silently does nothing. It also works on logical -database 0 only; on any other database it is disabled with a log warning. +connection, client-side caching silently does nothing. It is also limited to +standalone clients and to logical database 0; on any other database it is +disabled with a log warning. {{< /note >}} To enable client-side caching, pass a `ClientSideCacheConfig` object when you @@ -211,7 +212,6 @@ enables caching with the default settings: ```go import ( "context" - "fmt" "github.com/redis/go-redis/v9" ) @@ -236,31 +236,22 @@ with [`redis-cli`]({{< relref "/develop/tools/cli" >}}) and run the the server sees the first `Get("city")` call but not the second, which the client satisfies from the cache. -### Caching strategies +### How invalidation works -go-redis supports three client-side caching strategies, selected with the -`ClientSideCacheStrategy` option. All three share the same cache interface, -the same cacheable-command allow-list, and the same RESP3 and database-0 -requirements; they differ in how invalidation messages reach the cache. +Redis tracks the keys each connection reads and sends an invalidation message +when one of them changes. go-redis keeps a single cache shared by every +connection in the client's pool: each connection enables tracking on the +server, and go-redis applies the invalidation messages in the background. -| Strategy | Cache | Tracking | Best for | -| :-- | :-- | :-- | :-- | -| `CSCStrategySharedTracking` (default) | One shared, sharded cache | Every pool connection issues `CLIENT TRACKING ON`; a background drainer applies invalidations | General use. Works wherever RESP3 does (including managed or proxied environments) and needs no extra connection. | -| `CSCStrategyBroadcast` | One shared, sharded cache | A dedicated out-of-pool "sidecar" connection issues `CLIENT TRACKING ON BCAST` and owns all invalidation traffic | Highest throughput and lowest tail latency, where broadcasting mode is available. Uses one extra connection and receives invalidations for every write in the database. | -| `CSCStrategyPerConnection` | One private cache per pool connection | Every pool connection issues `CLIENT TRACKING ON` and owns its own cache | Small, long-lived pools (≲10 connections) that want hard isolation between connections. Cache memory multiplies by pool size, so avoid it at high concurrency. | +Because invalidation is asynchronous, an entry is evicted shortly after the +data changes rather than at the instant of the write. Use `DrainInterval` to +control how often invalidations are applied, and `MaxStaleness` to put a hard +time limit on how long any entry can be served. -If you don't set `ClientSideCacheStrategy`, the zero value -`CSCStrategySharedTracking` is used. The example below opts into broadcasting -mode instead: - -```go -client := redis.NewClient(&redis.Options{ - Addr: "localhost:6379", - Protocol: 3, - ClientSideCacheConfig: &redis.ClientSideCacheConfig{}, - ClientSideCacheStrategy: redis.CSCStrategyBroadcast, -}) -``` +Only deterministic read commands are cached. Writes, streaming replies, and +commands whose results depend on something other than their arguments are +always sent to the server. `SORT_RO` is cached only without its `BY` and `GET` +options, which read keys that the client cannot track. ### Configuration options @@ -270,29 +261,37 @@ cache: | Name | Description | | :-- | :-- | | `MaxEntries` | The maximum number of entries the cache can hold. Zero or negative means unlimited. If both `MaxEntries` and `MaxMemoryBytes` are unlimited, `MaxEntries` defaults to 10,000 so the cache cannot grow without bound. | -| `MaxMemoryBytes` | An approximate memory limit for the cache. Zero means unlimited. | -| `DrainInterval` | (`CSCStrategySharedTracking` only) How often the background drainer scans idle connections and applies buffered invalidations to the shared cache. The default is 5ms and the minimum is 1ms. | -| `MaxStaleness` | The hard upper bound on how long a cached entry can be served after the underlying data has changed. Zero means no explicit bound (the drain interval still applies). | +| `MaxMemoryBytes` | An approximate memory limit for the cache. Zero or negative means unlimited. The cache is divided into 16 shards that each enforce a 16th of this limit, so set it to at least 16 times the size of your largest cached reply. | +| `MaxStaleness` | The longest time an entry can be served after it was cached, regardless of invalidation. This is a safety net for a missed invalidation rather than the main way entries are kept fresh, so set it well above the time an invalidation takes to arrive. Zero, the default, disables it. | +| `DrainInterval` | How often go-redis checks idle connections for invalidation messages and applies them. The default is 5ms and the minimum is 1ms. | + +### Commands you can't use while caching + +The cache relies on the state of the connection it was populated from, so while +client-side caching is enabled, go-redis rejects commands that would change +that state: `SELECT`, `AUTH`, `HELLO` with arguments, `RESET`, +[`CLIENT TRACKING`]({{< relref "/commands/client-tracking" >}}), and the raw +`SUBSCRIBE`, `PSUBSCRIBE`, and `SSUBSCRIBE` commands. One rejected command +fails the entire pipeline it belongs to. The `Subscribe()`, `PSubscribe()`, and +`SSubscribe()` methods still work normally because they use their own +connections. + +For the same reason, client-side caching is disabled if you set any of the +credentials provider options, because the client's permissions could change +after data is cached. Fixed `Username` and `Password` values are supported. ### Monitoring the cache -Use the `CSCStats()` method to read the cumulative cache hit and miss counts -for a client: +Use the `CSCStats()` method to read the cache statistics for a client: ```go -hits, misses := client.CSCStats() -fmt.Printf("Cache hits: %d, misses: %d\n", hits, misses) +stats := client.CSCStats() +fmt.Printf("Cache hits: %d, misses: %d\n", stats.Hits, stats.Misses) +fmt.Printf("Entries: %d, memory: %d bytes\n", stats.Entries, stats.MemoryUsageBytes) ``` -Process-wide totals are also available via the package-level functions -`redis.CommandStats()` (served-command hits and misses), -`redis.CacheAdmissionRejects()` (entries rejected on admission), and -`redis.RESPInvalidationBytesRead()` (bytes of invalidation key names read). - -{{< note >}}To supply your own cache implementation, set the `ClientSideCache` -option instead of `ClientSideCacheConfig`. An explicit `ClientSideCache` is -honoured by the `CSCStrategySharedTracking` and `CSCStrategyBroadcast` -strategies. `CSCStrategyPerConnection` always builds a private cache per -connection from `ClientSideCacheConfig` and ignores an explicit -`ClientSideCache` (with a log warning). +{{< note >}}To supply your own cache implementation, or to share one cache +between several clients, set the `ClientSideCache` option. It takes precedence +over `ClientSideCacheConfig`. A shared cache is only safe between clients that +connect to the same server and database. {{< /note >}} From e5a21fdaba2bba5ca739a5d6a48ec7df76765579 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Tue, 4 Aug 2026 14:23:26 +0100 Subject: [PATCH 3/4] DOC-6831 Add a custom cache section to go-redis client-side caching Trim the cacheable-command detail, which the client-side caching introduction page already covers under "Which commands can cache data?", and expand the closing note about ClientSideCache into a "Supplying your own cache" section. The section documents three levels of customization, because the release supports three and they differ a lot in effort: set Sizer if you only want to change how entry memory is estimated, wrap the built-in cache if you want to change behavior, or implement the eight-method Cache interface from scratch. Wrapping is the one worth leading with, and go-redis's own tests use exactly that shape. The wrapping advice carries a footgun I verified rather than assumed. Cache statistics are read through an optional Stats() method that the Cache interface does not declare, so a decorator that embeds the interface satisfies Cache, compiles, and silently makes CSCStats() report zeros; embedding the concrete *LocalCache promotes Stats() and keeps it working. I confirmed both directions by compiling against v9.22.0 -- the concrete form satisfies an anonymous Stats() CSCStats interface and the interface form fails with "missing method Stats" -- then compile-verified the documented snippet as written, including the Sizer signature. Learned: an optional method reached by type assertion is invisible to the interface, so wrapping advice must name the concrete type to embed or it silently degrades a feature Constraint: custom cache decorators must embed the concrete *redis.LocalCache, not the redis.Cache interface, or CSCStats() reports zeros Gaps: the countingCache snippet is compile-verified against v9.22.0 but not run against a live server, so the lookup counter is not observed incrementing Ticket: DOC-6831 --- content/develop/clients/go/connect.md | 81 ++++++++++++++++++--------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/content/develop/clients/go/connect.md b/content/develop/clients/go/connect.md index 0bf578e259..e047f574a0 100644 --- a/content/develop/clients/go/connect.md +++ b/content/develop/clients/go/connect.md @@ -246,12 +246,8 @@ server, and go-redis applies the invalidation messages in the background. Because invalidation is asynchronous, an entry is evicted shortly after the data changes rather than at the instant of the write. Use `DrainInterval` to control how often invalidations are applied, and `MaxStaleness` to put a hard -time limit on how long any entry can be served. - -Only deterministic read commands are cached. Writes, streaming replies, and -commands whose results depend on something other than their arguments are -always sent to the server. `SORT_RO` is cached only without its `BY` and `GET` -options, which read keys that the client cannot track. +time limit on how long any entry can be served (see +[Configuration options](#configuration-options) for more information). ### Configuration options @@ -265,21 +261,6 @@ cache: | `MaxStaleness` | The longest time an entry can be served after it was cached, regardless of invalidation. This is a safety net for a missed invalidation rather than the main way entries are kept fresh, so set it well above the time an invalidation takes to arrive. Zero, the default, disables it. | | `DrainInterval` | How often go-redis checks idle connections for invalidation messages and applies them. The default is 5ms and the minimum is 1ms. | -### Commands you can't use while caching - -The cache relies on the state of the connection it was populated from, so while -client-side caching is enabled, go-redis rejects commands that would change -that state: `SELECT`, `AUTH`, `HELLO` with arguments, `RESET`, -[`CLIENT TRACKING`]({{< relref "/commands/client-tracking" >}}), and the raw -`SUBSCRIBE`, `PSUBSCRIBE`, and `SSUBSCRIBE` commands. One rejected command -fails the entire pipeline it belongs to. The `Subscribe()`, `PSubscribe()`, and -`SSubscribe()` methods still work normally because they use their own -connections. - -For the same reason, client-side caching is disabled if you set any of the -credentials provider options, because the client's permissions could change -after data is cached. Fixed `Username` and `Password` values are supported. - ### Monitoring the cache Use the `CSCStats()` method to read the cache statistics for a client: @@ -290,8 +271,58 @@ fmt.Printf("Cache hits: %d, misses: %d\n", stats.Hits, stats.Misses) fmt.Printf("Entries: %d, memory: %d bytes\n", stats.Entries, stats.MemoryUsageBytes) ``` -{{< note >}}To supply your own cache implementation, or to share one cache -between several clients, set the `ClientSideCache` option. It takes precedence -over `ClientSideCacheConfig`. A shared cache is only safe between clients that -connect to the same server and database. +### Supplying your own cache + +Set the `ClientSideCache` option to use your own cache instead of the built-in +one. It accepts any value that implements the +[`Cache`](https://pkg.go.dev/github.com/redis/go-redis/v9#Cache) interface and +takes precedence over `ClientSideCacheConfig`. You can also use it to share a +single cache between several clients, but only when those clients connect to the +same server and database. + +The simplest approach is to wrap the built-in cache, which `NewLocalCache()` +returns, and override only the methods you want to change. The example below +counts lookups and passes everything else through: + +```go +type countingCache struct { + *redis.LocalCache + lookups atomic.Int64 +} + +func (c *countingCache) Get(ctx context.Context, cacheKey string) ([]byte, bool) { + c.lookups.Add(1) + return c.LocalCache.Get(ctx, cacheKey) +} + +cache := &countingCache{ + LocalCache: redis.NewLocalCache(redis.ClientSideCacheConfig{MaxEntries: 1000}), +} + +client := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Protocol: 3, + ClientSideCache: cache, +}) +``` + +{{< note >}}Embed the concrete `*redis.LocalCache` type, as shown above, rather +than the `Cache` interface. Cache statistics come from an optional `Stats()` +method that the `Cache` interface doesn't declare, so a wrapper that embeds the +interface still compiles but makes `CSCStats()` report zeros. {{< /note >}} + +To write a cache from scratch, implement all eight `Cache` methods: `Get()` for +lookups, `Reserve()`, `FulfillOwned()`, and `Cancel()` to ensure that only one +caller fetches a missing key, and `DeleteByRedisKey()`, `DeleteByCacheKey()`, +`EvictByConn()`, and `Flush()` to remove entries when invalidation arrives. +go-redis calls these methods from several goroutines at once, so your +implementation must be thread-safe, must treat cache keys and Redis keys as +opaque strings and preserve them exactly, and must stop waiting for an +in-progress reservation when the context is canceled. Implement +`Stats() redis.CSCStats` as well if you want `CSCStats()` to keep working. + +If you only want to change how the cache estimates the memory an entry uses, you +don't need your own implementation. Set the `Sizer` field of +`ClientSideCacheConfig` to a function that returns a size in bytes, and the +built-in cache uses it in place of its own approximation. From bad83f929e30125824f836c48dedeabb21ceb392 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Tue, 4 Aug 2026 14:56:51 +0100 Subject: [PATCH 4/4] DOC-6831 Tidy the wording of the custom cache section Copyedit only: no change to the documented API surface or behavior. Ticket: DOC-6831 --- content/develop/clients/go/connect.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/content/develop/clients/go/connect.md b/content/develop/clients/go/connect.md index e047f574a0..ebf31da3fc 100644 --- a/content/develop/clients/go/connect.md +++ b/content/develop/clients/go/connect.md @@ -312,17 +312,17 @@ method that the `Cache` interface doesn't declare, so a wrapper that embeds the interface still compiles but makes `CSCStats()` report zeros. {{< /note >}} -To write a cache from scratch, implement all eight `Cache` methods: `Get()` for +To write a cache from scratch, you should implement all eight `Cache` methods: `Get()` for lookups, `Reserve()`, `FulfillOwned()`, and `Cancel()` to ensure that only one caller fetches a missing key, and `DeleteByRedisKey()`, `DeleteByCacheKey()`, `EvictByConn()`, and `Flush()` to remove entries when invalidation arrives. go-redis calls these methods from several goroutines at once, so your -implementation must be thread-safe, must treat cache keys and Redis keys as -opaque strings and preserve them exactly, and must stop waiting for an -in-progress reservation when the context is canceled. Implement -`Stats() redis.CSCStats` as well if you want `CSCStats()` to keep working. - -If you only want to change how the cache estimates the memory an entry uses, you -don't need your own implementation. Set the `Sizer` field of -`ClientSideCacheConfig` to a function that returns a size in bytes, and the -built-in cache uses it in place of its own approximation. +implementation must be thread-safe, and must treat cache keys and Redis keys as +opaque strings and preserve them exactly. It must also stop waiting for an +in-progress reservation when the context is canceled. You should also implement +`Stats() redis.CSCStats` if you want to use the `CSCStats()` function with your cache. + +If you only want to change how the cache estimates the memory used by an entry, you +don't need a full cache implementation. Instead, set the `Sizer` field of +`ClientSideCacheConfig` to a function that returns a size in bytes. The +built-in cache will then use it in place of its own approximation.