cache

package
v0.43.0 Latest Latest
Warning

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

Go to latest
Published: Jun 18, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package cache provides interfaces and types for caching implementations. It follows the same patterns as the database package with vendor-agnostic interfaces and multi-tenant support via CacheManager.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound is returned when a cache key doesn't exist or has expired.
	// This is not considered a fatal error - callers should handle cache misses gracefully.
	ErrNotFound = errors.New("cache: key not found")

	// ErrCASFailed is reserved for callers that wish to surface a failed
	// compare-and-set as an error. The Redis client and mock report a failed
	// comparison via the boolean return value of CompareAndSet (not this error);
	// a false result indicates the current value didn't match the expected value.
	ErrCASFailed = errors.New("cache: compare-and-set failed")

	// ErrClosed is returned when attempting to use a closed cache connection.
	ErrClosed = errors.New("cache: connection closed")

	// ErrInvalidTTL is returned when a TTL value is invalid (e.g., negative).
	ErrInvalidTTL = errors.New("cache: invalid TTL")

	// ErrManagerClosed is returned by CacheManager operations (Get, Remove)
	// after Close() has been called. Callers can errors.Is(err, ErrManagerClosed)
	// to distinguish "manager is gone" from per-cache failures and decide
	// whether to abort or fall back to a non-cache path.
	ErrManagerClosed = errors.New("cache: manager closed")
)

Sentinel errors for common cache operations. Use errors.Is() to check for these specific error conditions.

Functions

func Marshal

func Marshal[T any](v T) ([]byte, error)

Marshal serializes a value to CBOR bytes. Returns an error if serialization fails.

The generic type parameter T allows type-safe serialization:

type User struct {
    ID    int64  `cbor:"1,keyasint"`  // Optimized: integer keys
    Name  string `cbor:"2,keyasint"`
    Email string `cbor:"3,keyasint"`
}

user := User{ID: 123, Name: "Alice", Email: "alice@example.com"}
data, err := cache.Marshal(user)

Struct tags are optional but recommended for optimization:

  • `cbor:"1,keyasint"` - Use integer keys (smaller encoding)
  • `cbor:"-"` - Skip field
  • `cbor:"field_name,omitempty"` - Omit zero values

See: https://github.com/fxamacker/cbor#struct-tags

func MustMarshal

func MustMarshal[T any](v T) []byte

MustMarshal is like Marshal but panics on error. Use this only when you're certain the value can be serialized (e.g., in tests or with pre-validated data).

Example:

data := cache.MustMarshal(User{ID: 123, Name: "Alice"})
cache.Set(ctx, "user:123", data, 5*time.Minute)

func MustUnmarshal

func MustUnmarshal[T any](data []byte) T

MustUnmarshal is like Unmarshal but panics on error. Use this only when you're certain the data is valid CBOR (e.g., in tests or with data you just marshaled).

Example:

data, _ := cache.Get(ctx, "user:123")
user := cache.MustUnmarshal[User](data) // Panics if data is invalid

func Unmarshal

func Unmarshal[T any](data []byte) (T, error)

Unmarshal deserializes CBOR bytes into a value. Returns the deserialized value and an error if deserialization fails.

The generic type parameter T specifies the expected type:

data := []byte{...} // CBOR-encoded User
user, err := cache.Unmarshal[User](data)
if err != nil {
    return err
}
fmt.Println(user.Name) // "Alice"

Security features:

  • MaxArrayElements: 10000 (prevents DoS)
  • MaxMapPairs: 10000 (prevents DoS)
  • MaxNestedLevels: 16 (prevents stack overflow)

For pointer types, use:

user, err := cache.Unmarshal[*User](data)

Types

type Cache

type Cache interface {
	// Get retrieves a value from the cache by key.
	// Returns ErrNotFound if the key doesn't exist or has expired.
	Get(ctx context.Context, key string) ([]byte, error)

	// Set stores a value in the cache with the specified TTL.
	// If ttl is 0, the value is stored without expiration (use with caution).
	// Overwrites existing values.
	Set(ctx context.Context, key string, value []byte, ttl time.Duration) error

	// Delete removes a value from the cache.
	// Returns nil if the key doesn't exist (idempotent operation).
	Delete(ctx context.Context, key string) error

	// GetOrSet atomically retrieves a value if it exists, or sets it if it doesn't.
	// Returns:
	//   - storedValue: the value currently stored in cache (either existing or newly set)
	//   - wasSet: true if the value was newly set, false if it already existed
	//   - error: any error that occurred
	//
	// This is useful for deduplication and idempotency patterns.
	//
	// Example (message deduplication):
	//
	//	stored, wasSet, err := cache.GetOrSet(ctx, "processed:msg:"+msgID, []byte("done"), 1*time.Hour)
	//	if !wasSet {
	//	    // Message already processed
	//	    return nil
	//	}
	//	// Process message for the first time
	GetOrSet(ctx context.Context, key string, value []byte, ttl time.Duration) (storedValue []byte, wasSet bool, err error)

	// CompareAndSet performs an atomic compare-and-set operation.
	// Sets the key to newValue only if the current value equals expectedValue.
	// Pass nil for expectedValue to set only if the key doesn't exist (SET NX semantics).
	// Returns true if the value was set, false otherwise.
	//
	// This is useful for distributed locking and optimistic concurrency control.
	//
	// Example (distributed lock):
	//
	//	acquired, err := cache.CompareAndSet(ctx, lockKey, nil, []byte("worker-1"), 30*time.Second)
	//	if !acquired {
	//	    return ErrLockHeld
	//	}
	//	defer cache.Delete(ctx, lockKey)
	CompareAndSet(ctx context.Context, key string, expectedValue, newValue []byte, ttl time.Duration) (success bool, err error)

	// Health checks the health of the cache connection.
	// Should be fast (<100ms) and safe to call frequently.
	Health(ctx context.Context) error

	// Stats returns cache statistics for observability.
	// Keys may include: connections_active, hits, misses, evictions, memory_used, etc.
	// The specific keys depend on the cache implementation.
	Stats() (map[string]any, error)

	// Close closes the cache connection and releases resources.
	// After calling Close, the cache instance should not be used.
	Close() error
}

Cache defines the core interface for cache operations. All implementations must be thread-safe and context-aware.

Example usage:

cache, release, err := cacheManager.Get(ctx, tenantID)
if err != nil {
    return err
}
defer release()

// Basic operations
err = cache.Set(ctx, "user:123", userData, 5*time.Minute)
data, err := cache.Get(ctx, "user:123")

// Deduplication
stored, wasSet, err := cache.GetOrSet(ctx, "lock:task:456", []byte("processing"), 30*time.Second)

// Distributed locking
acquired, err := cache.CompareAndSet(ctx, "lock:job:789", nil, []byte("worker-1"), 1*time.Minute)

type CacheManager added in v0.18.0

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

CacheManager implements the Manager interface for multi-tenant cache instances.

func NewCacheManager added in v0.18.0

func NewCacheManager(cfg ManagerConfig, connector Connector) (*CacheManager, error)

NewCacheManager creates a new cache manager with the given configuration. The connector function is called to create new cache instances on demand.

func (*CacheManager) Close added in v0.18.0

func (m *CacheManager) Close() error

Close shuts down all managed cache instances and stops the cleanup goroutine. After Close() returns, subsequent calls to Get() and Remove() return ErrManagerClosed.

func (*CacheManager) Get added in v0.18.0

func (m *CacheManager) Get(ctx context.Context, key string) (Cache, ReleaseFunc, error)

Get retrieves or creates a cache instance for the given key, plus a ReleaseFunc the caller must invoke when finished with it for the current unit of work (typically deferred). Returns ErrManagerClosed if Close() has been called. The lease prevents a cache instance evicted while in use from being closed under an active caller (the #606 race). On error the returned ReleaseFunc is nil — check err first.

func (*CacheManager) Remove added in v0.18.0

func (m *CacheManager) Remove(key string) error

Remove explicitly removes a cache instance from the manager. Returns ErrManagerClosed if Close() has been called.

func (*CacheManager) Stats added in v0.18.0

func (m *CacheManager) Stats() ManagerStats

Stats returns current manager statistics.

type ConfigError

type ConfigError struct {
	Field   string // Configuration field that failed validation
	Message string // Human-readable error message
	Err     error  // Underlying error, if any
}

ConfigError represents a configuration error during cache initialization. These errors are fail-fast and should panic at application startup.

func NewConfigError

func NewConfigError(field, message string, err error) *ConfigError

NewConfigError creates a new configuration error.

func (*ConfigError) Error

func (e *ConfigError) Error() string

Error implements the error interface.

func (*ConfigError) Unwrap

func (e *ConfigError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As support.

type ConfigProvider added in v0.26.0

type ConfigProvider interface {
	// CacheConfig returns the cache configuration for the given key.
	// For single-tenant apps, key will be "". For multi-tenant, key will be the tenant ID.
	CacheConfig(ctx context.Context, key string) (*config.CacheConfig, error)
}

ConfigProvider provides per-key cache configurations. This interface abstracts where tenant-specific cache configs come from.

type ConnectionError

type ConnectionError struct {
	Op      string // Operation that failed (e.g., "dial", "ping")
	Address string // Cache server address
	Err     error  // Underlying error
}

ConnectionError represents a cache connection error. These errors may be transient and could be retried.

func NewConnectionError

func NewConnectionError(op, address string, err error) *ConnectionError

NewConnectionError creates a new connection error.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

Error implements the error interface.

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As support.

type Connector added in v0.18.0

type Connector func(ctx context.Context, key string) (Cache, error)

Connector is a function that creates a new cache instance for a given key. This abstraction allows dependency injection for testing.

type Manager

type Manager interface {
	// Get returns a cache instance for the specified tenant plus a ReleaseFunc the caller
	// must invoke when finished with it for the current unit of work (typically deferred).
	// Creates a new connection if one doesn't exist (lazy initialization).
	// Uses singleflight to prevent duplicate connections for the same tenant.
	// Implements LRU eviction when max size is exceeded; the lease defers closing an
	// evicted-but-in-use instance until its last lease is released (ADR-032).
	Get(ctx context.Context, tenantID string) (Cache, ReleaseFunc, error)

	// Stats returns aggregated statistics across all managed caches.
	// Includes manager-specific metrics like total_connections, lru_evictions, etc.
	Stats() map[string]any

	// Close closes all managed cache connections and stops background cleanup.
	// Should be called during application shutdown.
	Close() error
}

Manager manages cache instances for multiple tenants. It follows the same pattern as database.DbManager with LRU eviction, singleflight for preventing stampedes, and idle connection cleanup.

Example usage:

manager := cache.NewManager(config, logger)
manager.StartCleanup(1 * time.Minute)
defer manager.Close()

// Get cache for tenant (auto-creates if needed); release the lease when done
inst, release, err := manager.Get(ctx, tenantID)
if err != nil {
    return err
}
defer release()

type ManagerConfig added in v0.18.0

type ManagerConfig struct {
	MaxSize         int           // Maximum number of active cache instances (0 = unlimited)
	IdleTTL         time.Duration // Idle timeout for cache instances (0 = never timeout)
	CleanupInterval time.Duration // How often to run idle cleanup (default: 5m)
}

ManagerConfig configures the cache manager's behavior.

func DefaultManagerConfig added in v0.18.0

func DefaultManagerConfig() ManagerConfig

DefaultManagerConfig returns sensible defaults for the cache manager.

type ManagerStats added in v0.18.0

type ManagerStats struct {
	ActiveCaches int   // Current number of active cache instances
	TotalCreated int   // Total caches created since manager start
	Evictions    int   // Total evictions due to LRU policy
	IdleCleanups int   // Total cleanups due to idle timeout
	Errors       int   // Total initialization and close errors
	MaxSize      int   // Maximum allowed active caches
	IdleTTL      int64 // Idle timeout duration in seconds
}

ManagerStats provides metrics about the cache manager's state.

type OperationError

type OperationError struct {
	Op  string // Operation that failed (e.g., "get", "set", "cas")
	Key string // Cache key involved in the operation
	Err error  // Underlying error
}

OperationError represents a cache operation error. These errors occur during cache operations (Get, Set, Delete, etc.).

func NewOperationError

func NewOperationError(op, key string, err error) *OperationError

NewOperationError creates a new operation error.

func (*OperationError) Error

func (e *OperationError) Error() string

Error implements the error interface.

func (*OperationError) Unwrap

func (e *OperationError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As support.

type ReleaseFunc added in v0.43.0

type ReleaseFunc func()

ReleaseFunc releases a lease obtained from Get. Callers must invoke it (typically deferred) when finished with the cache for the current unit of work. It is idempotent. Release does NOT close the shared cache instance; it signals this borrower is done, so a cache instance evicted while leased is closed only once its last lease is released. See ADR-032.

Directories

Path Synopsis
internal
Package testing provides utilities for testing cache logic in go-bricks applications.
Package testing provides utilities for testing cache logic in go-bricks applications.

Jump to

Keyboard shortcuts

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