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