cache

package
v0.55.1 Latest Latest
Warning

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

Go to latest
Published: May 14, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package cache provides a tiered L1 (memory) + optional L2 (Redis) cache. L1 uses S3-FIFO eviction with 3 queues (small, main, ghost) for high hit rates. If RedisURL is empty, operates as L1-only (no external dependencies needed at runtime).

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrCacheMiss indicates the requested key was not found in the cache.
	ErrCacheMiss = errors.New("cache: miss")

	// ErrL2Unavailable indicates the L2 store is not available (nil receiver or closed).
	ErrL2Unavailable = errors.New("cache: L2 unavailable")
)

Sentinel errors for L2 cache operations.

Functions

func GetJSON

func GetJSON[T any](c *Cache, ctx context.Context, key string) (T, bool, error)

GetJSON retrieves a value from the cache and unmarshals it from JSON. Returns the zero value, false, nil on cache miss.

func GetOrLoadJSON

func GetOrLoadJSON[T any](c *Cache, ctx context.Context, key string,
	loader func(context.Context) (T, error),
) (T, error)

GetOrLoadJSON retrieves a typed value, calling loader on cache miss. The loaded value is marshaled to JSON and stored in the cache.

func Key

func Key(parts ...string) string

Key builds a deterministic cache key from parts using FNV-128a.

func SetJSON

func SetJSON[T any](c *Cache, ctx context.Context, key string, val T) error

SetJSON marshals val as JSON and stores it in the cache.

func SetJSONWithTTL

func SetJSONWithTTL[T any](c *Cache, ctx context.Context, key string, val T, ttl time.Duration) error

SetJSONWithTTL marshals val as JSON and stores it with a custom TTL.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache is a tiered L1 (memory) + optional L2 (Redis) cache. L1 uses the S3-FIFO eviction algorithm with three queues.

func New

func New(cfg Config) *Cache

New creates a new Cache. If cfg.RedisURL is empty, L2 is disabled. Call Close() when done to stop the background cleanup goroutine.

func (*Cache) Clear

func (c *Cache) Clear() int

Clear removes all entries from L1 and returns the number cleared. L2 is not affected. OnEvict callbacks are NOT fired (bulk operation).

func (*Cache) Close

func (c *Cache) Close()

Close stops the background cleanup goroutine and closes L2 if set.

func (*Cache) Delete

func (c *Cache) Delete(ctx context.Context, key string)

Delete removes a key from both L1 and L2.

func (*Cache) Get

func (c *Cache) Get(ctx context.Context, key string) ([]byte, bool)

Get retrieves a value from L1 (then L2 if configured). Returns nil, false on miss.

func (*Cache) GetIfValid

func (c *Cache) GetIfValid(ctx context.Context, key string, validator Validator) ([]byte, bool)

GetIfValid is like Get but additionally calls validator on every L1 hit. When validator returns false, the entry is evicted from L1 and the call falls through to L2. validator is NOT called on L1 miss or on L2 hit — callers wanting external-state validation against L2 should use a freshness-bearing key (e.g. include modTime in the cache key).

A nil validator behaves identically to plain Get (no validation).

Use to invalidate against external state: file modTime+size, DB row updated_at, ETag, source hash. Returning false → entry treated as stale and evicted; cache_validator_evict_total{reason="stale"} is incremented.

func (*Cache) GetOrLoad

func (c *Cache) GetOrLoad(ctx context.Context, key string, loader func(context.Context) ([]byte, error)) ([]byte, error)

GetOrLoad returns the value for key, loading it via loader on cache miss. Concurrent loads for the same key are deduplicated (singleflight). The loaded value is stored in L1.

func (*Cache) GetOrLoadWithTTL

func (c *Cache) GetOrLoadWithTTL(ctx context.Context, key string, ttl time.Duration,
	loader func(context.Context) ([]byte, error),
) ([]byte, error)

GetOrLoadWithTTL is like GetOrLoad but stores the loaded value with a custom TTL.

func (*Cache) InvalidateByTag

func (c *Cache) InvalidateByTag(ctx context.Context, tag string) int

InvalidateByTag removes all entries associated with the given tag from L1 and L2 (best-effort). Returns the number of entries removed.

func (*Cache) Set

func (c *Cache) Set(ctx context.Context, key string, data []byte)

Set stores a value in L1 (and L2 if configured) using global TTLs.

func (*Cache) SetWithTTL

func (c *Cache) SetWithTTL(ctx context.Context, key string, data []byte, ttl time.Duration)

SetWithTTL stores a value with a custom TTL for both L1 and L2. L1 applies jitter to the provided TTL; L2 uses it directly. If ttl <= 0, falls back to global TTLs (same as Set).

func (*Cache) SetWithTags

func (c *Cache) SetWithTags(ctx context.Context, key string, data []byte, tags []string)

SetWithTags stores a value with associated tags using global TTLs.

func (*Cache) SetWithTagsAndTTL

func (c *Cache) SetWithTagsAndTTL(ctx context.Context, key string, data []byte, ttl time.Duration, tags []string)

SetWithTagsAndTTL stores a value with a custom TTL and associated tags. If ttl <= 0, falls back to global TTLs.

func (*Cache) Stats

func (c *Cache) Stats() Stats

Stats returns a snapshot of cache statistics.

func (*Cache) Tags

func (c *Cache) Tags(key string) []string

Tags returns a copy of the tags associated with the given key. Returns nil if the key is not in L1.

type Config

type Config struct {
	// RedisURL is the Redis connection URL. Empty means L1-only mode.
	RedisURL string

	// RedisDB selects the Redis database number (default 0).
	RedisDB int

	// Prefix is prepended to all Redis keys (e.g. "gs:" for go-search).
	Prefix string

	// L1MaxItems is the max number of items in memory (default 1000).
	L1MaxItems int

	// L1TTL is the TTL for L1 cache entries (default 30m).
	L1TTL time.Duration

	// L2TTL is the TTL for L2 Redis entries (default 24h). Ignored if no Redis.
	L2TTL time.Duration

	// JitterPercent adds random TTL variation to prevent cache stampedes.
	// 0.1 means ±10% jitter. 0 disables jitter (default).
	JitterPercent float64

	// L2 is an optional L2 store (e.g. Redis). If set, overrides RedisURL.
	// Pass a mock here in tests instead of using a real Redis.
	L2 L2

	// OnEvict is called after an entry is removed from L1.
	// Called outside the cache lock — safe to call cache methods.
	// Must be goroutine-safe (may fire from multiple goroutines concurrently).
	OnEvict func(key string, data []byte, reason EvictReason)

	// MaxWeight is the maximum total weight of L1 entries in bytes.
	// 0 (default) disables weight-based eviction entirely.
	// Requires Weigher to be set; if Weigher is nil, MaxWeight is ignored.
	MaxWeight int64

	// Weigher computes the weight (e.g. byte size) of a cache entry.
	// When nil (default), no weight tracking is performed and MaxWeight is ignored.
	// The zero-value nil preserves exact existing behavior for all callers.
	Weigher func(key string, data []byte) int64

	// IdleTTL evicts an entry when it has not been accessed for this duration.
	// 0 (default) disables time-to-idle eviction entirely — no goroutine is spawned,
	// no lastAccess updates are checked. Exact existing behavior is preserved.
	IdleTTL time.Duration

	// Metrics, when non-nil, enables opt-in Prometheus metric publication for
	// this cache instance. Construct via WithMetrics(reg, name). nil (default)
	// disables emission entirely — no Prometheus registration, no goroutine,
	// zero overhead for callers using statsd / OTel / no metrics. Exact
	// existing behavior is preserved.
	Metrics *MetricsConfig
}

Config configures the cache.

type EvictReason

type EvictReason int

EvictReason indicates why a cache entry was removed.

const (
	EvictExpired  EvictReason = iota // TTL elapsed
	EvictCapacity                    // evicted for space (S3-FIFO)
	EvictExplicit                    // Delete() called
)

type L2

type L2 interface {
	Get(ctx context.Context, key string) ([]byte, error)
	Set(ctx context.Context, key string, data []byte, ttl time.Duration) error
	Del(ctx context.Context, key string) error
	Close() error
}

L2 is an optional second-tier cache (typically Redis). Get returns the value and nil error on hit, ErrCacheMiss on miss. Implementations must be safe for concurrent use.

type MetricsConfig

type MetricsConfig struct {
	// contains filtered or unexported fields
}

MetricsConfig configures opt-in Prometheus metric publication for a Cache instance. Construct via WithMetrics. A nil *MetricsConfig (or unset Config.Metrics) disables emission entirely — no goroutine, no allocation, no Prometheus registration cost.

func WithMetrics

func WithMetrics(reg prometheus.Registerer, name string) *MetricsConfig

WithMetrics returns a *MetricsConfig that wires the Cache to publish its Stats() snapshot as Prometheus metrics on the given Registerer. The name is required and labels every metric (cache="<name>"); two Cache instances sharing the same Registerer must use distinct names — otherwise the second New() call panics on duplicate registration. Pass nil reg or empty name to disable: the returned *MetricsConfig is nil-equivalent, no registration.

The published shape (CounterFunc-based, evaluated lazily on each scrape):

gokit_cache_hits_total{cache="<name>",tier="L1"}
gokit_cache_hits_total{cache="<name>",tier="L2"}
gokit_cache_misses_total{cache="<name>",tier="L1"}
gokit_cache_misses_total{cache="<name>",tier="L2"}
gokit_cache_evictions_total{cache="<name>"}
gokit_cache_size{cache="<name>"} (gauge — current L1 entry count)

Counters are monotonic since cache construction.

type RedisL2

type RedisL2 struct {
	// contains filtered or unexported fields
}

RedisL2 implements L2 using Redis.

func NewRedisL2

func NewRedisL2(redisURL string, db int, prefix string) *RedisL2

NewRedisL2 connects to Redis and returns an L2 store. Returns nil if the URL is empty or Redis is unreachable (logs a warning).

func (*RedisL2) Close

func (r *RedisL2) Close() error

Close closes the underlying Redis client. No-op if receiver is nil.

func (*RedisL2) Del

func (r *RedisL2) Del(ctx context.Context, key string) error

Del removes a key from Redis. Returns ErrL2Unavailable if receiver is nil or circuit breaker is open.

func (*RedisL2) Get

func (r *RedisL2) Get(ctx context.Context, key string) ([]byte, error)

Get retrieves a value from Redis by key. Returns ErrCacheMiss if the key does not exist or receiver is nil. Returns ErrL2Unavailable if the circuit breaker is open.

func (*RedisL2) Set

func (r *RedisL2) Set(ctx context.Context, key string, data []byte, ttl time.Duration) error

Set stores a value in Redis with the given TTL. Returns ErrL2Unavailable if receiver is nil or circuit breaker is open.

type Stats

type Stats struct {
	L1Hits      int64
	L1Misses    int64
	L1Size      int
	L2Hits      int64
	L2Misses    int64
	L2Errors    int64
	Evictions   int64
	HitRatio    float64
	TotalWeight int64 // sum of Weigher(k,v) for all live L1 entries; 0 when Weigher is nil
}

Stats holds cache statistics.

type Validator

type Validator func(cached []byte) bool

Validator inspects a cached value and reports whether it is still fresh against external state (file modTime+size, DB row updated_at, ETag, source hash, etc.).

Returning true keeps the entry and serves it to the caller. Returning false treats the entry as stale: the entry is evicted from L1, a metric is incremented, and the call falls through to L2 (or reports a miss when L2 is not configured).

Implementations MUST be cheap (Validator runs on every L1 hit) and safe for concurrent invocation.

Jump to

Keyboard shortcuts

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