Documentation
¶
Overview ¶
Package imcache is a lightweight, zero-dependency, generic, sharded, thread-safe in-memory cache for Go 1.26+.
It is built entirely on the Go standard library with no external dependencies.
Features:
- Generics: fully type-safe Cache[V any]; no interface{} casts at call sites.
- Sharded locking: 256 independent RWMutex shards (configurable) so reads and writes on different keys never block each other.
- LRU eviction: optional per-shard capacity limit with O(1) eviction via container/list.
- Lazy + periodic expiry: expired items are removed on access and during periodic janitor sweeps; the janitor stops cleanly on Close().
- Atomic stats: lock-free hit/miss/eviction counters.
- Range iterators: All() and Keys() return iter.Seq2/iter.Seq for lazy, allocation-free iteration.
Index ¶
- Constants
- type Cache
- func (c *Cache[V]) All() iter.Seq2[string, V]
- func (c *Cache[V]) Close()
- func (c *Cache[V]) Count() int
- func (c *Cache[V]) Delete(key string)
- func (c *Cache[V]) DeleteExpired()
- func (c *Cache[V]) Flush()
- func (c *Cache[V]) Get(key string) (V, bool)
- func (c *Cache[V]) GetOrSet(key string, value V, ttl time.Duration) (actual V, loaded bool)
- func (c *Cache[V]) Items() map[string]Item[V]
- func (c *Cache[V]) Keys() iter.Seq[string]
- func (c *Cache[V]) Peek(key string) (V, bool)
- func (c *Cache[V]) ResetStats()
- func (c *Cache[V]) Set(key string, value V, ttl time.Duration)
- func (c *Cache[V]) SetIfAbsent(key string, value V, ttl time.Duration) (actual V, loaded bool)
- func (c *Cache[V]) Stats() Stats
- type EvictCallback
- type Item
- type Option
- type Stats
Examples ¶
Constants ¶
const ( // NoExpiration indicates that an item should never expire. NoExpiration time.Duration = -1 // DefaultExpiration uses the Cache's default TTL passed to New. DefaultExpiration time.Duration = 0 )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache[V any] struct { // contains filtered or unexported fields }
Cache is a generic, sharded, thread-safe in-memory key-value cache.
V is the value type; keys are always strings. Create one with New. A Cache must not be copied after first use.
func New ¶
New creates a new Cache.
- defaultTTL: TTL applied when Set is called with DefaultExpiration. Use NoExpiration to make items live forever by default.
- cleanupInterval: how often a background goroutine sweeps expired items. Pass 0 or a negative value to disable automatic sweeps (you can call Cache.DeleteExpired manually).
- opts: optional configuration via WithNumShards, WithMaxItemsPerShard, and WithOnEvict.
Remember to call Cache.Close when the cache is no longer needed to stop the background goroutine (if cleanupInterval > 0).
Example ¶
package main
import (
"fmt"
"time"
"github.com/psdhajare/imcache"
)
func main() {
// Create a cache with 5-minute default TTL and 10-minute janitor sweep.
c := imcache.New[string](5*time.Minute, 10*time.Minute)
defer c.Close()
c.Set("greeting", "hello", imcache.DefaultExpiration)
if val, ok := c.Get("greeting"); ok {
fmt.Println(val)
}
}
Output: hello
Example (WithLRU) ¶
package main
import (
"fmt"
"github.com/psdhajare/imcache"
)
func main() {
// Create a bounded cache with LRU eviction: 4 shards x 100 items each.
c := imcache.New[int](imcache.NoExpiration, 0,
imcache.WithNumShards(4),
imcache.WithMaxItemsPerShard(100),
)
defer c.Close()
c.Set("answer", 42, imcache.NoExpiration)
if val, ok := c.Get("answer"); ok {
fmt.Println(val)
}
}
Output: 42
func (*Cache[V]) All ¶
All returns an iterator over all non-expired entries across all shards. Entries are yielded lazily under per-shard RLocks — no map allocation. The iteration order is non-deterministic.
Example ¶
package main
import (
"fmt"
"github.com/psdhajare/imcache"
)
func main() {
c := imcache.New[int](imcache.NoExpiration, 0)
defer c.Close()
c.Set("a", 1, imcache.NoExpiration)
c.Set("b", 2, imcache.NoExpiration)
sum := 0
for _, v := range c.All() {
sum += v
}
fmt.Println("sum:", sum)
}
Output: sum: 3
func (*Cache[V]) Close ¶
func (c *Cache[V]) Close()
Close stops the background janitor goroutine started by New. It is safe to call Close more than once and from multiple goroutines.
func (*Cache[V]) Count ¶
Count returns the number of items currently held (including expired but not-yet-deleted items). For an exact live count, call DeleteExpired first.
func (*Cache[V]) Delete ¶
Delete removes key from the cache. The eviction callback is invoked if set. It is a no-op if the key does not exist.
func (*Cache[V]) DeleteExpired ¶
func (c *Cache[V]) DeleteExpired()
DeleteExpired scans all shards and removes items that have passed their TTL. This is called automatically by the background janitor; you only need to call it directly if you disabled automatic cleanup.
func (*Cache[V]) Flush ¶
func (c *Cache[V]) Flush()
Flush removes all items from every shard. The eviction callback is NOT invoked for flushed items for performance reasons; if you need per-item cleanup, iterate Items() before calling Flush.
func (*Cache[V]) Get ¶
Get returns the value associated with key and true, or the zero value and false if the key is absent or expired. Accessing a key updates its LRU position when WithMaxItemsPerShard is set.
func (*Cache[V]) GetOrSet ¶
GetOrSet returns the existing value for key if it is present and not expired. Otherwise it stores value under key and returns value. The boolean reports whether an existing value was returned.
Example ¶
package main
import (
"fmt"
"time"
"github.com/psdhajare/imcache"
)
func main() {
c := imcache.New[string](time.Hour, 0)
defer c.Close()
// First call stores and returns the value.
val, loaded := c.GetOrSet("key", "computed-value", time.Hour)
fmt.Println(val, loaded)
// Second call returns the existing value.
val, loaded = c.GetOrSet("key", "other-value", time.Hour)
fmt.Println(val, loaded)
}
Output: computed-value false computed-value true
func (*Cache[V]) Items ¶
Items returns a point-in-time snapshot of all non-expired items across all shards. The returned map is a copy; mutations do not affect the cache.
For large caches, prefer Cache.All which iterates lazily without allocating.
func (*Cache[V]) Keys ¶
Keys returns an iterator over all non-expired keys across all shards. The iteration order is non-deterministic. Each shard is held under an RLock while its keys are yielded.
func (*Cache[V]) Peek ¶
Peek returns the value for key without updating LRU order or recording stats. Returns zero value and false when the key is absent or expired.
func (*Cache[V]) ResetStats ¶
func (c *Cache[V]) ResetStats()
ResetStats zeroes all performance counters.
func (*Cache[V]) Set ¶
Set adds or replaces an item in the cache.
- ttl == DefaultExpiration: use the cache's default TTL.
- ttl == NoExpiration: item never expires.
- ttl > 0: item expires after the given duration.
func (*Cache[V]) SetIfAbsent ¶
SetIfAbsent sets key only if it does not already exist (or has expired).
If the key already holds a live value, it returns that existing value and loaded=true. Otherwise it stores value, returns it, and reports loaded=false.
type EvictCallback ¶
EvictCallback is invoked whenever an item is removed from the cache, whether by TTL expiry, LRU capacity eviction, or explicit Cache.Delete.
The callback runs synchronously in the goroutine that triggered the eviction, but outside any shard lock. Slow callbacks will block the calling goroutine without affecting other cache operations.
Register a callback with WithOnEvict.
type Item ¶
type Item[V any] struct { // Value is the cached value. Value V // ExpiresAt is the absolute time when this item expires. // A zero value means the item has no expiry. ExpiresAt time.Time }
Item represents a point-in-time snapshot of a cached entry, returned by Cache.Items.
type Option ¶
type Option func(*config)
Option is a functional option for configuring a Cache via New.
Available options: WithNumShards, WithMaxItemsPerShard, WithOnEvict.
func WithMaxItemsPerShard ¶
WithMaxItemsPerShard sets the maximum number of items allowed per shard. When a shard reaches capacity, the least-recently-used (LRU) item is evicted before the new item is inserted.
Total cache capacity ≈ numShards × maxItemsPerShard. Default is 0 (unbounded).
func WithNumShards ¶
WithNumShards sets the number of internal shards. Must be a positive integer; it will be rounded up to the next power of 2. Default is 256. More shards reduce lock contention under high write concurrency at the cost of slightly higher memory overhead.
func WithOnEvict ¶
func WithOnEvict[V any](fn EvictCallback[V]) Option
WithOnEvict registers fn as the eviction callback. It is called synchronously (in the goroutine that triggered the eviction) for every evicted item. Pass nil to disable callbacks.
Example ¶
package main
import (
"fmt"
"github.com/psdhajare/imcache"
)
func main() {
c := imcache.New[string](imcache.NoExpiration, 0,
imcache.WithNumShards(1),
imcache.WithMaxItemsPerShard(1),
imcache.WithOnEvict(func(key string, val string) {
fmt.Printf("evicted %s=%s\n", key, val)
}),
)
defer c.Close()
c.Set("first", "a", imcache.NoExpiration)
c.Set("second", "b", imcache.NoExpiration) // evicts "first"
}
Output: evicted first=a
type Stats ¶
type Stats struct {
// Hits is the number of [Cache.Get] calls that found a live entry.
Hits int64
// Misses is the number of [Cache.Get] calls that found no live entry,
// including lookups where the key existed but had expired.
Misses int64
// Evictions is the total number of items removed by TTL expiry,
// LRU capacity eviction, or explicit [Cache.Delete].
Evictions int64
// HitRate is Hits / (Hits + Misses). It is 0 when no requests have
// been made yet.
HitRate float64
}
Stats holds cache performance counters. Obtain a snapshot with Cache.Stats and reset with Cache.ResetStats.