cache

package module
v0.0.0-...-72be2ab Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2025 License: MIT Imports: 9 Imported by: 0

README

exp-go-cache

Note: This is an experimental project for exploring multi-tier caching strategies and design patterns in Go. The API may change and is not recommended for production use.

A flexible, type-safe multi-tier caching library for Go with support for local and remote cache backends.

Features

  • Type-Safe Generics: Fully generic implementation using Go 1.18+ generics for compile-time type safety
  • Multi-Tier Caching: Implements a tiered caching strategy (L1: Local Cache → L2: Remote Cache)
  • Two Caching Patterns:
    • TieredCache: Single-key operations with singleflight protection
    • BatchTieredCache: Multi-key batch operations with optimized pipeline support
  • Pluggable Backends: Support for multiple cache implementations
  • Flexible Serialization: Multiple encoding formats
    • JSON (default)
    • MessagePack for better performance and smaller payload size
  • Compute Function: Built-in support for cache-aside pattern with compute functions
  • Cache Stampede Protection: TieredCacher uses singleflight to prevent duplicate compute function executions
  • Batch Optimization: BatchTieredCacher uses Redis Pipeline for efficient multi-key operations
  • Context Support: Full context.Context support for cancellation and timeouts

Installation

go get github.com/naoto0822/exp-go-cache

Quick Start

Single-Key Operations with TieredCache

See examples/tiered_cache.go for a complete example.

Multi-Key Operations with BatchTieredCache

See examples/batch_tiered_cache.go for a complete example.

Caching Strategies

TieredCache - Single-Key Operations

Optimized for single-key lookups with cache stampede protection.

Cache Flow:

Get Request (single key)
    ↓
Check L1 (Local) ──Hit──→ Return Value
    ↓ Miss
Check L2 (Remote) ──Hit──→ Populate L1 → Return Value
    ↓ Miss
Execute Compute Function (via singleflight) → Populate L1 & L2 → Return Value

Cache Stampede Protection:

Uses singleflight to ensure only one compute function executes per key:

  • Without singleflight: 100 concurrent requests for same key = 100 database calls
  • With singleflight: 100 concurrent requests for same key = 1 database call (others wait and share result)
BatchTieredCache - Multi-Key Operations

Optimized for multi-key batch operations with efficient pipeline support.

Cache Flow:

BatchGet Request (multiple keys)
    ↓
BatchGet L1 (Local) ──Hits──→ Add to results
    ↓ Misses
BatchGet L2 (Remote via Pipeline) ──Hits──→ Populate L1 + Add to results
    ↓ Misses
Execute Batch Compute Function → Populate L1 & L2 → Add to results
    ↓
Return all results

Batch Optimization:

  • L1 (Ristretto): Simple loop (fast memory access)
  • L2 (Redis): Uses Pipeline for 1 network round-trip instead of N
  • Compute Function: Client can implement batch database query (N queries → 1 query)

Performance Comparison:

Operation Without Batch With Batch
100 keys Redis fetch 100 network calls 1 network call (Pipeline)
100 keys DB fetch 100 SQL queries 1 SQL query (IN clause)

When to Use:

  • TieredCache: Single key lookups, need stampede protection, high concurrency for same key
  • BatchTieredCache: Multiple keys at once, batch database support, minimize network overhead

Class Diagram

---
config:
  layout: dagre
---
classDiagram
direction LR
    class TieredCache {
	    -caches []Cacher
	    -sfGroup singleflight.Group
	    +Get(ctx, key, ttl, computeFn) value, error
	    +Set(ctx, key, value, ttl) error
	    +Delete(ctx, key) error
	    -getCache(ctx, key) value, int, bool, error
	    -setCache(ctx, key, value, ttl) error
	    -populateUpperTiers(ctx, key, value, ttl, tierIndex) error
    }
    class Cacher {
        +Get(ctx, key) value, error
        +Set(ctx, key, value, ttl) error
        +Delete(ctx, key) error
    }
    class BatchTieredCache {
	    -caches []BatchCacher
	    +BatchGet(ctx, keys, ttl, batchComputeFn) map, error
	    +BatchSet(ctx, items, ttl) error
	    -populateUpperTiers(ctx, items, ttl, tierIndex) error
    }
    class BatchCacher {
	    +Get(ctx, key) value, error
	    +Set(ctx, key, value, ttl) error
	    +Delete(ctx, key) error
	    +BatchGet(ctx, keys) map, error
	    +BatchSet(ctx, items, ttl) error
    }
    class RistrettoCache {
	    -cache ristretto.Cache
	    +Get(ctx, key) value, error
	    +Set(ctx, key, value, ttl) error
	    +Delete(ctx, key) error
	    +BatchGet(ctx, keys) map, error
	    +BatchSet(ctx, items, ttl) error
	    +Close() error
    }
    class RedisCache {
	    -client redis.Client
	    -coder Coder
	    +Get(ctx, key) value, error
	    +Set(ctx, key, value, ttl) error
	    +Delete(ctx, key) error
	    +BatchGet(ctx, keys) map, error
	    +BatchSet(ctx, items, ttl) error
	    +Close() error
    }
    class Coder {
	    +Encode(value) []byte, error
	    +Decode(data) value, error
    }
    class JSONSerializer {
	    +Encode(value) []byte, error
	    +Decode(data) value, error
    }
    class MsgpackSerializer {
	    +Encode(value) []byte, error
	    +Decode(data) value, error
    }

	<<interface>> Cacher
	<<interface>> BatchCacher
	<<interface>> Coder

    TieredCache o-- "0..*" Cacher : contains
    BatchTieredCache o-- "0..*" BatchCacher : contains
    RedisCache o-- Coder : uses
    JSONSerializer ..|> Coder : implements
    MsgpackSerializer ..|> Coder : implements
    RistrettoCache ..|> BatchCacher : implements
    RistrettoCache ..|> Cacher : implements
    RedisCache ..|> BatchCacher : implements
    RedisCache ..|> Cacher : implements

Performance Considerations

  • Ristretto uses approximate algorithms (TinyLFU) for admission and eviction, providing excellent hit ratios
  • Redis Pipeline batches multiple commands into single network round-trip (used in BatchTieredCache)
  • MessagePack encoding is faster and produces smaller payloads compared to JSON
  • Singleflight prevents thundering herd problem in TieredCache by deduplicating concurrent requests for the same key
  • Batch Operations minimize N+1 query problems by fetching multiple keys efficiently
  • Both cache tiers are populated on compute to maximize cache hits
  • Context cancellation is respected throughout the caching flow

Dependencies

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrCacheMiss indicates the key was not found in cache
	ErrCacheMiss = errors.New("cache miss")
)

Functions

This section is empty.

Types

type BatchCacher

type BatchCacher[V any] interface {
	Cacher[V]

	// BatchGet retrieves multiple values from cache
	// Returns a map of key-value pairs for found keys
	// Missing keys are simply not included in the returned map
	BatchGet(ctx context.Context, keys []string) (map[string]V, error)

	// BatchSet stores multiple values in cache with a TTL
	// All items share the same TTL
	BatchSet(ctx context.Context, items map[string]V, ttl time.Duration) error
}

BatchCacher defines the interface for cache implementations that support batch operations

type BatchComputeFunc

type BatchComputeFunc[V any] func(ctx context.Context, keys []string) (map[string]V, error)

BatchComputeFunc is a function that computes multiple values when cache misses occur It receives a slice of keys and returns a map of key-value pairs

type BatchLocalCacher deprecated

type BatchLocalCacher[V any] interface {
	BatchCacher[V]
}

Deprecated: Use BatchCacher instead BatchLocalCacher defines the interface for local cache implementations that support batch operations

type BatchRemoteCacher deprecated

type BatchRemoteCacher[V any] interface {
	BatchCacher[V]
}

Deprecated: Use BatchCacher instead BatchRemoteCacher defines the interface for remote cache implementations that support batch operations

type BatchTieredCache

type BatchTieredCache[V any] struct {
	// contains filtered or unexported fields
}

BatchTieredCache implements multi-key cache operations with tiered caching strategy Strategy: caches[0] (L1) → caches[1] (L2) → ... → caches[n] (Ln) Optimized for batch operations where the compute function can fetch multiple keys efficiently

func NewBatchTieredCache

func NewBatchTieredCache[V any](caches ...BatchCacher[V]) *BatchTieredCache[V]

NewBatchTieredCache creates a new batch tiered cache with dependency injection caches is a slice where caches[0] is L1 (fastest), caches[1] is L2, etc. Empty or nil caches in the slice are skipped

func (*BatchTieredCache[V]) BatchGet

func (bc *BatchTieredCache[V]) BatchGet(ctx context.Context, keys []string, ttl time.Duration, batchComputeFn BatchComputeFunc[V]) (map[string]V, error)

BatchGet retrieves multiple values using the tiered caching strategy: 1. Check L1, L2, ..., Ln in order using BatchGet 2. For each tier hit, populate upper tiers 3. For all misses, execute batchComputeFn to fetch all at once 4. Populate all tiers with computed values Returns a map of successfully retrieved values (key -> value)

func (*BatchTieredCache[V]) BatchSet

func (bc *BatchTieredCache[V]) BatchSet(ctx context.Context, items map[string]V, ttl time.Duration) error

BatchSet stores multiple values in all cache tiers All items share the same TTL

type Cacher

type Cacher[V any] interface {
	// Get retrieves a value from cache
	// Returns ErrCacheMiss if the key is not found
	Get(ctx context.Context, key string) (V, error)

	// Set stores a value in cache with a TTL
	Set(ctx context.Context, key string, value V, ttl time.Duration) error

	// Delete removes a value from cache
	// Returns ErrCacheMiss if the key is not found
	Delete(ctx context.Context, key string) error
}

Cacher defines the unified interface for cache implementations (local or remote) This interface can be used for multi-tier caching where caches[0] is L1, caches[1] is L2, etc.

type Coder

type Coder[V any] interface {
	// Encode serializes a value to bytes
	Encode(value V) ([]byte, error)

	// Decode deserializes bytes to a value
	Decode(data []byte) (V, error)
}

Coder defines the interface for encoding and decoding values

type ComputeFunc

type ComputeFunc[V any] func(ctx context.Context, key string) (V, error)

ComputeFunc is a function that computes the value when cache misses occur

type JSONCoder

type JSONCoder[V any] struct{}

JSONCoder implements Coder using JSON encoding

func NewJSONCoder

func NewJSONCoder[V any]() *JSONCoder[V]

NewJSONCoder creates a new JSONCoder instance

func (*JSONCoder[V]) Decode

func (c *JSONCoder[V]) Decode(data []byte) (V, error)

Decode deserializes JSON bytes to a value

func (*JSONCoder[V]) Encode

func (c *JSONCoder[V]) Encode(value V) ([]byte, error)

Encode serializes a value to JSON bytes

type LocalCacher deprecated

type LocalCacher[V any] interface {
	Cacher[V]
}

Deprecated: Use Cacher instead LocalCacher defines the interface for local cache implementations with generic type support

type MessagePackCoder

type MessagePackCoder[V any] struct {
	// contains filtered or unexported fields
}

MessagePackCoder implements Coder using MessagePack encoding

func NewMessagePackCoder

func NewMessagePackCoder[V any]() *MessagePackCoder[V]

NewMessagePackCoder creates a new MessagePackCoder instance

func (*MessagePackCoder[V]) Decode

func (c *MessagePackCoder[V]) Decode(data []byte) (V, error)

Decode deserializes MessagePack bytes to a value

func (*MessagePackCoder[V]) Encode

func (c *MessagePackCoder[V]) Encode(value V) ([]byte, error)

Encode serializes a value to MessagePack bytes

type RedisCache

type RedisCache[V any] struct {
	// contains filtered or unexported fields
}

RedisCache wraps go-redis client to implement the RemoteCacher interface with generic type support

func NewRedisCache

func NewRedisCache[V any](config *RedisCacheConfig, coder Coder[V]) (*RedisCache[V], error)

NewRedisCache creates a new RedisCache instance

func (*RedisCache[V]) BatchGet

func (r *RedisCache[V]) BatchGet(ctx context.Context, keys []string) (map[string]V, error)

BatchGet retrieves multiple values from Redis using Pipeline Returns a map of key-value pairs for found keys Missing keys are simply not included in the returned map

func (*RedisCache[V]) BatchSet

func (r *RedisCache[V]) BatchSet(ctx context.Context, items map[string]V, ttl time.Duration) error

BatchSet stores multiple values in Redis with a TTL using Pipeline All items share the same TTL

func (*RedisCache[V]) Close

func (r *RedisCache[V]) Close() error

Close closes the Redis connection

func (*RedisCache[V]) Delete

func (r *RedisCache[V]) Delete(ctx context.Context, key string) error

Delete removes a value from Redis

func (*RedisCache[V]) Get

func (r *RedisCache[V]) Get(ctx context.Context, key string) (V, error)

Get retrieves a value from Redis

func (*RedisCache[V]) Ping

func (r *RedisCache[V]) Ping(ctx context.Context) error

Ping checks if the Redis server is reachable

func (*RedisCache[V]) Set

func (r *RedisCache[V]) Set(ctx context.Context, key string, value V, ttl time.Duration) error

Set stores a value in Redis with a TTL

type RedisCacheConfig

type RedisCacheConfig struct {
	// Addr is the Redis server address (e.g., "localhost:6379")
	Addr string

	// Password for Redis authentication (optional)
	Password string

	// DB is the Redis database number (0-15, default is 0)
	DB int

	// DialTimeout is the timeout for establishing new connections
	DialTimeout time.Duration

	// ReadTimeout is the timeout for socket reads
	ReadTimeout time.Duration

	// WriteTimeout is the timeout for socket writes
	WriteTimeout time.Duration

	// PoolSize is the maximum number of socket connections
	PoolSize int

	// MinIdleConns is the minimum number of idle connections
	MinIdleConns int
}

RedisCacheConfig holds configuration for RedisCache

func DefaultRedisCacheConfig

func DefaultRedisCacheConfig() *RedisCacheConfig

DefaultRedisCacheConfig returns a default configuration

type RemoteCacher deprecated

type RemoteCacher[V any] interface {
	Cacher[V]
}

Deprecated: Use Cacher instead RemoteCacher defines the interface for remote cache implementations with generic type support

type RistrettoCache

type RistrettoCache[V any] struct {
	// contains filtered or unexported fields
}

RistrettoCache wraps ristretto cache to implement the LocalCacher interface with generic type support

func NewRistrettoCache

func NewRistrettoCache[V any](config *RistrettoCacheConfig) (*RistrettoCache[V], error)

NewRistrettoCache creates a new RistrettoCache instance

func (*RistrettoCache[V]) BatchGet

func (r *RistrettoCache[V]) BatchGet(ctx context.Context, keys []string) (map[string]V, error)

BatchGet retrieves multiple values from the cache Returns a map of key-value pairs for found keys Missing keys are simply not included in the returned map

func (*RistrettoCache[V]) BatchSet

func (r *RistrettoCache[V]) BatchSet(ctx context.Context, items map[string]V, ttl time.Duration) error

BatchSet stores multiple values in the cache with a TTL All items share the same TTL

func (*RistrettoCache[V]) Clear

func (r *RistrettoCache[V]) Clear()

Clear removes all items from the cache

func (*RistrettoCache[V]) Close

func (r *RistrettoCache[V]) Close() error

Close closes the cache and releases resources

func (*RistrettoCache[V]) Delete

func (r *RistrettoCache[V]) Delete(ctx context.Context, key string) error

Delete removes a value from the cache

func (*RistrettoCache[V]) Get

func (r *RistrettoCache[V]) Get(ctx context.Context, key string) (V, error)

Get retrieves a value from the cache

func (*RistrettoCache[V]) Metrics

func (r *RistrettoCache[V]) Metrics() *ristretto.Metrics

Metrics returns cache metrics from ristretto

func (*RistrettoCache[V]) Set

func (r *RistrettoCache[V]) Set(ctx context.Context, key string, value V, ttl time.Duration) error

Set stores a value in the cache with a TTL

type RistrettoCacheConfig

type RistrettoCacheConfig struct {
	// NumCounters determines the number of keys tracked for admission & eviction.
	// A good starting point is 10x the number of items you expect to keep in cache.
	NumCounters int64

	// MaxCost is the maximum total cost of items in cache.
	// When cost is set to 1 per item, this effectively limits the number of items.
	MaxCost int64

	// BufferItems is the size of the Get/Set buffers.
	// A larger buffer improves throughput but uses more memory.
	BufferItems int64
}

func DefaultRistrettoCacheConfig

func DefaultRistrettoCacheConfig() *RistrettoCacheConfig

type TieredCache

type TieredCache[V any] struct {
	// contains filtered or unexported fields
}

TieredCache implements a multi-tier caching strategy Strategy: caches[0] (L1) → caches[1] (L2) → ... → caches[n] (Ln) Uses singleflight to prevent cache stampede on compute function execution

func NewTieredCache

func NewTieredCache[V any](caches ...Cacher[V]) *TieredCache[V]

NewTieredCache creates a new multi-tier cache with dependency injection caches is a slice where caches[0] is L1 (fastest), caches[1] is L2, etc. Empty or nil caches in the slice are skipped

func (*TieredCache[V]) Delete

func (tc *TieredCache[V]) Delete(ctx context.Context, key string) error

Delete removes a key from all cache tiers

func (*TieredCache[V]) Get

func (tc *TieredCache[V]) Get(ctx context.Context, key string, ttl time.Duration, computeFn ComputeFunc[V]) (V, error)

Get retrieves a value using the tiered caching strategy with compute function: 1. Check L1, L2, ..., Ln in order 2. If found in Li (i > 0), populate upper tiers (L0 to Li-1) 3. If not found in any tier, execute computeFn and populate all tiers Uses singleflight to ensure only one compute function executes per key concurrently

func (*TieredCache[V]) Set

func (tc *TieredCache[V]) Set(ctx context.Context, key string, value V, ttl time.Duration) error

Set stores a value in all cache tiers

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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