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 ¶
- Variables
- func GetJSON[T any](c *Cache, ctx context.Context, key string) (T, bool, error)
- func GetOrLoadJSON[T any](c *Cache, ctx context.Context, key string, ...) (T, error)
- func Key(parts ...string) string
- func SetJSON[T any](c *Cache, ctx context.Context, key string, val T) error
- func SetJSONWithTTL[T any](c *Cache, ctx context.Context, key string, val T, ttl time.Duration) error
- type Cache
- func (c *Cache) Clear() int
- func (c *Cache) Close()
- func (c *Cache) Delete(ctx context.Context, key string)
- func (c *Cache) Get(ctx context.Context, key string) ([]byte, bool)
- func (c *Cache) GetIfValid(ctx context.Context, key string, validator Validator) ([]byte, bool)
- func (c *Cache) GetOrLoad(ctx context.Context, key string, loader func(context.Context) ([]byte, error)) ([]byte, error)
- func (c *Cache) GetOrLoadWithTTL(ctx context.Context, key string, ttl time.Duration, ...) ([]byte, error)
- func (c *Cache) InvalidateByTag(ctx context.Context, tag string) int
- func (c *Cache) Set(ctx context.Context, key string, data []byte)
- func (c *Cache) SetWithTTL(ctx context.Context, key string, data []byte, ttl time.Duration)
- func (c *Cache) SetWithTags(ctx context.Context, key string, data []byte, tags []string)
- func (c *Cache) SetWithTagsAndTTL(ctx context.Context, key string, data []byte, ttl time.Duration, tags []string)
- func (c *Cache) Stats() Stats
- func (c *Cache) Tags(key string) []string
- type Config
- type EvictReason
- type L2
- type MetricsConfig
- type RedisL2
- type Stats
- type Validator
Constants ¶
This section is empty.
Variables ¶
var ( // ErrCacheMiss indicates the requested key was not found in the cache. ErrCacheMiss = errors.New("cache: miss") ErrL2Unavailable = errors.New("cache: L2 unavailable") )
Sentinel errors for L2 cache operations.
Functions ¶
func GetJSON ¶
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.
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 ¶
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 ¶
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) Get ¶
Get retrieves a value from L1 (then L2 if configured). Returns nil, false on miss.
func (*Cache) GetIfValid ¶
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 ¶
InvalidateByTag removes all entries associated with the given tag from L1 and L2 (best-effort). Returns the number of entries removed.
func (*Cache) SetWithTTL ¶
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 ¶
SetWithTags stores a value with associated tags using global TTLs.
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 ¶
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) Del ¶
Del removes a key from Redis. 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 ¶
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.