shardedcache

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2025 License: MIT Imports: 11 Imported by: 2

README

Sharded In-Memory Cache

A high-performance sharded in-memory cache for Go with group namespaces, disk persistence, and comprehensive cache operations. Designed for concurrent access with excellent performance characteristics.

Features

  • Persistence Support: Disk persistence and automatic reload on startup
  • Type Safety: Generic GetTyped[T]() for type-safe value retrieval
  • Group Support: Namespace management for organized cache operations
  • Expiration Control: Flexible expiration with NoExpiration and DefaultExpiration constants
  • Statistics: Detailed cache statistics including hit rates and item counts
  • Eviction Callbacks: Custom handlers for item eviction events
  • Batch Operations: Efficient bulk delete operations with prefix/key matching

Installation

go get github.com/simp-lee/cache

Quick Start

Basic Usage
package main

import (
    "fmt"
    "time"
    shardedcache "github.com/simp-lee/cache"
)

func main() {
    // Create cache instance
    cache := shardedcache.NewCache(shardedcache.Options{
        MaxSize:           1000,
        DefaultExpiration: time.Minute * 5,
        ShardCount:        32, // Will auto-adjust to nearest power of two if needed
    })

    // Basic operations
    cache.Set("key", "value")
    value, exists := cache.Get("key")
    if exists {
        fmt.Println(value) // Output: value
    }

    // With expiration
    cache.SetWithExpiration("temp", "data", time.Hour)
    
    // Type-safe retrieval
    str, exists := shardedcache.GetTyped[string](cache, "key")
    
    // Utility functions
    result := cache.GetOrSet("new_key", "default_value")
    result = cache.GetOrSetFunc("computed", func() interface{} {
        return "expensive computation"
    })
}
Singleton Pattern
// Initialize once at startup
shardedcache.Init(shardedcache.Options{
    DefaultExpiration: time.Hour,
    ShardCount:        64,
})

// Use anywhere in your application
cache := shardedcache.Get()
cache.Set("global_key", "value")
Expiration & Cleanup
// Expiration constants
const (
    NoExpiration      time.Duration = -1 // Item never expires
    DefaultExpiration time.Duration = 0  // Use cache's default expiration
)

// Usage examples
cache := shardedcache.NewCache(shardedcache.Options{
    DefaultExpiration: time.Hour,
    // CleanupInterval > 0 starts a background goroutine that periodically removes expired entries.
    // Setting CleanupInterval to 0 disables periodic cleanup (access still lazily removes expired items).
})

cache.SetWithExpiration("permanent", "value", shardedcache.NoExpiration)
cache.SetWithExpiration("default", "value", shardedcache.DefaultExpiration)
cache.SetWithExpiration("custom", "value", time.Minute*30)

// Safe expiration time access (returns time.Time values, not pointers)
value, expTime, exists := cache.GetWithExpiration("key")
if exists {
    if !expTime.IsZero() { // NoExpiration will yield zero time
        remaining := time.Until(expTime)
        fmt.Printf("Expires in: %v\n", remaining)
    }
    _ = value
}

// Expired entries are:
// 1) Removed lazily on access (Get / GetWithExpiration)
// 2) Removed eagerly by the periodic cleaner if CleanupInterval > 0
Group Support

Groups provide namespace isolation for cache keys with (practically) zero additional memory (only a tiny wrapper object; no separate storage structure):

// Create groups for different data types
users := cache.Group("users")
posts := cache.Group("posts")

// Groups are logically isolated (implemented via key prefix `<group>\x00:`)
users.Set("1", userData)
posts.Set("1", postData)  // Different from users.Get("1")

// All cache operations available in groups
users.SetWithExpiration("session:123", sessionData, time.Hour)
value := users.GetOrSetFunc("profile:456", func() interface{} {
    return fetchUserProfile("456")
})

// Group-specific operations
users.Clear()                                    // Clear only user data
deleted := users.DeletePrefix("session:")        // Delete user sessions
deleted = users.DeleteKeys([]string{"1", "2"})   // Delete specific users
Advanced Features
Type-Safe Operations
// Store typed data
cache.Set("user", User{Name: "John", Age: 30})

// Retrieve with type safety
user, exists := shardedcache.GetTyped[User](cache, "user")
if exists {
    fmt.Println(user.Name) // Direct access to User fields
}
Statistics and Monitoring
// Set eviction callback
cache.OnEvicted(func(key string, value interface{}) {
    log.Printf("Item evicted: %s", key)
})

// Get cache statistics (keys are camelCase)
stats := cache.Stats()
// Provided fields:
// count, hits, misses, hitRate,
// maxSize, defaultExpiration, cleanupInterval,
// persistInterval, persistThreshold, shardCount, persistPath
fmt.Printf("Hit rate: %.2f\n", stats["hitRate"]) // hits / (hits + misses)
fmt.Printf("Total items: %d\n", cache.Count())
Persistence
cache := shardedcache.NewCache(shardedcache.Options{
    PersistPath:      "/tmp/cachedata", // Directory path, NOT a single file
    PersistInterval:  time.Minute * 5,   // Periodic flush (0 disables; negative input coerced to default 13m)
    PersistThreshold: 100,               // Flush after N mutations (0 = every change; negative coerced to 100)
})
// Persistence details:
// - A directory is created if missing.
// - Each shard stores its own file: <PersistPath>/<shardIndex>.dat
// - Triggers:
//   * Threshold-based (mutations reach PersistThreshold)  -- requires PersistInterval > 0 (persister goroutine must be running)
//   * Time-based (ticker at PersistInterval if there are changes)
//   * Final flush on Close()
// - Loading: On startup each shard attempts to load its corresponding file.
// - Encoding: Go gob (interface{}). For custom concrete types register them, e.g.:
//     gob.Register(MyStruct{})
// Recommendation: Call Close() on graceful shutdown to ensure last changes are flushed.
Concurrency & Singleflight-like Behavior

GetOrSetFunc / GetOrSetFuncWithExpiration avoid duplicate computation for the same key via shard-level locking (not per-key lock). A shard lock means different keys on the same shard may serialize during the computation window; once value stored, later goroutines read it.

Eviction & Capacity

MaxSize applies per shard (effective total capacity = MaxSize * shardCount). When adding a new key beyond capacity, the oldest (by creation time) item in that shard is evicted. This is a simple FIFO-by-insert-time policy (not LRU). For LRU/FIFO policy sophistication, see SwiftCache.

Key Enumeration Complexity

Keys() and Count() iterate all shards (O(N)). Group Count() first builds the group's key list (also O(N)). Avoid invoking them on very hot paths for extremely large caches.

Configuration

type Options struct {
    MaxSize           int           // Max items per shard (0 = unlimited; negative -> coerced to 0)
    DefaultExpiration time.Duration // Default expiration (<=0 -> no expiration; negative coerced to 0)
    CleanupInterval   time.Duration // Background expiry cleanup (0 disables; negative input coerced to default 11m)
    PersistPath       string        // Directory path for shard files (empty -> disable persistence)
    PersistInterval   time.Duration // Periodic persistence (0 disables; negative input coerced to default 13m)
    PersistThreshold  int           // Mutations threshold (0 -> every change; negative coerced to 100; threshold requires PersistInterval > 0 to flush immediately)
    ShardCount        int           // Desired shard count (auto-rounded up to next power of 2; <=0 -> default 32)
}

Behavior Summary:
- Shard auto-adjust: e.g. 33 -> 64
- Negative CleanupInterval/PersistInterval are coerced to defaults (11m / 13m) rather than disabling
- PersistThreshold only effective during runtime if PersistInterval > 0 (persister goroutine active)
- Negative values for sizes/thresholds coerced to safe defaults
- Persistence stores per-shard gob files
- Close() flushes pending changes
- Clear() resets hits & misses counters and triggers eviction callbacks for each item

API Reference

Signatures (concise overview)
// Core
Set(key string, value interface{})
SetWithExpiration(key string, value interface{}, expiration time.Duration)
Get(key string) (value interface{}, found bool)
GetWithExpiration(key string) (value interface{}, expireTime time.Time, found bool)
Delete(key string) bool
Has(key string) bool
Clear()                  // Clears all cache items and resets hit/miss counters
Close()                  // Flush persistence & stop background goroutines

// Batch & Introspection
DeleteKeys(keys []string) (deleted int)
DeletePrefix(prefix string) (deleted int)   // prefix == "" -> no-op (use Clear())
Keys() []string
Count() int
Stats() map[string]interface{}              // Fields: count,hits,misses,hitRate,maxSize,defaultExpiration,cleanupInterval,persistInterval,persistThreshold,shardCount,persistPath
OnEvicted(cb func(key string, value interface{}))

// Get-Or-Set helpers (single computation per key per shard)
GetOrSet(key string, defaultValue interface{}) interface{}
GetOrSetFunc(key string, f func() interface{}) interface{}
GetOrSetFuncWithExpiration(key string, f func() interface{}, expiration time.Duration) interface{}

// Generic helper
GetTyped[T any](c CacheInterface, key string) (val T, found bool) // returns zero T + false on miss or type mismatch

// Group interface (obtained via cache.Group(name))
type Group interface {
    Set(key string, value interface{})
    SetWithExpiration(key string, value interface{}, expiration time.Duration)
    Get(key string) (interface{}, bool)
    GetWithExpiration(key string) (interface{}, time.Time, bool)
    Delete(key string) bool
    DeleteKeys(keys []string) int
    DeletePrefix(prefix string) int
    GetOrSet(key string, defaultValue interface{}) interface{}
    GetOrSetFunc(key string, f func() interface{}) interface{}
    GetOrSetFuncWithExpiration(key string, f func() interface{}, expiration time.Duration) interface{}
    Keys() []string
    Count() int
    Has(key string) bool
    Clear()
}
Notes
  • Groups are implemented by prefixing keys with <group>\x00:; avoid raw keys containing \x00: if possible.
  • Calling shardedcache.Get() before Init() will panic.
  • Eviction policy when MaxSize exceeded is oldest-by-creation (FIFO per shard).
  • Shard count auto-rounded up to the next power of two.
  • Persistence writes one gob file per shard inside the provided directory.
  • sync.Pool reduces allocations for items & time objects.

Performance

Designed for high efficiency and performance in concurrent environments. The benchmarks below were produced on the following test environment:

Test Environment:

  • CPU: 13th Gen Intel(R) Core(TM) i5-13500H
  • OS: Windows 11 (windows/amd64)
  • Go Version: go1.24.6
  • GOMAXPROCS: 16 (default)
  • Cores: 16 logical processors

Minor variations are expected across different hardware and Go versions.

Performance Comparison

Benchmark results comparing our ShardedCache with go-cache, bigcache and sync.Map:

Get Operations
Implementation ops/s ns/op Hit Rate B/op allocs/op
ShardedCache 22,687,654 52.25 100% 23 1
GoCache 16,722,361 70.87 100% 23 1
SyncMap 23,469,448 51.16 100% 23 1
BigCache 22,618,719 55.79 100% 54 3
Set Operations
Implementation ops/s ns/op B/op allocs/op
ShardedCache 16,102,454 85.04 75 5
GoCache 3,581,130 395.70 63 4
SyncMap 4,562,907 304.90 48 3
BigCache 30,792,758 71.15 186 4
Mixed Operations (80% Get, 20% Set)
Implementation ops/s ns/op Hit Rate B/op allocs/op
ShardedCache 18,508,464 65.68 100% 31 2
GoCache 2,130,778 569.80 100% 31 2
SyncMap 2,105,696 567.90 100% 47 3
BigCache 22,440,392 54.70 100% 53 3
Memory Usage

Memory efficiency comparison with 1 million items (approximate, depends on key/value shapes):

Implementation Approx Memory
ShardedCache 169.71 MB
GoCache 135.03 MB
SyncMap 102.77 MB
BigCache 337.07 MB
Key Performance Characteristics

ShardedCache demonstrates excellent performance characteristics:

  • Fast read operations: ~52ns per Get operation
  • Efficient write operations: ~85ns per Set operation
  • Excellent mixed workload performance: ~66ns per operation
  • Balanced memory usage: ~170MB for 1M items
  • Consistent performance under concurrent access
  • Low allocation overhead: 1-5 allocations per operation

Performance Advantages:

  • 35% faster Get operations than GoCache
  • 4.6x faster Set operations than GoCache
  • 8.6x faster Mixed operations than GoCache
  • Comparable read performance with SyncMap
  • Better write performance than SyncMap (3.6x faster)
  • More balanced memory usage compared to BigCache
Use Case Recommendations
  • High-Throughput Scenarios: ShardedCache and BigCache both excel
  • Memory-Constrained Environments: SyncMap or GoCache
  • Balanced Performance: ShardedCache offers the best overall balance
  • Write-Heavy Workloads: BigCache shows superior performance
  • Read-Heavy Workloads: All implementations perform well, with SyncMap and ShardedCache leading slightly

Choose ShardedCache when you need:

  • Consistent high performance across all operations
  • Good memory efficiency
  • Reliable concurrent access
  • Rich feature set including expiration, statistics, and group support

For advanced LRU/FIFO eviction policies, see SwiftCache. This project uses a simpler creation-time FIFO eviction when MaxSize is exceeded.

License

MIT License

Documentation

Overview

Package shardedcache provides a sharded in-memory cache with expiration capabilities and persistence.

Index

Constants

View Source
const (
	NoExpiration      time.Duration = -1 // Item never expires
	DefaultExpiration time.Duration = 0  // Use cache's default expiration time
)

Expiration constants for cache items

Variables

This section is empty.

Functions

func GetTyped

func GetTyped[T any](c CacheInterface, key string) (T, bool)

GetTyped returns the cache value of the specified type

func Init

func Init(opts ...Options)

Init initializes the default cache instance (singleton pattern)

Types

type CacheInterface

type CacheInterface interface {
	Set(key string, value interface{})
	SetWithExpiration(key string, value interface{}, expiration time.Duration)
	Get(key string) (interface{}, bool)
	GetWithExpiration(key string) (interface{}, time.Time, bool)
	Delete(key string) bool
	DeleteKeys(keys []string) int
	DeletePrefix(prefix string) int
	GetOrSet(key string, value interface{}) interface{}
	GetOrSetFunc(key string, f func() interface{}) interface{}
	GetOrSetFuncWithExpiration(key string, f func() interface{}, expiration time.Duration) interface{}
	Stats() map[string]interface{}
	OnEvicted(f func(key string, value interface{}))
	Keys() []string
	Count() int
	Has(key string) bool
	Clear()
	Close()
	Group(name string) Group
}

func Get

func Get() CacheInterface

Get returns the default cache instance

func NewCache

func NewCache(opts ...Options) CacheInterface

NewCache creates a new sharded cache instance

type Group added in v1.0.2

type Group interface {
	// Basic operations
	Set(key string, value interface{})
	SetWithExpiration(key string, value interface{}, expiration time.Duration)
	Get(key string) (interface{}, bool)
	GetWithExpiration(key string) (interface{}, time.Time, bool)
	Delete(key string) bool

	// Batch delete operations
	DeleteKeys(keys []string) int
	DeletePrefix(prefix string) int

	// Utility operations
	GetOrSet(key string, value interface{}) interface{}
	GetOrSetFunc(key string, f func() interface{}) interface{}
	GetOrSetFuncWithExpiration(key string, f func() interface{}, expiration time.Duration) interface{}

	// Group operations
	Keys() []string
	Count() int
	Has(key string) bool
	Clear()
}

type Options

type Options struct {
	MaxSize           int           // Maximum items per shard (0 = unlimited)
	DefaultExpiration time.Duration // Default item expiration time (0 = no expiration)
	CleanupInterval   time.Duration // Interval for cleaning expired items (0 = disable auto cleanup, defaults to 11m)
	PersistPath       string        // File path for persistence storage (empty = disable persistence)
	PersistInterval   time.Duration // Interval for data persistence (0 = disable auto persistence, defaults to 13m)
	PersistThreshold  int           // Number of changes before triggering persistence (0 = persist every change, defaults to 100)
	ShardCount        int           // Number of cache shards, must be power of 2 (defaults to 32)
}

type ShardedCache

type ShardedCache struct {
	// contains filtered or unexported fields
}

ShardedCache represents a sharded cache implementation with multiple independent cache shards

func (*ShardedCache) Clear

func (sc *ShardedCache) Clear()

Clear implements CacheInterface

func (*ShardedCache) Close

func (sc *ShardedCache) Close()

Close implements CacheInterface

func (*ShardedCache) Count

func (sc *ShardedCache) Count() int

Count implements CacheInterface

func (*ShardedCache) Delete

func (sc *ShardedCache) Delete(key string) bool

Delete implements CacheInterface

func (*ShardedCache) DeleteKeys added in v1.0.4

func (sc *ShardedCache) DeleteKeys(keys []string) int

DeleteKeys implements CacheInterface

func (*ShardedCache) DeletePrefix added in v1.0.4

func (sc *ShardedCache) DeletePrefix(prefix string) int

DeletePrefix implements CacheInterface

func (*ShardedCache) Get

func (sc *ShardedCache) Get(key string) (interface{}, bool)

Get implements CacheInterface

func (*ShardedCache) GetOrSet

func (sc *ShardedCache) GetOrSet(key string, value interface{}) interface{}

GetOrSet implements CacheInterface

func (*ShardedCache) GetOrSetFunc

func (sc *ShardedCache) GetOrSetFunc(key string, f func() interface{}) interface{}

GetOrSetFunc implements CacheInterface

func (*ShardedCache) GetOrSetFuncWithExpiration

func (sc *ShardedCache) GetOrSetFuncWithExpiration(key string, f func() interface{}, expiration time.Duration) interface{}

GetOrSetFuncWithExpiration implements CacheInterface

func (*ShardedCache) GetWithExpiration

func (sc *ShardedCache) GetWithExpiration(key string) (interface{}, time.Time, bool)

GetWithExpiration implements CacheInterface

func (*ShardedCache) Group added in v1.0.2

func (sc *ShardedCache) Group(name string) Group

Group implements CacheInterface

func (*ShardedCache) Has

func (sc *ShardedCache) Has(key string) bool

Has implements CacheInterface

func (*ShardedCache) Keys

func (sc *ShardedCache) Keys() []string

Keys implements CacheInterface

func (*ShardedCache) OnEvicted

func (sc *ShardedCache) OnEvicted(f func(key string, value interface{}))

OnEvicted implements CacheInterface

func (*ShardedCache) Set

func (sc *ShardedCache) Set(key string, value interface{})

Set implements CacheInterface

func (*ShardedCache) SetWithExpiration

func (sc *ShardedCache) SetWithExpiration(key string, value interface{}, expiration time.Duration)

SetWithExpiration implements CacheInterface

func (*ShardedCache) Stats

func (sc *ShardedCache) Stats() map[string]interface{}

Stats implements CacheInterface

Jump to

Keyboard shortcuts

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