A generic in-memory cache Go library

  • By Ernest Nguyen Hung
  • Last update: Apr 12, 2023
  • Comments: 6

imcache

GitHub Workflow Status Go Report Card Go Version GoDoc Coverage Status

imcache is a generic in-memory cache Go library.

It supports expiration, sliding expiration, 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() {
	// Create a new Cache instance with default configuration.
	c := imcache.New[int32, string]()
	// Set a new value with no expiration time.
	c.Set(1, "one", imcache.WithNoExpiration())
	// Get the value for the key 1.
	value, ok := c.Get(1)
	if !ok {
		panic("value for the key '1' not found")
	}
	fmt.Println(value)
}

Expiration

imcache supports no expiration, absolute expiration and sliding expiration. No expiration simply means that the entry will never expire, absolute expiration means that the entry will expire after a certain time and sliding expiration means that the entry will expire after a certain time if it hasn't been accessed.

// Set a new value with no expiration time.
c.Set(1, "one", imcache.WithNoExpiration())
// Set a new value with a sliding expiration time.
c.Set(2, "two", imcache.WithSlidingExpiration(time.Second))
// Set a new value with an absolute expiration time.
c.Set(3, "three", imcache.WithExpiration(time.Second))

If you want to use default expiration time for the given cache instance, you can use the WithDefaultExpiration Expiration option. By default the default expiration time is set to no expiration. You can set the default expiration time when creating a new Cache instance.

// Create a new Cache instance with default absolute expiration time equal to 1 second.
c1 := imcache.New(imcache.WithDefaultExpirationOption[int32, string](time.Second))
// Set a new value with default expiration time (absolute).
c1.Set(1, "one", imcache.WithDefaultExpiration())

// Create a new Cache instance with default sliding expiration time equal to 1 second.
c2 := imcache.New(imcache.WithDefaultSlidingExpirationOption[int32, string](time.Second))
// Set a new value with default expiration time (sliding).
c2.Set(1, "one", imcache.WithDefaultExpiration())

Key eviction

imcache follows very naive and simple eviction approach. If an expired entry is accessed by any Cache method, it is removed from the cache.

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. You can enable it by calling the StartCleaner method. The Cleaner can be stopped by calling the StopCleaner method.

c := imcache.New[string, string]()
// Start Cleaner which will remove expired entries every 5 minutes.
_ = c.StartCleaner(5 * time.Minute)
defer c.StopCleaner()

To be notified when an entry is evicted from the cache, you can use the EvictionCallback. It's a function that accepts the key and value of the evicted entry along with the reason why the entry was evicted. EvictionCallback can be configured when creating a new 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(
		imcache.WithDefaultExpirationOption[string, interface{}](time.Second),
		imcache.WithEvictionCallbackOption(LogEvictedEntry),
	)
	c.Set("foo", "bar", imcache.WithDefaultExpiration())

	time.Sleep(time.Second)

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

Sharding

imcache supports sharding. It's possible to create a new Cache instance with a given number of shards. 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.

Currently, imcache provides only one implementation of the Hasher64 interface: DefaultStringHasher64. It uses the FNV-1a hash function.

A sharded Cache instance can be created by calling the NewSharded method. It accepts the number of shards, Hasher64 interface and optional configuration Options as arguments.

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

All previous examples apply to sharded Cache instances as well.

Download

imcache.zip

Comments(6)

  • 1

    Remove dependency on go-cmp

    Because this is a library, users have to carry around all dependencies, so the fewer the better.

    I did a cursory look - is go-cmp only used in tests?

    Can you get away with regular comparisons and/or using reflect.DeepEqual or is there a real need for something different?

  • 2

    Add `ReplaceWithFunc` method

    This PR adds ReplaceWithFunc method.

    It replaces the value for the given key with the result of the given function that takes the old value as an argument.

  • 3

    Simplify API

    This PR simplifies imcache API.

    • Get method follows value, ok pattern instead of returning ErrNotFound
    • Remove Add method
    • Add GetOrSet method returning a value if it exists, setting the value otherwise
    • Replace method returns boolean flag indicating if an entry is present and a value is replaced instead of returning ErrNotFound
    • Replace method returns boolean flag indicating if an entry is present and then removed instead of returning ErrNotFound
  • 4

    cleaner: return err instead of panic

    This PR changes the Cleaner to return an err instead of panic when starting.

    StartCleaner method returns an error if a cleaner's interval is equal to or less than 0 or if the Cleaner is already running.

  • 5

    Introduce generics

    This PR introduces generics.

    imcache was meant to be generic from the first day. It has been using the concrete key and value types for the initial tests purposes.

  • 6

    Add brief comparison to other in-memory cache libraries

    Add a new README section with brief comparison between imcache and other in-memory cache libraries.

    It would help users looking for an in-memory cache library to better understand if imcache is a great fit for them.