cache

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 27, 2026 License: MIT Imports: 18 Imported by: 0

README

cache

Documentation

Overview

Package cache provides a robust hybrid cache for distributed systems, inspired by ZiggyCreatures FusionCache.

Index

Constants

This section is empty.

Variables

View Source
var ErrFactoryHardTimeout = fmt.Errorf("cache: factory hard timeout")

ErrFactoryHardTimeout is returned when the factory exceeds the hard timeout and no stale fail safe value is available.

View Source
var ErrLockTimeout = fmt.Errorf("cache: stampede lock timeout")

ErrLockTimeout is returned when LockTimeout elapses before the stampede lock is acquired. The caller may proceed without the lock (best-effort).

Functions

func Get

func Get[T any](ctx context.Context, c Cache, key string, opts ...EntryOption) (T, bool, error)

func GetOrSet

func GetOrSet[T any](
	ctx context.Context,
	c Cache,
	key string,
	factory func(ctx context.Context) (T, error),
	opts ...EntryOption,
) (T, error)

Types

type Cache

type Cache interface {
	// Get returns the cached value for key if present and not logically expired.
	// Returns (nil, false, nil) on a clean cache miss.
	Get(ctx context.Context, key string, opts ...EntryOption) (any, bool, error)

	// Set stores value under key in all configured cache layers.
	Set(ctx context.Context, key string, value any, opts ...EntryOption) error

	// GetOrSet returns the cached value for key. On a cache miss it calls factory,
	// stores the result, and returns it. This is the primary method.
	GetOrSet(ctx context.Context, key string, factory FactoryFunc, opts ...EntryOption) (any, error)

	// Delete removes the entry from all cache layers and notifies the backplane.
	Delete(ctx context.Context, key string, opts ...EntryOption) error

	// DeleteByTag removes all entries associated with tag from all layers.
	DeleteByTag(ctx context.Context, tag string, opts ...EntryOption) error

	// Expire marks the entry as logically expired without removing it.
	// The value remains available as a fail-safe fallback until physical expiry.
	// If fail-safe is not enabled, this is equivalent to Delete.
	Expire(ctx context.Context, key string, opts ...EntryOption) error

	// Clear removes all entries from L1. If clearL2 is true and an L2 adapter
	// is configured, it calls Clear on the adapter as well.
	Clear(ctx context.Context, clearL2 bool) error

	// Name returns the configured cache name.
	Name() string

	// DefaultEntryOptions returns a copy of the cache-wide default entry options.
	DefaultEntryOptions() EntryOptions

	// Events returns the EventEmitter for subscribing to cache lifecycle events.
	Events() *EventEmitter

	// Close shuts down background goroutines and releases resources.
	// It is safe to call Close multiple times.
	Close() error
}

func New

func New(opts ...Option) (Cache, error)

type EntryOption

type EntryOption func(*EntryOptions)

EntryOption modifies an EntryOptions. Compose multiple options freely.

func WithAllowStaleOnReadOnly

func WithAllowStaleOnReadOnly() EntryOption

func WithAutoClone

func WithAutoClone() EntryOption

func WithBackgroundL2Ops

func WithBackgroundL2Ops() EntryOption

func WithDistributedCacheTimeouts

func WithDistributedCacheTimeouts(soft, hard time.Duration) EntryOption

func WithDuration

func WithDuration(d time.Duration) EntryOption

func WithEagerRefresh

func WithEagerRefresh(threshold float32) EntryOption

func WithFactoryTimeouts

func WithFactoryTimeouts(soft, hard time.Duration) EntryOption

func WithFailSafe

func WithFailSafe(maxDuration, throttleDuration time.Duration) EntryOption

func WithJitter

func WithJitter(max time.Duration) EntryOption

func WithLockTimeout

func WithLockTimeout(d time.Duration) EntryOption

func WithPriority

func WithPriority(p EvictionPriority) EntryOption

func WithSize

func WithSize(n int64) EntryOption

func WithSkipL2

func WithSkipL2() EntryOption

func WithSkipL2ReadWhenStale

func WithSkipL2ReadWhenStale() EntryOption

func WithTags

func WithTags(tags ...string) EntryOption

type EntryOptions

type EntryOptions struct {
	// Duration is how long the entry is considered fresh (logically valid).
	// When fail-safe is enabled the physical TTL in backing stores is
	// FailSafeMaxDuration; Duration marks the "stale after" boundary.
	Duration time.Duration

	// DistributedCacheDuration overrides Duration for the L2 layer only.
	// Zero means "use Duration".
	DistributedCacheDuration time.Duration

	// JitterMaxDuration adds a random extra TTL in [0, JitterMaxDuration) to
	// both L1 and L2 entries. Prevents thundering-herd expiry spikes across
	// nodes in multi-instance deployments. Zero disables jitter.
	JitterMaxDuration time.Duration

	// IsFailSafeEnabled activates the fail-safe mechanism: if a factory call
	// or L2 fetch fails and a stale entry exists, the stale value is returned
	// rather than propagating the error.
	IsFailSafeEnabled bool

	// FailSafeMaxDuration is the total lifetime of an entry in the backing
	// store when fail-safe is on. The entry will be physically present (but
	// logically stale) for this duration after it was first written, enabling
	// it to be used as a fallback.
	// Must be >= Duration when fail-safe is enabled.
	FailSafeMaxDuration time.Duration

	// DistributedCacheFailSafeMaxDuration overrides FailSafeMaxDuration for
	// the L2 physical TTL. Zero means "use FailSafeMaxDuration".
	DistributedCacheFailSafeMaxDuration time.Duration

	// FailSafeThrottleDuration is how long a fail-safe-activated stale value
	// is temporarily promoted back to "fresh" in L1 to prevent the factory
	// from being hammered again immediately after an error.
	FailSafeThrottleDuration time.Duration

	// AllowStaleOnReadOnly permits stale (logically expired) values to be
	// returned from read-only operations (Get) without triggering a factory call.
	AllowStaleOnReadOnly bool

	// FactorySoftTimeout is the maximum time to wait for the factory before
	// returning a stale fail-safe value to the caller. The factory continues
	// running in the background and caches its result when done.
	// Zero means no soft timeout.
	FactorySoftTimeout time.Duration

	// FactoryHardTimeout is the absolute maximum time to wait for the factory.
	// After this the call returns an error (or stale value if fail-safe is on),
	// regardless of whether a stale value is available.
	// Zero means wait indefinitely.
	FactoryHardTimeout time.Duration

	// AllowTimedOutFactoryBackgroundCompletion: when true, a factory that
	// triggered a soft or hard timeout (but eventually succeeds) will have its
	// result stored in the cache. Default: true.
	AllowTimedOutFactoryBackgroundCompletion bool

	// DistributedCacheSoftTimeout is the max time to wait for an L2 read/write
	// before falling back to a stale value (fail-safe must be on for a fallback
	// to be available). Zero means no soft timeout.
	DistributedCacheSoftTimeout time.Duration

	// DistributedCacheHardTimeout is the absolute max time for any L2 operation.
	// Zero means wait indefinitely.
	DistributedCacheHardTimeout time.Duration

	// AllowBackgroundDistributedCacheOperations: when true, L2 writes are
	// fire-and-forget goroutines. This improves latency but means a write
	// failure is logged rather than returned to the caller.
	// Default: false (blocking, deterministic behaviour).
	AllowBackgroundDistributedCacheOperations bool

	// EagerRefreshThreshold: when a cache hit occurs after this fraction of
	// Duration has elapsed, a background factory call is started to refresh
	// the entry before it expires, so callers never observe a miss.
	// Value must be in (0.0, 1.0); zero or values outside this range disable
	// eager refresh.
	// Example: 0.9 starts refreshing at 90% of Duration elapsed.
	EagerRefreshThreshold float32

	// SkipBackplaneNotifications: if true, mutations (Set/Delete/Expire) will
	// not publish backplane messages for this operation.
	SkipBackplaneNotifications bool

	// AllowBackgroundBackplaneOperations: when true, backplane publishes are
	// fire-and-forget goroutines. Default: true.
	AllowBackgroundBackplaneOperations bool

	// ReThrowBackplaneExceptions: when true and AllowBackgroundBackplaneOperations
	// is false, backplane publish errors are returned to the caller.
	ReThrowBackplaneExceptions bool

	// ReThrowDistributedCacheExceptions: when true and AllowBackgroundDistributedCacheOperations
	// is false, L2 errors are returned to the caller.
	ReThrowDistributedCacheExceptions bool

	// ReThrowSerializationExceptions: when true, serialization errors during L2
	// reads/writes are returned to the caller. Default: true.
	ReThrowSerializationExceptions bool

	// Priority hints to the L1 eviction policy. Higher priority entries are
	// evicted last under memory pressure. Default: PriorityNormal.
	Priority EvictionPriority

	// Size is an arbitrary weight unit used by L1 when a SizeLimit is configured
	// on the cache. Typically represents relative byte size or item weight.
	Size int64

	// SkipL1Read: bypass reading from the in-process memory cache (L1).
	// Use with care, removes stampede protection.
	SkipL1Read bool

	// SkipL1Write: bypass writing to L1 after a factory call or L2 hit.
	SkipL1Write bool

	// SkipL2Read: bypass reading from the distributed cache (L2).
	SkipL2Read bool

	// SkipL2Write: bypass writing to L2.
	SkipL2Write bool

	// SkipL2ReadWhenStale: when L1 has a stale entry, skip checking L2 for a
	// newer version. Useful when L2 is local (not shared across nodes).
	SkipL2ReadWhenStale bool

	// Tags associates string labels with this entry for bulk invalidation via
	// DeleteByTag. Tags are stored in both L1 (in-memory reverse index) and L2
	// (implementation-defined, e.g., a Redis SET per tag).
	Tags []string

	// LockTimeout is the maximum time to wait to acquire the stampede protection
	// lock for this key. After this, the caller proceeds without the lock
	// (risks a mini-stampede but prevents indefinite starvation).
	// Zero means wait indefinitely.
	LockTimeout time.Duration

	// EnableAutoClone: when true, values returned from L1 are deep-cloned
	// (via the Serializer: marshal -> unmarshal) before being returned to the
	// caller. Prevents callers from inadvertently mutating cached objects.
	// Requires a Serializer to be configured.
	EnableAutoClone bool
}

EntryOptions holds all per-entry settings. It is a plain value type; cache copies it on every call so mutations never affect the stored defaults.

type Event

type Event interface {
	// contains filtered or unexported methods
}

Event is the marker interface for all cache events.

type EventBackplaneCircuitBreakerStateChange

type EventBackplaneCircuitBreakerStateChange struct{ Open bool }

type EventBackplaneReceived

type EventBackplaneReceived struct {
	Key  string
	Type backplane.MessageType
}

type EventBackplaneSent

type EventBackplaneSent struct {
	Key  string
	Type backplane.MessageType
}

type EventCacheHit

type EventCacheHit struct {
	Key     string
	IsStale bool
}

type EventCacheMiss

type EventCacheMiss struct{ Key string }

type EventEagerRefreshComplete

type EventEagerRefreshComplete struct{ Key string }

type EventEagerRefreshStarted

type EventEagerRefreshStarted struct{ Key string }

type EventEmitter

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

EventEmitter is a simple in-process event bus. Safe for concurrent use.

func (*EventEmitter) On

func (e *EventEmitter) On(handler EventHandler) (unsubscribe func())

type EventFactoryCall

type EventFactoryCall struct{ Key string }

type EventFactoryError

type EventFactoryError struct {
	Key string
	Err error
}

type EventFactorySuccess

type EventFactorySuccess struct {
	Key    string
	Shared bool
}

type EventFailSafeActivated

type EventFailSafeActivated struct {
	Key        string
	StaleValue any
}

type EventHandler

type EventHandler func(Event)

EventHandler is a callback invoked for each cache event. Handlers are called synchronously on the goroutine that produced the event.

type EventHardTimeoutActivated

type EventHardTimeoutActivated struct{ Key string }

type EventL2CircuitBreakerStateChange

type EventL2CircuitBreakerStateChange struct{ Open bool }

type EventL2Error

type EventL2Error struct {
	Key string
	Err error
}

type EventL2Hit

type EventL2Hit struct{ Key string }

type EventL2Miss

type EventL2Miss struct{ Key string }

type EventSoftTimeoutActivated

type EventSoftTimeoutActivated struct{ Key string }

type EvictionPriority

type EvictionPriority int

EvictionPriority hints to the L1 eviction policy.

const (
	PriorityLow         EvictionPriority = -1
	PriorityNormal      EvictionPriority = 0 // default
	PriorityHigh        EvictionPriority = 1
	PriorityNeverRemove EvictionPriority = 2
)

type FactoryFunc

type FactoryFunc func(ctx context.Context) (any, error)

FactoryFunc is the function called on a cache miss to produce a fresh value. It receives the request context and should respect cancellation.

type Option

type Option func(*Options)

Option configures an Options at construction time.

func WithBackplane

func WithBackplane(bp backplane.Backplane) Option

func WithBackplaneCircuitBreaker

func WithBackplaneCircuitBreaker(threshold int, openDuration time.Duration) Option

func WithCacheName

func WithCacheName(name string) Option

func WithDefaultEntryOptions

func WithDefaultEntryOptions(eo EntryOptions) Option

func WithKeyPrefix

func WithKeyPrefix(prefix string) Option

func WithL1

func WithL1(adapter l1.Adapter) Option

func WithL2

func WithL2(adapter l2.Adapter) Option

func WithL2CircuitBreaker

func WithL2CircuitBreaker(threshold int, openDuration time.Duration) Option

func WithLogger

func WithLogger(logger *slog.Logger) Option

func WithNodeID

func WithNodeID(id string) Option

func WithSerializer

func WithSerializer(s serializer.Serializer) Option

type Options

type Options struct {
	// CacheName identifies this cache instance in logs, events, and backplane messages.
	// Default: "default"
	CacheName string

	// KeyPrefix is prepended to every key before it is passed to L1, L2, and the
	// backplane. Enables namespace isolation when multiple caches share an L2 backend.
	// Example: "myapp:products:"
	KeyPrefix string

	// DefaultEntryOptions is the baseline EntryOptions for every cache operation.
	// Per-call EntryOption funcs are applied on top of a copy of this value.
	DefaultEntryOptions EntryOptions

	// L1 is the in-process memory cache adapter.
	// If nil, a default sync.Map-backed adapter is used.
	L1 l1.Adapter

	// L2 is the distributed cache adapter.
	// If nil, cache operates as a pure in-process memory cache.
	L2 l2.Adapter

	// Serializer is required when L2 is non-nil.
	// It encodes/decodes Go values to/from []byte for L2 storage.
	Serializer serializer.Serializer

	// Backplane enables inter-node invalidation.
	// If nil, no cross-node notifications are sent or received.
	Backplane backplane.Backplane

	// Logger is the structured logger for internal diagnostics.
	// If nil, all logging is silently discarded.
	Logger *slog.Logger

	// NodeID uniquely identifies this cache node in backplane messages.
	// Used to suppress processing of self-sent notifications.
	// If empty, a random UUID is generated at construction time.
	NodeID string

	// DistributedCacheCircuitBreakerThreshold is the number of consecutive L2
	// errors that cause the circuit breaker to open (reject further L2 calls).
	// 0 disables the L2 circuit breaker entirely.
	DistributedCacheCircuitBreakerThreshold int

	// DistributedCacheCircuitBreakerDuration is how long the L2 circuit
	// breaker stays open before attempting recovery.
	DistributedCacheCircuitBreakerDuration time.Duration

	// BackplaneCircuitBreakerThreshold is the consecutive-failure threshold
	// for the backplane circuit breaker. 0 disables it.
	BackplaneCircuitBreakerThreshold int

	// BackplaneCircuitBreakerDuration is how long the backplane circuit
	// breaker stays open. Only relevant when threshold > 0.
	BackplaneCircuitBreakerDuration time.Duration

	// BackplaneAutoRecovery enables automatic L1 re-sync when the backplane
	// reconnects after an outage. Default: true.
	BackplaneAutoRecovery bool

	// AutoRecoveryMaxRetries is the maximum number of recovery attempts.
	// Default: 10.
	AutoRecoveryMaxRetries int

	// AutoRecoveryDelay is the pause between recovery attempts.
	// Default: 2s.
	AutoRecoveryDelay time.Duration

	// IgnoreIncomingBackplaneNotifications disables acting on received backplane
	// messages. Useful for read-only replicas or during testing.
	IgnoreIncomingBackplaneNotifications bool

	// SkipL2OnError: if true (default), L2 errors are logged and swallowed.
	// cache continues with L1 only. If false, L2 errors propagate to callers
	// unless overridden by the per-entry ReThrowDistributedCacheExceptions flag.
	SkipL2OnError bool
}

Options configures the entire cache instance. Set once via Option funcs passed to New(). Never mutate after construction.

Directories

Path Synopsis
adapters
backplane/memory
Package memory provides an in-process backplane using Go channels.
Package memory provides an in-process backplane using Go channels.
backplane/noop
Package noop provides a backplane that silently discards all messages.
Package noop provides a backplane that silently discards all messages.
l2/memory
Package memory is an implementation of the L2 cache using an in-memory L2 cache, backed by sync.Map.
Package memory is an implementation of the L2 cache using an in-memory L2 cache, backed by sync.Map.
serializer/json
Package json is an implementation of the cache Serializer using encoding/json.
Package json is an implementation of the cache Serializer using encoding/json.
l2/redis module
Package backplane provides the interfaces for implementing a backplane in the core cache package.
Package backplane provides the interfaces for implementing a backplane in the core cache package.
internal
clock
Package clock provides a time abstraction so cache internals can be tested without sleeping or time.Sleep-based races.
Package clock provides a time abstraction so cache internals can be tested without sleeping or time.Sleep-based races.
Package l2 provides the adapter interface for the core cache package.
Package l2 provides the adapter interface for the core cache package.
Package serializer provides the Serializer interface for the core cache package.
Package serializer provides the Serializer interface for the core cache package.

Jump to

Keyboard shortcuts

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