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
- Variables
- func Marshal[T any](v T) ([]byte, error)
- func MustMarshal[T any](v T) []byte
- func MustUnmarshal[T any](data []byte) T
- func Unmarshal[T any](data []byte) (T, error)
- type Cache
- type CacheManager
- type ConfigError
- type ConnectionError
- type Connector
- type Manager
- type ManagerConfig
- type ManagerStats
- type OperationError
- type TenantCacheResourceSource
Constants ¶
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 )
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 ¶
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 ¶
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
func MustMarshal ¶
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 ¶
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 ¶
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
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
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.