cache

package
v0.20.0 Latest Latest
Warning

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

Go to latest
Published: Dec 18, 2025 License: MIT Imports: 9 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

View Source
const (
	// DefaultCacheIdleTTL is the default idle timeout for cached instances.
	// After this period of inactivity, a cache will be closed and evicted.
	// Used in manager.go and manager_test.go (10+ occurrences).
	DefaultCacheIdleTTL = 15 * time.Minute

	// DefaultCleanupInterval is the default frequency for running idle cleanup checks.
	// The cleanup goroutine wakes up at this interval to check for expired caches.
	// Used in manager.go (5+ occurrences).
	DefaultCleanupInterval = 5 * time.Minute

	// DefaultMaxSize is the default maximum number of tenant cache instances.
	// When exceeded, the oldest (LRU) cache is evicted.
	// Zero means unlimited caches.
	DefaultMaxSize = 100
)
View Source
const (
	// TestSlowCreationDelay simulates slow cache creation in concurrency tests.
	// Used in manager_test.go to test race conditions and singleflight behavior.
	TestSlowCreationDelay = 50 * time.Millisecond

	// TestCleanupWaitPeriod is the wait time for cleanup goroutine to execute.
	// Used in manager_test.go to verify idle cache eviction (3+ occurrences).
	TestCleanupWaitPeriod = 300 * time.Millisecond

	// TestShortTTL is a very short TTL for testing expiration behavior.
	// Used in integration tests to verify cache entries expire correctly.
	TestShortTTL = 100 * time.Millisecond

	// TestMediumTTL is a moderate TTL for test data that should persist during test execution.
	TestMediumTTL = 5 * time.Second

	// TestLongTTL is a long TTL for test data that should not expire during tests.
	TestLongTTL = 10 * time.Minute
)

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 returned when a CompareAndSet operation fails because
	// the current value doesn't match the expected value.
	// This indicates a concurrent modification or lock contention.
	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")
)

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, err := cacheManager.Get(ctx, tenantID)
if err != nil {
    return err
}

// 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.

func (*CacheManager) Get added in v0.18.0

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

Get retrieves or creates a cache instance for the given key.

func (*CacheManager) Remove added in v0.18.0

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

Remove explicitly removes a cache instance from the manager.

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 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.
	// 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.
	Get(ctx context.Context, tenantID string) (Cache, 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)
cache, err := manager.Get(ctx, tenantID)

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 TenantCacheResourceSource added in v0.18.0

type TenantCacheResourceSource 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)
}

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

Directories

Path Synopsis
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