Skip to content
Saiop68Public

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

1 Commit

Folders and files

Repository files navigation

⚑ GoCacheX

The fastest, most feature-rich caching library for Go

Features β€’ Quick Start β€’ API β€’ Policies β€’ Benchmarks β€’ Examples

Go 1.24+ MIT License Zero Dependencies Race Clean Coverage


✨ Highlights

  • πŸš€ Blazing fast β€” Sub-microsecond Get/Set, sharded design, lock striping
  • 🧬 Full generics β€” Type-safe API with Cache[K, V], no interface{}
  • πŸ”’ Thread-safe β€” Zero data races under -race, tested with 100K+ goroutines
  • πŸ“Š 7 eviction policies β€” LRU, LFU, FIFO, Random, Clock, ARC, TinyLFU
  • ⏱️ TTL support β€” Per-key TTL, Touch, auto-cleanup, expiration callbacks
  • πŸ”„ Atomic operations β€” GetOrSet, Compute, CAS, Increment, Decrement
  • πŸ“‘ Metrics β€” Prometheus, OpenTelemetry, JSON β€” zero external deps
  • πŸ’Ύ Persistence β€” Save/Load/Snapshot with JSON, Gob, compressed formats
  • 🎯 Zero allocations β€” sync.Pool, pre-computed hashes, cache-line padding
  • πŸ”Œ Extensible β€” Middleware, events, custom hashers, pluggable serializers

πŸ“¦ Installation

go get github.com/gocachex/gocachex

Requires Go 1.24+ | Zero external dependencies in core

πŸš€ Quick Start

package main

import (
    "fmt"
    "time"

    "github.com/gocachex/gocachex"
    "github.com/gocachex/gocachex/policy"
)

type User struct {
    ID   int
    Name string
}

func main() {
    // Create a cache with LRU eviction, 5-minute TTL, max 100K entries.
    cache := gocachex.New[string, User](
        gocachex.WithTTL[string, User](5 * time.Minute),
        gocachex.WithPolicy[string, User](policy.LRU),
        gocachex.WithMaxEntries[string, User](100_000),
    )
    defer cache.Close()

    // Set with default TTL.
    cache.Set("user:1", User{ID: 1, Name: "Alice"})

    // Set with custom TTL.
    cache.Set("user:2", User{ID: 2, Name: "Bob"}, 10*time.Minute)

    // Get.
    if user, ok := cache.Get("user:1"); ok {
        fmt.Printf("Found: %+v\n", user)
    }

    // Atomic GetOrSet.
    user, loaded := cache.GetOrSet("user:3", User{ID: 3, Name: "Charlie"})
    fmt.Printf("User: %+v (loaded from cache: %v)\n", user, loaded)

    // Lazy loading with singleflight.
    user, err := cache.GetOrLoad("user:4", func(key string) (User, error) {
        return User{ID: 4, Name: "Diana"}, nil // e.g., fetch from database
    })

    // Stats.
    stats := cache.Stats()
    fmt.Printf("Hit ratio: %.2f%%\n", stats.HitRatio*100)
}

πŸ“‹ Features

Core API

Method Description
New[K, V](opts...) Create a new cache
Get(key) Retrieve a value
Set(key, value, ttl...) Insert or update
Delete(key) Remove an entry
Has(key) Check existence
Clear() Remove all entries
Len() Entry count
Keys() All keys (snapshot)
Values() All values (snapshot)
Range(func) Iterate entries
Close() Shutdown + cleanup

TTL Management

Method Description
Set(key, val, ttl) Per-key TTL
Touch(key) Reset TTL
TTL(key) Remaining TTL
Cleanup() Manual sweep
WithTTL(d) Default TTL
WithOnExpired(fn) Expiration callback

Atomic Operations

Method Description
GetOrSet(key, val) Get existing or set new
Compute(key, fn) Read-modify-write
CompareAndSwap(key, old, new, cmp) Conditional swap
Swap(key, val) Replace and return old
Replace(key, val) Replace if exists
Increment(cache, key, delta) Atomic increment
Decrement(cache, key, delta) Atomic decrement
GetOrLoad(key, loader) Lazy load with singleflight

Iterators

// Range-over-func (Go 1.23+)
for key, value := range cache.All() {
    fmt.Println(key, value)
}

// Snapshot iterator
it := cache.NewSnapshotIterator()
for it.Next() {
    key, value := it.Value()
    fmt.Println(key, value)
}

// Concurrent iteration (parallel across shards)
cache.ConcurrentRange(func(key string, value int) bool {
    process(key, value)
    return true
})

🎯 Eviction Policies

Policy Algorithm Best For
LRU Doubly-linked list + hashmap General workloads
LFU Frequency buckets with aging Popularity-driven access
FIFO Insertion-order queue Simple, predictable eviction
Random Random selection Lowest overhead
Clock Second-chance circular buffer LRU approximation, less overhead
ARC Adaptive 4-list (T1/T2/B1/B2) Self-tuning recency vs frequency
TinyLFU Count-Min Sketch + SLRU Best hit ratio for most workloads
cache := gocachex.New[string, int](
    gocachex.WithPolicy[string, int](policy.TinyLFU),
    gocachex.WithMaxEntries[string, int](10_000),
)

βš™οΈ Configuration

cache := gocachex.New[string, int](
    gocachex.WithTTL[string, int](5 * time.Minute),          // Default TTL
    gocachex.WithShards[string, int](128),                     // Shard count
    gocachex.WithPolicy[string, int](policy.ARC),              // Eviction policy
    gocachex.WithMaxEntries[string, int](1_000_000),           // Max entries
    gocachex.WithMaxMemory[string, int](1 << 30),              // 1GB memory limit
    gocachex.WithCleanupInterval[string, int](30 * time.Second), // Cleanup interval
    gocachex.WithCleanupDisabled[string, int](),               // Disable auto-cleanup
    gocachex.WithHasher[string, int](customHasher),            // Custom hash function
    gocachex.WithClock[string, int](mockClock),                // Custom clock (testing)
    gocachex.WithLogger[string, int](slog.Default()),          // Structured logging
    gocachex.WithStatsDisabled[string, int](),                 // Disable stats overhead
    gocachex.WithOnExpired[string, int](expiredHandler),       // Expiration callback
    gocachex.WithOnEvicted[string, int](evictedHandler),       // Eviction callback
    gocachex.WithOnSet[string, int](setHandler),               // Set callback
    gocachex.WithOnDelete[string, int](deleteHandler),         // Delete callback
    gocachex.WithOnUpdate[string, int](updateHandler),         // Update callback
)

πŸ“Š Metrics

Prometheus (zero dependencies)

promExporter := metrics.NewPrometheusExporter("myapp_cache")

http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/plain; version=0.0.4")
    promExporter.WriteTo(w, cache.Stats())
})

JSON

jsonExporter := metrics.NewJSONExporter(true) // pretty print
jsonExporter.WriteTo(w, cache.Stats())

OpenTelemetry

otelExporter := metrics.NewOTelExporter(yourMeterRecorder)
otelExporter.Export(cache.Stats())

πŸ’Ύ Persistence

// File-based with atomic writes.
store := persistence.NewFileStore("cache.dat")

// With gzip compression.
compressedStore := persistence.NewCompressedStore(store)

// Serialize with JSON.
serializer := serialization.NewJSONSerializer()
data, _ := serializer.Marshal(cache.Snapshot())
compressedStore.Save(data)

// Restore.
loaded, _ := compressedStore.Load()
var snapshot map[string]int
serializer.Unmarshal(loaded, &snapshot)

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Cache[K, V]                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚
β”‚  β”‚ Shard 0 β”‚ β”‚ Shard 1 β”‚ β”‚ Shard N β”‚  ...      β”‚
β”‚  β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”β”‚ β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”β”‚ β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”β”‚           β”‚
β”‚  β”‚β”‚RWMutexβ”‚β”‚ β”‚β”‚RWMutexβ”‚β”‚ β”‚β”‚RWMutexβ”‚β”‚           β”‚
β”‚  β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”‚ β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”‚ β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”‚           β”‚
β”‚  β”‚β”‚ Map   β”‚β”‚ β”‚β”‚ Map   β”‚β”‚ β”‚β”‚ Map   β”‚β”‚           β”‚
β”‚  β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”‚ β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”‚ β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”‚           β”‚
β”‚  β”‚β”‚Policy β”‚β”‚ β”‚β”‚Policy β”‚β”‚ β”‚β”‚Policy β”‚β”‚           β”‚
β”‚  β”‚β””β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ β”‚β””β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ β”‚β””β”€β”€β”€β”€β”€β”€β”€β”˜β”‚           β”‚
β”‚  β”‚ [pad64] β”‚ β”‚ [pad64] β”‚ β”‚ [pad64] β”‚           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚
β”‚                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚  β”‚  Stats   β”‚ β”‚  Events  β”‚ β”‚  Cleanup β”‚        β”‚
β”‚  β”‚ (atomic) β”‚ β”‚ (async)  β”‚ β”‚ (worker) β”‚        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key design decisions:

  • Power-of-2 sharding with bitwise AND masking (faster than modulo)
  • Cache-line padding (64 bytes) between shards to prevent false sharing
  • Atomic TTL checks on read (lock-free via atomic.Int64)
  • Lazy + proactive expiration (check on Get + background sweep)
  • Per-shard policies β€” no cross-shard coordination needed
  • sync.Pool for entry object recycling

πŸ§ͺ Testing

# Run all tests with race detector
go test -race -v ./...

# Run benchmarks
go test -bench=. -benchmem -count=3 ./benchmarks/

# Run fuzz tests (60 seconds)
go test -fuzz=FuzzSetGet -fuzztime=60s ./tests/

# Coverage report
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out

πŸ“ Project Structure

GoCacheX/
β”œβ”€β”€ cache.go              # Main Cache[K,V] public API
β”œβ”€β”€ config.go             # Functional options
β”œβ”€β”€ entry.go              # Cache entry with atomic fields
β”œβ”€β”€ shard.go              # Sharded internals
β”œβ”€β”€ atomic.go             # Atomic ops + GetOrLoad
β”œβ”€β”€ events.go             # Event callbacks
β”œβ”€β”€ iterator.go           # Safe/snapshot/concurrent iterators
β”œβ”€β”€ hooks.go              # Middleware + plugin system
β”œβ”€β”€ hash.go               # Hash functions (maphash + FNV)
β”œβ”€β”€ clock.go              # Clock abstraction (real + mock)
β”œβ”€β”€ pool.go               # sync.Pool integration
β”œβ”€β”€ errors.go             # Sentinel errors
β”œβ”€β”€ policy/               # 7 eviction policies
β”‚   β”œβ”€β”€ lru.go            # Least Recently Used
β”‚   β”œβ”€β”€ lfu.go            # Least Frequently Used
β”‚   β”œβ”€β”€ fifo.go           # First In First Out
β”‚   β”œβ”€β”€ random.go         # Random eviction
β”‚   β”œβ”€β”€ clock.go          # Clock (second-chance)
β”‚   β”œβ”€β”€ arc.go            # Adaptive Replacement Cache
β”‚   β”œβ”€β”€ tinylfu.go        # TinyLFU
β”‚   └── sketch.go         # Count-Min Sketch + Bloom filter
β”œβ”€β”€ stats/                # Lock-free statistics
β”œβ”€β”€ metrics/              # Prometheus, OTel, JSON exporters
β”œβ”€β”€ serialization/        # Pluggable serializers (Gob, JSON, MsgPack)
β”œβ”€β”€ persistence/          # File persistence + compression
β”œβ”€β”€ internal/             # Cache-line padding, fast hashers
β”œβ”€β”€ examples/             # Usage examples
β”œβ”€β”€ tests/                # Unit, fuzz, stress, integration tests
└── benchmarks/           # Comprehensive benchmarks

πŸ”„ Migration Guide

From patrickmn/go-cache

- import "github.com/patrickmn/go-cache"
+ import "github.com/gocachex/gocachex"

- c := cache.New(5*time.Minute, 10*time.Minute)
+ c := gocachex.New[string, any](
+     gocachex.WithTTL[string, any](5*time.Minute),
+     gocachex.WithCleanupInterval[string, any](10*time.Minute),
+ )

- c.Set("key", value, cache.DefaultExpiration)
+ c.Set("key", value)

- val, found := c.Get("key")
- myVal := val.(MyType)  // type assertion needed!
+ myVal, found := c.Get("key")  // type-safe, no assertion!

From dgraph-io/ristretto

- import "github.com/dgraph-io/ristretto"
+ import "github.com/gocachex/gocachex"
+ import "github.com/gocachex/gocachex/policy"

- cache, _ := ristretto.NewCache(&ristretto.Config{...})
+ cache := gocachex.New[string, MyType](
+     gocachex.WithPolicy[string, MyType](policy.TinyLFU),
+     gocachex.WithMaxEntries[string, MyType](100_000),
+ )

❓ FAQ

Q: Is it safe for concurrent use? Yes. All operations are thread-safe. Tested with 100,000+ concurrent goroutines and clean under -race.

Q: What's the default eviction policy? LRU. Change it with WithPolicy().

Q: Can I use integer keys? Yes. Any comparable type works: string, int, uint64, custom structs, etc.

Q: Does it support generics? Yes. Full generics throughout. Cache[string, User], Cache[int, []byte], etc.

Q: How many shards should I use? The default (GOMAXPROCS * 16) works well for most workloads. Increase for extreme write contention, decrease for memory-constrained environments.

Q: Are external dependencies required? No. The core library has zero external dependencies. Prometheus/OTel metrics use lightweight interfaces β€” no heavy SDK imports.

πŸ“„ License

MIT License β€” see LICENSE for details.


Built with ❀️ for the Go community

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages