SkillAgentSearch skills...

Imcache

A zero-dependency generic in-memory cache Go library

Install / Use

/learn @erni27/Imcache
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

imcache

GitHub Workflow Status Go Report Card Go Version GoDoc Coverage Status

imcache is a zero-dependency generic in-memory cache Go library.

It supports absolute expiration, sliding expiration, max entries limit, eviction callbacks and sharding. It's safe for concurrent use by multiple goroutines.

import "github.com/erni27/imcache"

Usage

package main

import (
	"fmt"

	"github.com/erni27/imcache"
)

func main() {
	// Zero value Cache is a valid non-sharded cache
	// with no expiration, no sliding expiration,
	// no entry limit and no eviction callback.
	var c imcache.Cache[uint32, string]
	c.Set(1, "one", imcache.WithNoExpiration())
	value, ok := c.Get(1)
	if !ok {
		panic("value for the key '1' not found")
	}
	fmt.Println(value)
}

Expiration

imcache supports the following expiration options:

  • WithNoExpiration - the entry will never expire.
  • WithExpiration - the entry will expire after a certain time.
  • WithExpirationDate - the entry will expire at a certain date.
  • WithSlidingExpiration - the entry will expire after a certain time if it hasn't been accessed. The expiration time is reset every time the entry is accessed. It is slided to now + sliding expiration time when now is the time when the entry was accessed.
// The entry will never expire.
c.Set(1, "one", imcache.WithNoExpiration())
// The entry will expire after 1 second.
c.Set(2, "two", imcache.WithExpiration(time.Second))
// The entry will expire at the given date.
c.Set(3, "three", imcache.WithExpirationDate(time.Now().Add(time.Second)))
// The entry will expire after 1 second if it hasn't been accessed.
// Otherwise, the expiration time will slide to the access time + 1 second.
c.Set(4, "four", imcache.WithSlidingExpiration(time.Second))

One can also use the WithExpirationOption and WithSlidingExpirationOption options to set the default expiration time for the given cache instance. By default, the default expiration time is set to no expiration.

// Create a new cache instance with the default expiration time equal to 1 second.
c1 := imcache.New[int32, string](imcache.WithDefaultExpirationOption[int32, string](time.Second))
// The entry will expire after 1 second (the default expiration time).
c1.Set(1, "one", imcache.WithDefaultExpiration())

// Create a new cache instance with the default sliding expiration time equal to 1 second.
c2 := imcache.New[int32, string](imcache.WithDefaultSlidingExpirationOption[int32, string](time.Second))
// The entry will expire after 1 second (the default expiration time) if it hasn't been accessed.
// Otherwise, the expiration time will slide to the access time + 1 second.
c2.Set(1, "one", imcache.WithDefaultExpiration())

Key eviction

imcache actively evicts expired entries. It removes expired entries when they are accessed by most of Cache methods (both read and write). Peek, PeekMultiple and PeekAll methods are the exception. They don't remove the expired entries and do not slide the expiration time (if the sliding expiration is set).

It is possible to use the Cleaner to periodically remove expired entries from the cache. The Cleaner is a background goroutine that periodically removes expired entries from the cache. The Cleaner is disabled by default. One can use the WithCleanerOption option to enable the Cleaner and set the cleaning interval.

// Create a new Cache with a Cleaner which will remove expired entries every 5 minutes.
c := imcache.New[string, string](imcache.WithCleanerOption[string, string](5 * time.Minute))
// Close the Cache. This will stop the Cleaner if it is running.
defer c.Close()

To be notified when the entry is evicted from the cache, one can use the EvictionCallback. It's a function that accepts the key and the value of the evicted entry along with the reason why the entry was evicted. One can use the WithEvictionCallbackOption option to set the EvictionCallback for the given cache instance.

package main

import (
	"log"
	"time"

	"github.com/erni27/imcache"
)

func LogEvictedEntry(key string, value interface{}, reason imcache.EvictionReason) {
	log.Printf("Evicted entry: %s=%v (%s)", key, value, reason)
}

func main() {
	c := imcache.New[string, interface{}](
		imcache.WithDefaultExpirationOption[string, interface{}](time.Second),
		imcache.WithEvictionCallbackOption[string, interface{}](LogEvictedEntry),
	)
	c.Set("foo", "bar", imcache.WithDefaultExpiration())

	time.Sleep(time.Second)

	_, ok := c.Get("foo")
	if ok {
		panic("expected entry to be expired")
	}
}

EvictionCallback is invoked in a separate goroutine to not block any Cache method.

Max entries limit

imcache supports setting the max entries limit. When the max entries limit is reached, the entry is evicted according to the chosen eviction policy. imcache supports the following eviction policies:

  • EvictionPolicyLRU - the least recently used entry is evicted.
  • EvictionPolicyLFU - the least frequently used entry is evicted.
  • EvictionPolicyRandom - a random entry is evicted.

One can use the WithMaxEntriesLimitOption option to set the max entries limit and the eviction policy for the given cache instance.

c := imcache.New[uint32, string](imcache.WithMaxEntriesLimitOption[uint32, string](1000, imcache.EvictionPolicyLRU))

Sharding

imcache supports sharding. Each shard is a separate Cache instance. A shard for a given key is selected by computing the hash of the key and taking the modulus of the number of shards. imcache exposes the Hasher64 interface that wraps Sum64 accepting a key and returning a 64-bit hash of the input key. It can be used to implement custom sharding algorithms.

A Sharded instance can be created by calling the NewSharded method.

c := imcache.NewSharded[string, string](4, imcache.DefaultStringHasher64{})

All previous examples apply to Sharded type as well. Note that Option(s) are applied to each shard (Cache instance separately) not to the Sharded instance itself.

Performance

imcache was compared to the vanilla Go map with simple locking mechanism. The benchmarks were run on an Apple M1 Pro 8-core CPU with 32 GB of RAM running macOS Ventura 13.4.1 using Go 1.21.6.

Reads

go version
go version go1.21.6 darwin/arm64
go test -benchmem -bench "Get_|Get$|Peek_|Peek$"
goos: darwin
goarch: arm64
pkg: github.com/erni27/imcache
BenchmarkCache_Get-8                                                                 2655246	       428.5 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get/2_Shards-8                                                      2810713	       436.8 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get/4_Shards-8                                                      2732820	       444.9 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get/8_Shards-8                                                      2957444	       445.7 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get/16_Shards-8                                                     2773999	       447.0 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get/32_Shards-8                                                     2752075	       443.4 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get/64_Shards-8                                                     2752899	       439.7 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get/128_Shards-8                                                    2771691	       456.3 ns/op	      23 B/op	       1 allocs/op
BenchmarkCache_Get_MaxEntriesLimit_EvictionPolicyLRU-8                               2410712	       526.8 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLRU/2_Shards-8                    2346715	       543.2 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLRU/4_Shards-8                    2317453	       566.2 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLRU/8_Shards-8                    2293774	       556.5 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLRU/16_Shards-8                   2292554	       557.7 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLRU/32_Shards-8                   2262634	       542.0 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLRU/64_Shards-8                   2318079	       544.2 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLRU/128_Shards-8                  2278434	       565.6 ns/op	      23 B/op	       1 allocs/op
BenchmarkCache_Get_MaxEntriesLimit_EvictionPolicyLFU-8                               2482602	       528.0 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLFU/2_Shards-8                    2403782	       534.2 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLFU/4_Shards-8                    2286364	       548.8 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLFU/8_Shards-8                    2239857	       576.0 ns/op	      23 B/op	       1 allocs/op
BenchmarkSharded_Get_MaxEntriesLimit_Evicti
View on GitHub
GitHub Stars123
CategoryDevelopment
Updated4mo ago
Forks6

Languages

Go

Security Score

97/100

Audited on Nov 8, 2025

No findings