redis

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package redis is a cache.Cache backed by Redis, in either single-node or cluster mode.

What choosing it commits you to

The store is shared and outlives the process, which is the reason to pick it: every replica sees the same entries, an invalidation performed anywhere is effective everywhere, and a deploy does not start cold.

The price is that every operation is a network call, so this provider has failure modes the memory one does not. Calls go through a circuit breaker, and a tripped one reports cache.ErrUnavailable — deliberately not cache.ErrNotFound, because a caller that needs to distinguish "absent" from "could not ask" must not be told the former when the latter is true. Batched reads and writes exist on the interface because round trips are the cost that matters here: GetMany, SetMany, and DeleteMany do in one call what a loop would do in n.

Values are encoded on write and decoded into a fresh value on read, so nothing is shared between the caller and the store the way it is in the memory provider.

Namespaces are what make whole-cache operations possible

A Redis database may hold more than this cache's entries. With cfg.Namespace set, every key is transparently prefixed with it — callers still use bare keys — and the prefix is what lets Flush and an empty-prefix DeleteByPrefix delete exactly what this cache owns. Without one they report cache.ErrNamespaceRequired rather than guess.

Entries carry no record of the codec that wrote them, so pointing a cache with a new codec at keys warmed by the old one produces decode errors until they expire. Give the new codec its own namespace when switching.

Cluster mode

A cluster is inferred from more than one address and can also be declared outright with cfg.Cluster, which is necessary when the cluster is reached through a single seed — a multi-key command against a cluster misread as single-node fails with CROSSSLOT. In cluster mode, prefix deletion fans out across masters, since SCAN answers only for the node it was sent to.

Index

Constants

This section is empty.

Variables

View Source
var ErrCodecTypeMismatch = errors.New("codec type does not match cache type")

ErrCodecTypeMismatch indicates WithCodec was given a codec for a type other than the cache's. Option carries no type parameter, so the compiler cannot catch this; NewRedisCache reports it instead.

Functions

This section is empty.

Types

type Cache

type Cache[T any] struct {
	// contains filtered or unexported fields
}

Cache is the redis-backed cache.Cache implementation. It is exported, and returned by NewRedisCache, so a caller can depend on this cache rather than on the interface every provider shares — and so face only the failures this provider actually has, rather than the union of every provider's.

func NewRedisCache

func NewRedisCache[T any](cfg *Config, expiration time.Duration, cb circuitbreaking.CircuitBreaker, opts ...Option) (*Cache[T], error)

NewRedisCache builds a new redis-backed cache. When cfg.Namespace is set, every key is transparently prefixed with it: callers always use bare keys, the namespace marks which entries this cache owns, and Flush becomes possible (it deletes exactly the namespace's keys). Without a namespace, Flush and an empty-prefix DeleteByPrefix return cache.ErrNamespaceRequired rather than guess at ownership in a possibly shared database.

Values are stored through cache.NewDefaultCodec unless WithCodec says otherwise. Entries carry no record of the codec that wrote them, so pointing a cache with one codec at a store warmed by another produces decode errors until the old entries expire; give the new codec its own cfg.Namespace when switching.

func (*Cache[T]) Close

func (c *Cache[T]) Close() error

Close releases the connection pool. It does not evict anything: the entries live in redis and outlive any one client.

It is safe to call more than once — go-redis's Close is idempotent.

func (*Cache[T]) Delete

func (i *Cache[T]) Delete(ctx context.Context, key string) error

func (*Cache[T]) DeleteByPrefix

func (i *Cache[T]) DeleteByPrefix(ctx context.Context, prefix string) error

DeleteByPrefix removes every entry whose (caller-visible) key begins with prefix, via a cursor SCAN over the namespaced pattern. Without a configured namespace an empty prefix is refused with cache.ErrNamespaceRequired — matching every key in a possibly shared database is not ownership.

func (*Cache[T]) DeleteMany

func (i *Cache[T]) DeleteMany(ctx context.Context, keys []string) error

DeleteMany removes the given keys. In cluster mode a multi-key DEL requires every key to share a hash slot, so the keys are bucketed by slot and deleted one DEL per slot; a single-node client deletes them in one DEL.

func (*Cache[T]) Flush

func (i *Cache[T]) Flush(ctx context.Context) error

Flush removes every entry this cache owns. Ownership is the configured namespace; without one this cache cannot distinguish its entries in a possibly shared database, and Flush returns cache.ErrNamespaceRequired rather than reach for FLUSHDB.

func (*Cache[T]) Get

func (i *Cache[T]) Get(ctx context.Context, key string) (*T, error)

func (*Cache[T]) GetMany

func (i *Cache[T]) GetMany(ctx context.Context, keys []string) (map[string]*T, error)

GetMany fetches multiple keys, returning only those that were present. In cluster mode MGET requires every key to share a hash slot, so the keys are bucketed by slot and fetched one MGET per slot; a single-node client fetches them all in one MGET. Results are keyed by the caller's bare keys.

func (*Cache[T]) Ping

func (i *Cache[T]) Ping(ctx context.Context) error

Ping reports whether redis is reachable.

It goes through the observer and the breaker like every other method. It used to bypass both, which made the one call whose entire purpose is to report reachability the one call that neither recorded a failure nor let the breaker learn from it — and left a health check hitting a dead redis emitting nothing.

A refusal from an open breaker is ErrUnavailable rather than a redis error: the breaker is open precisely because redis has been failing, and answering "unavailable" without waiting for another timeout is the point of having one.

func (*Cache[T]) Set

func (i *Cache[T]) Set(ctx context.Context, key string, value *T, opts ...cache.WriteOption) error

func (*Cache[T]) SetIfPresent

func (i *Cache[T]) SetIfPresent(ctx context.Context, key string, value *T, opts ...cache.WriteOption) error

SetIfPresent writes key only if redis already holds it, as a single SET with the XX flag.

This is the reason the method is on the interface at all: redis decides the condition and performs the write in one command, so nothing can delete the key in between. A GET followed by a SET would leave exactly that window, and no amount of care on this side closes it.

A refusal comes back as redis.Nil — the same reply a missing GET produces — and is translated to ErrNotFound. Like a read miss it is a healthy answer rather than an infrastructure failure, so it feeds the breaker a success: the server responded, correctly, that the condition did not hold.

func (*Cache[T]) SetMany

func (i *Cache[T]) SetMany(ctx context.Context, items map[string]*T, opts ...cache.WriteOption) error

SetMany stores multiple values, each with the expiration resolved from this call's options (the cache's configured default when none are given). The writes and their expiry are applied together inside a single Lua script (see batchSetScript), which is both atomic and a single round trip. In cluster mode EVAL requires every key to share a hash slot, so the batch is split per slot.

type Config

type Config struct {
	Username string `env:"USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
	Password string `env:"PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
	// Namespace, when set, is transparently prepended to every key this cache
	// stores. It marks which entries the cache owns in a possibly shared
	// database, which is what makes Flush (and an empty-prefix
	// DeleteByPrefix) safe — and therefore possible. Include a trailing
	// delimiter if you want one (e.g. "myservice:"); the namespace is used
	// verbatim.
	Namespace string   `env:"NAMESPACE" json:"namespace,omitempty" yaml:"namespace,omitempty"`
	Addresses []string `env:"ADDRESSES" json:"addresses,omitempty" yaml:"addresses,omitempty"`
	Cluster   bool     `env:"CLUSTER"   json:"cluster,omitempty"   yaml:"cluster,omitempty"`
}

Config configures a Redis-backed consumer.

func (*Config) ValidateWithContext

func (cfg *Config) ValidateWithContext(ctx context.Context) error

ValidateWithContext validates a Config struct.

type Option

type Option func(*options)

Option configures a redis cache at construction.

It carries no type parameter even though the cache does. Go cannot infer a type argument from a call's result type, so an Option[T] would force every call site to spell the cached type out by hand — WithLogger[MyValue](l) — forever. WithCodec is the one option that depends on the cached type; it stays generic but still needs no annotation, because T is inferable from the codec it is handed.

func WithCodec

func WithCodec[T any](codec cache.Codec[T]) Option

WithCodec swaps the value codec. The default is cache.NewDefaultCodec; supply cache.NewGobCodec for values with interface-typed fields, or a codec of your own when a fixed format beats a self-describing one. Values written with one codec are unreadable through another — see cache.Codec for the migration caveat.

T is inferred from the codec, so this needs no type argument:

redis.WithCodec(cache.NewGobCodec[Session]())

It must match the cache it configures. Because Option carries no type parameter, a codec for the wrong type cannot be rejected by the compiler; NewRedisCache returns ErrCodecTypeMismatch instead, at construction.

func WithLogger

func WithLogger(logger logging.Logger) Option

WithLogger attaches a logger. An absent logger logs nowhere.

func WithMetricsProvider

func WithMetricsProvider(metricsProvider metrics.Provider) Option

WithMetricsProvider attaches a metrics provider for the cache's hit, miss, set, delete, and error counters and its latency histogram. An absent provider records nothing.

func WithScanPageSize

func WithScanPageSize(size int64) Option

WithScanPageSize sets the COUNT a single SCAN iteration asks for during prefix deletion. It is a hint to redis, not a guarantee: an iteration may return more or fewer keys.

The default of 1000 trades round trips against per-command latency. Raise it to sweep a large keyspace in fewer round trips; lower it on a latency- sensitive shared instance, since redis serves SCAN on its single command thread and a large COUNT blocks other clients for the duration. A non-positive size is ignored, keeping the default.

func WithTracerProvider

func WithTracerProvider(tracerProvider tracing.Provider) Option

WithTracerProvider attaches a tracer provider, enabling spans on every cache operation. An absent tracer provider traces nowhere.

Directories

Path Synopsis
Package slots produces Redis Cluster keys whose slots are pre-planned to distribute evenly across the cluster's nodes.
Package slots produces Redis Cluster keys whose slots are pre-planned to distribute evenly across the cluster's nodes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL