cache

package
v0.17.1 Latest Latest
Warning

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

Go to latest
Published: Nov 13, 2025 License: MIT Imports: 5 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 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 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.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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