cache

package
v1.0.442 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package cache provides a small key/value cache abstraction (Provider) with a TTL per entry, a pattern-based key listing and a minimal publish/subscribe channel, backed either by process memory or by Redis.

Values are JSON encoded on Set and decoded into the pointer passed to Get, so both backends behave the same. A missing or expired entry is reported as ErrNotFound (test with IsNotFoundError).

Keys: a key is cleaned as a rooted path and joined with the provider prefix, so "x", "/x" and "x/" name the same entry and ".." cannot leave the prefix ("../x" is "<prefix>/x"); a proxy prefix is cleaned the same way. Keys takes a Redis glob pattern relative to the provider ('*' and '?' also match '/', "[...]" sets, '\' escapes), matches the prefix literally and returns the matching unexpired keys relative to the provider, in the form Get accepts (the key "" only for an empty pattern); the memory provider implements the Redis matcher, so both backends list the same keys.

var p cache.Provider
if cfg.Provider == "redis" {
	p, err = cache.NewRedisProvider(*cfg.Redis, "/myapp")
} else {
	p = cache.NewMemoryProvider("/myapp")
}
err = p.Set(ctx, "user:1", user, 10*time.Minute) // 0 => default TTL, cache.KeepTTL => no expiry
var u User
err = p.Get(ctx, "user:1", &u)

// scope a shared provider to a sub-namespace
users := cache.NewProxyProvider("users", p)

Config YAML/JSON fields: provider (redis|memory) and redis {server, ttl, client_tls {cert, key, trusted_ca}, user, password}. The package does not itself switch on Config.Provider; the caller picks the constructor.

TTL: a ttl of 0 means DefaultTTL for the memory provider and RedisConfig.TTL (default 1h) for Redis; KeepTTL stores the value without expiry (Redis KEEPTTL). The memory provider never evicts on its own: the caller must run CleanExpired periodically, and NowFunc can be overridden in tests to control its clock. GetOrSet reads through the cache on a miss and stores a successful getter result with the provider's default TTL. The getter returns a pointer to the value to store. On a miss, the destination must point to a concrete type; providers cannot reliably restore values into interface destinations. Concurrent misses may run the getter more than once.

Pub/Sub: Subscribe returns a Subscription whose ReceiveMessage returns the next message, ctx.Err() as soon as the context is done, or ErrClosed after Close; Close is idempotent, and closing the provider closes its subscriptions (a Subscribe during or after that Close returns a subscription reporting ErrClosed). A message published after Subscribe returns reaches the new subscriber: the Redis provider waits for the server to confirm the subscription, and a subscription it could not establish reports the error from ReceiveMessage. Delivery is at most once and Publish never waits for a subscriber: each subscriber buffers 100 undelivered messages; a memory subscriber whose buffer is full misses the message, and a Redis subscriber follows the go-redis channel policy (its reader waits up to one minute, then drops the message). The memory provider delivers in-process only; the Redis provider uses Redis channels, which are not prefixed.

sub := p.Subscribe(ctx, "invalidate")
defer sub.Close()
for {
	msg, err := sub.ReceiveMessage(ctx)
	if err != nil {
		return err // ctx done, ErrClosed, or the subscription failed
	}
	handle(msg)
}

Index

Constants

This section is empty.

Variables

View Source
var DefaultTTL = 30 * time.Minute

DefaultTTL is the expiry applied by the memory provider when Set is called with ttl == 0. It is a package variable and may be changed at startup.

View Source
var ErrClosed = errors.New("subscription closed")

ErrClosed is returned by Subscription.ReceiveMessage after Close or after the provider was closed, discarding buffered messages, and by the failed subscription that Subscribe returns during or after the provider's Close.

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is returned by Get for a missing or expired key.

View Source
var KeepTTL = time.Duration(-1)

KeepTTL is the ttl value for Set that stores the value without expiry (Redis KEEPTTL semantics: an existing key keeps its TTL, a new key has none).

View Source
var NowFunc = time.Now

NowFunc is the clock used by the memory provider for expiry; override it in tests to simulate time passing.

Functions

func GetOrSet added in v0.24.557

func GetOrSet(ctx context.Context, p Provider, key string, value any, getter func() (any, error)) error

GetOrSet decodes the cached value for key into value (a non-nil pointer). On a miss it calls getter, which must return a pointer, and copies the pointed-to result into value. A successful result is also stored with the provider's default TTL (Set with ttl 0). A cache write failure is returned without changing value. Interface destinations are unsupported on a miss because providers cannot reliably restore the getter's concrete type. Concurrent misses may call getter more than once. Errors other than a miss are returned as-is.

func IsNotFoundError

func IsNotFoundError(err error) bool

IsNotFoundError reports whether err is (or wraps) ErrNotFound, or whose message contains "not found".

Types

type Config

type Config struct {
	// Provider specifies the cache provider: redis|memory
	Provider string `json:"provider" yaml:"provider"`
	// Redis holds the Redis connection settings when Provider is "redis".
	Redis *RedisConfig `json:"redis" yaml:"redis"`
}

Config specifies configuration of the cache. The package does not switch on Provider itself; callers choose NewMemoryProvider or NewRedisProvider(*Config.Redis, prefix).

type Provider

type Provider interface {
	// Set stores v (JSON encoded, or raw for string/[]byte on Redis) under
	// key. ttl == 0 applies the provider default, KeepTTL disables expiry.
	Set(ctx context.Context, key string, v any, ttl time.Duration) error
	// Get decodes the value stored under key into v, which must be a
	// non-nil pointer. It returns ErrNotFound for a missing or expired key.
	Get(ctx context.Context, key string, v any) error
	// Delete removes the keys; missing keys are not an error.
	Delete(ctx context.Context, keys ...string) error
	// CleanExpired removes expired entries from the memory provider;
	// it is a no-op for Redis, which expires keys itself.
	CleanExpired(ctx context.Context)
	// Close closes the client, releasing any open resources. The memory
	// and Redis providers close their live subscriptions first, so a
	// pending ReceiveMessage returns ErrClosed and queued messages are
	// discarded; a Subscribe that runs during or after Close returns a
	// failed subscription that reports ErrClosed.
	// It is rare to Close a Client, as the Client is meant to be long-lived and shared between many goroutines.
	Close() error
	// Keys returns the unexpired keys, relative to the provider, that
	// match the Redis glob pattern ('*' and '?' also match '/'), which is
	// relative to the provider and cleaned like a key; the prefix matches
	// literally. Only keys that Get can read are listed, and the key ""
	// only for an empty pattern. The memory provider matches as Redis KEYS
	// does (ranges with bytes >= 0x80 compare unsigned).
	// It scans the whole keyspace and is meant for tests.
	Keys(ctx context.Context, pattern string) ([]string, error)

	// IsLocal returns true when the cache lives in this process only.
	IsLocal() bool

	// Publish sends message to every current subscriber of channel. It
	// never waits for a subscriber: a memory subscriber whose buffer of
	// 100 undelivered messages is full misses the message,
	// and a Redis subscriber is subject to the go-redis channel policy
	// (delivery waits up to one minute, then the message is dropped). A
	// Redis Publish also fails when ctx is done or the server is unreachable.
	Publish(ctx context.Context, channel, message string) error
	// Subscribe registers a subscriber for channel; call Close on the result
	// when done. A message published after Subscribe returns is delivered
	// to the new subscriber (Redis confirms the subscription with the
	// server before returning; the wait ends when ctx is done, returning
	// ctx.Err() through ReceiveMessage; otherwise connecting is bounded by
	// the go-redis dial timeout and the confirmation by 10 seconds).
	// Subscribe never returns nil: when the subscription could not be
	// established, or the provider is closed, the returned
	// Subscription reports the error from ReceiveMessage and its Close is
	// a no-op.
	Subscribe(ctx context.Context, channel string) Subscription
}

Provider is the cache interface implemented by the memory, Redis and proxy providers. Implementations are safe for concurrent use. A key is cleaned as a rooted path and stored under the provider prefix, so ".." cannot leave the prefix.

func NewMemoryProvider

func NewMemoryProvider(prefix string) Provider

NewMemoryProvider returns an in-process Provider whose keys are stored under prefix like the Redis provider's (an empty prefix becomes "/"). Values are kept JSON encoded; the store is unbounded and expired entries are only dropped on Get or CleanExpired.

func NewProxyProvider

func NewProxyProvider(prefix string, prov Provider) Provider

NewProxyProvider returns a Provider that stores every key under prefix in prov, which cleans the combined key again; like a key, prefix cannot leave prov's namespace. Keys strips prefix again. Its Close is a no-op (the parent owns the connection) and Publish/Subscribe pass channels through unprefixed.

func NewRedisProvider

func NewRedisProvider(cfg RedisConfig, prefix string) (Provider, error)

NewRedisProvider returns a Provider backed by Redis. cfg.Server is parsed with redis.ParseURL, and a malformed URL is reported without the URL, which may embed a password; cfg.ClientTLS files (if set) configure TLS and cfg.Password overrides the URL credentials. An empty prefix becomes "/"; a zero cfg.TTL becomes 1h. Maintenance notifications are disabled. The connection is established lazily, so a bad address only fails later.

type RedisConfig

type RedisConfig struct {
	// Server is the redis:// or rediss:// URL.
	Server string `json:"server,omitempty" yaml:"server,omitempty"`
	// TTL is the expiry applied when Set is called with ttl == 0; default 1h.
	TTL time.Duration `json:"ttl,omitempty" yaml:"ttl,omitempty"`
	// ClientTLS describes the TLS certs used to connect to the cluster
	ClientTLS *gserver.TLSInfo `json:"client_tls,omitempty" yaml:"client_tls,omitempty"`
	// User is the ACL user name; only applied when Password is set.
	User string `json:"user,omitempty" yaml:"user,omitempty"`
	// Password overrides the credentials embedded in Server.
	Password string `json:"password,omitempty" yaml:"password,omitempty"`
}

RedisConfig specifies the Redis connection used by NewRedisProvider.

type Subscription

type Subscription interface {
	// Close unregisters the subscription and releases its resources,
	// discarding buffered messages. It is idempotent, and it unblocks a
	// pending ReceiveMessage, which then returns ErrClosed. Closing the
	// provider closes its subscriptions; calling Close on them afterwards
	// is harmless. During an outage Close may wait for an
	// in-flight go-redis redial.
	Close() error
	// ReceiveMessage returns the next buffered message, or blocks until a
	// message arrives, ctx is done (returning ctx.Err()) or the
	// subscription is closed (returning ErrClosed). It returns promptly in
	// the last two cases and may be called from one goroutine at a time.
	// A Subscription that Subscribe could not establish returns the
	// subscribe error from every ReceiveMessage call. A Redis subscription
	// does not report connection loss: go-redis reconnects and resubscribes
	// in the background, and messages published meanwhile are lost.
	ReceiveMessage(ctx context.Context) (string, error)
}

Subscription is a channel subscription returned by Provider.Subscribe. Delivery is at most once: a subscriber that has fallen more than 100 messages behind misses messages; see Provider.Publish.

Jump to

Keyboard shortcuts

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