Redis cluster cache provider
Redis Cluster cache provider implementing cache.Provider against a sharded Redis Cluster deployment, with hash-slot routing and the same tag and search semantics as the single-node provider.
Overview
Redis Cluster shards the keyspace across multiple Redis nodes using CRC16 hash slots. The Piko provider wraps redis.ClusterClient, which discovers the cluster topology from any of the seed addresses and routes each command to the correct shard. From the application's perspective the API matches the single-node Redis provider. The difference is that one node going down does not take the whole cache with it, and write capacity scales with shard count.
Use Redis Cluster when the working set or write throughput outgrows a single node. Use plain Redis when one node is enough, Valkey Cluster for a BSD-licensed sharded fork, and Otter for in-process caching at single-instance scale.
The provider is a drop-in replacement for the single-node provider. It asserts cache.Provider at compile time and wires in with the same two bootstrap options, so application code does not change. Every namespace shares one redis.ClusterClient, and each namespace becomes a key prefix. Topology discovery happens once, and there is no per-namespace connection cost.
Tag-based invalidation is cluster-aware. The provider wraps each tag key in Redis hash-tag braces ({tag}), so the keys for one tag land on the same hash slot. This lets multi-key tag operations run within a single shard. The package is pure Go with no build tags or cgo, so it runs in compiled builds and in the interpreted dev-i mode.
Requirements
- A reachable Redis Cluster. Supply at least one seed node in
Addrs, and the client discovers the rest from cluster topology. - Network egress to every cluster node, not only the seeds. The client connects directly to whichever shard owns each key.
- A
cache.EncodingRegistryfor cache values. - Redis Stack (RediSearch) on the cluster for full-text search and structured queries. Plain key-value caching and tag invalidation work without it.
Configuration
import (
"os"
"time"
"piko.sh/piko/wdk/cache"
"piko.sh/piko/wdk/cache/cache_encoder_json"
"piko.sh/piko/wdk/cache/cache_provider_redis_cluster"
)
registry := cache.NewEncodingRegistry(cache_encoder_json.New[any]().(cache.AnyEncoder))
provider, err := cache_provider_redis_cluster.NewRedisClusterProvider(cache_provider_redis_cluster.Config{
Addrs: []string{ // required: at least one seed node
"redis-1.example.com:6379",
"redis-2.example.com:6379",
"redis-3.example.com:6379",
},
Password: os.Getenv("REDIS_PASSWORD"), // empty for unauthenticated clusters
Namespace: "myapp:", // global key prefix
IndexPrefix: "index:", // RediSearch index prefix; default index:
DefaultTTL: 1 * time.Hour, // entry expiry; default 1 hour
Registry: registry, // required: value encoder registry
})
if err != nil {
return err
}
cache_encoder_json.New[any]() returns EncoderPort[any], so assert it to cache.AnyEncoder before you pass it to NewEncodingRegistry.
NewRedisClusterProvider pings the cluster on construction. It fails fast if it cannot reach any seed or if Registry is nil, so a misconfigured cluster surfaces at boot and not on the first request.
The provider applies the shared cache defaults, so timeouts and retry limits match every other cache provider. OperationTimeout defaults to 2 seconds, AtomicOperationTimeout to 5 seconds, BulkOperationTimeout to 10 seconds, FlushTimeout to 30 seconds, and SearchTimeout to 5 seconds. MaxComputeRetries defaults to 10 for optimistic-lock retries. Set KeyRegistry when keys are structs instead of simple types. Leave these fields zero to take the defaults.
Namespaces and supported types
Each namespace is a key prefix on the shared client. The first call for a namespace creates an adapter, and later calls with the same key and value types reuse it. The reflection-based factory supports a fixed set of type pairs: string keys with []byte, string, int, or any values, and int keys with string, int, or any values. For domain types outside this set, register a factory blueprint with cache.RegisterProviderFactory().
Cluster-wide invalidation
InvalidateAll deletes only the keys under the namespace prefix. Without a namespace, it returns an error unless you enable AllowUnsafeFLUSHDB. That flag runs FLUSHDB, which clears all data on all master nodes in the cluster. Leave it disabled in shared clusters.
Bootstrap
ssr := piko.New(
piko.WithCacheProvider("redis-cluster", provider),
piko.WithDefaultCacheProvider("redis-cluster"),
)
See also
Other cache providers:
- Redis, single-node Redis for smaller deployments.
- Valkey Cluster, BSD-licensed sharded fork.
- Valkey, single-node Valkey alternative.
- Otter, in-process cache for single-instance setups.
- Multilevel, Otter L1 in front of a Redis Cluster L2.
Cache encoders and transformers:
- JSON encoder, register with the value
Registry. - Gob encoder, Go-native encoding alternative.
- Crypto transformer, encrypt cached values.
- Zstd transformer, compress cached values.
Framework docs:
- How to use the cache, wiring the cache service end-to-end.
- Cache API reference, every type and method on the cache service.
- About caching, design rationale for the cache port.
External:
- Redis Cluster specification, sharding and failover semantics.
- Hash tags, co-locating keys on the same shard.