mcache is a simple, fast, thread-safe in-memory cache library with by-key TTL written in Go.
- Thread-safe cache operations
- Set key-value pairs with optional expiration time
- Update a key-value pair unconditionally, even if the key is still live
- Get values by key
- Check if a key exists
- Delete key-value pairs
- Clear the entire cache
- Get the current number of keys in the cache
- Cleanup expired key-value pairs
- Generic type support
Use go get to install the package:
go get github.com/parMaster/mcache/v2Import the mcache package in your Go code:
import "github.com/parMaster/mcache/v2"Create a new cache instance using the NewCache constructor, and use it to perform cache operations:
cache := mcache.NewCache[string]()
data, err := cache.Get("key")
if err == nil {
return data
}
data = ExpensiveFunctionCall()
cache.Set("key", data, 5*time.Minute) // cache data for 5 minutesSee the examples directory for more examples.
The Cacher interface is used to define the cache operations:
type Cacher[T any] interface {
Set(key string, value T, ttl time.Duration) bool
Update(key string, value T, ttl time.Duration) bool
Get(key string) (T, error)
Has(key string) error
Del(key string) error
Clear()
Len() int
}Set a key-value pair in the cache. The key must be a string, value type defined during cache creation, ttl is time.Duration type. If ttl is 0, the key-value pair will not expire.:
cache.Set("key", "value", time.Duration(0))If the key already exists and is not expired, false will be returned. If the key exists but is expired, the value will be updated.
You can also set a key-value pair with an expiration time (in seconds):
cache.Set("key", "value", time.Minute)The value will automatically expire after the specified duration.
Set a key-value pair unconditionally, replacing any existing value or
expiration — including a live (non-expired) key, which Set would refuse:
updated := cache.Update("key", "value", time.Minute)Returns false only if ttl is negative; the existing entry, if any, is
left untouched in that case.
Retrieve a value from the cache by key:
value, err := cache.Get("key")
if err != nil {
// handle error
}If the key does not exist, an error mcache.ErrKeyNotFound will be returned. If the key exists but is expired, an error mcache.ErrExpired will be returned, and the key-value pair will be deleted.
Either error or value could be checked to determine if the key exists. Error is easier to check when the value is a zero value.
Check if a key exists in the cache:
err := cache.Has("key")
if err != nil {
// possible errors:
// mcache.ErrKeyNotFound - key doesn't exist
// mcache.ErrExpired - key existed but expired (and was deleted)
}
if err == nil {
// key exists
}Delete a key-value pair from the cache:
err := cache.Del("key")
if err != nil {
// handle error
}Clear the entire cache:
cache.Clear()Get the number of keys currently in the cache (including expired keys not yet cleaned up):
count := cache.Len()Cleanup expired key-value pairs in the cache. You can call this method periodically to remove expired key-value pairs from the cache:
cache.Cleanup()WithCleanup is a functional option to the NewCache constructor that allows you to specify a cleanup interval:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cache := mcache.NewCache(mcache.WithCleanup[string](ctx, time.Minute)) // cleanup every 60 secondsIt runs Cleanup in a background goroutine on the given interval, until ctx is cancelled.
Note: as of v2, Cleanup is not part of the Cacher interface — it's
only callable on the concrete *Cache[T] returned by NewCache. This
keeps the interface focused on operations any implementation must support,
since Cleanup is meant to be driven by WithCleanup's background
goroutine, not called ad hoc by interface-typed callers.
v2 consolidates all breaking changes since v1.0.0 into one release:
WithCleanuptakes acontext.Contextas its first argument:WithCleanup[T](ctx, interval)instead ofWithCleanup[T](interval). The background goroutine now stops whenctxis cancelled.Hasreturns a singleerrorinstead of(bool, error). Checkerr == nilinstead of the old bool.Clearreturns nothing instead oferror(it never actually failed).Cleanupis no longer part of theCacherinterface — call it on the concrete*Cache[T]if you need it directly, or useWithCleanup.- New:
Update(key, value, ttl) boolfor unconditional overwrites, andLen() intfor the current key count.
100% test coverage:
$ go test -cover -race -cpu 24 .
ok github.com/parMaster/mcache/v2 5.634s coverage: 100.0% of statementsBlinding fast and efficient:
$ go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: github.com/parMaster/mcache/v2
cpu: Apple M3 Pro
BenchmarkWrite-11 3989781 322.6 ns/op 159 B/op 3 allocs/op
BenchmarkRead-11 6190872 241.6 ns/op 15 B/op 1 allocs/op
BenchmarkRWD-11 4737552 253.8 ns/op 80 B/op 6 allocs/op
BenchmarkConcurrentRWD-11 1000000 3688 ns/op 1128 B/op 14 allocs/op
PASS
ok github.com/parMaster/mcache/v2 13.823sContributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request.
This project is licensed under the MIT license.