zcache is an in-memory key:value store/cache with time-based evictions.
It is suitable for applications running on a single machine. It's essentially a thread-safe map with expiration times. Any object can be stored, for a given duration or forever, and the cache can be safely used by multiple goroutines.
Although zcache isn't meant to be used as a persistent datastore, the contents
can be saved to and loaded from a file (using c.Items() to retrieve the items
map to serialize, and NewFrom() to create a cache from a deserialized one) to
recover from downtime quickly.
The canonical import path is zgo.at/zcache/v2, or zgo.at/zcache for the v1.
Reference docs are at https://godocs.io/zgo.at/zcache/v2 and
https://godocs.io/zgo.at/zcache
This is a fork of https://github.com/patrickmn/go-cache – which no longer seems actively maintained. There are two versions of zcache:
- v1 is 100% compatible with go-cache and a drop-in replacement with various enhancements. As of 2025 this is only maintained for bugfixes, and not new features.
- v2 makes various incompatible changes to the API; some functions calls are improved and it uses generics, which requires Go 1.18.
This README documents v2; see README.v1.md for the v1 README. See the "changes" section below for a list of changes.
Usage
Some examples from example_test.go:
func ExampleSimple() { // Create a cache with a default expiration time of 5 minutes, and which // purges expired items every 10 minutes. // // This creates a cache with string keys and values, with Go 1.18 type // parameters. c := zcache.New[string, string](5*time.Minute, 10*time.Minute) // Set the value of the key "foo" to "bar", with the default expiration. c.Set("foo", "bar") // Set the value of the key "baz" to "never", with no expiration time. The // item won't be removed until it's removed with c.Delete("baz"). c.SetWithExpire("baz", "never", zcache.NoExpiration) // Get the value associated with the key "foo" from the cache; due to the // use of type parameters this is a string, and no type assertions are // needed. foo, ok := c.Get("foo") if ok { fmt.Println(foo) } // Output: bar } func ExampleStruct() { type MyStruct struct{ Value string } // Create a new cache that stores a specific struct. c := zcache.New[string, *MyStruct](zcache.NoExpiration, zcache.NoExpiration) c.Set("cache", &MyStruct{Value: "value"}) v, _ := c.Get("cache") fmt.Printf("%#v\n", v) // Output: &zcache_test.MyStruct{Value:"value"} } func ExampleAny() { // Create a new cache that stores any value, behaving similar to zcache v1 // or go-cache. c := zcache.New[string, any](zcache.NoExpiration, zcache.NoExpiration) c.Set("a", "value 1") c.Set("b", 42) a, _ := c.Get("a") b, _ := c.Get("b") // This needs type assertions. p := func(a string, b int) { fmt.Println(a, b) } p(a.(string), b.(int)) // Output: value 1 42 } func ExampleProxy() { type Site struct { ID int Hostname string } site := &Site{ ID: 42, Hostname: "example.com", } // Create a new site which caches by site ID (int), and a "proxy" which // caches by the hostname (string). c := zcache.New[int, *Site](zcache.NoExpiration, zcache.NoExpiration) p := zcache.NewProxy[string, int, *Site](c) p.Set(42, "example.com", site) siteByID, ok := c.Get(42) fmt.Printf("%v %v\n", ok, siteByID) siteByHost, ok := p.Get("example.com") fmt.Printf("%v %v\n", ok, siteByHost) // They're both the same object/pointer. fmt.Printf("%v\n", siteByID == siteByHost) // Output: // true &{42 example.com} // true &{42 example.com} // true }
Changes
Incompatible changes in v2
-
Use type parameters instead of
map[string]interface{}; you can get the same as before withzcache.New[string, any](..), but if you know you will only storeMyStructyou can usezcache.New[string, *MyStruct](..)for additional type safety. -
Remove
Save(),SaveFile(),Load(),LoadFile(); you can still persist stuff to disk by usingItems()andNewFrom(). These methods were already deprecated. -
Rename
Set()toSetWithExpire(), and renameSetDefault()toSet(). Most of the time you want to use the default expiry time, so make that the easier path. -
The
Increment*andDecrement*functions have been removed; you can replace them withModify():cache := New[string, int](DefaultExpiration, 0) cache.Set("one", 1) cache.Modify("one", func(v int) int { return v + 1 })The performance of this is roughly the same as the old Increment, and this is a more generic method that can also be used for other things like appending to a slice.
-
Rename
Flush()toReset(); I think that more clearly conveys what it's intended for asFlush()is typically used to flush a buffer or the like.
Compatible changes from go-cache
All these changes are in both v1 and v2:
- Add
Keys()to list all keys. - Add
Touch()to update the expiry on an item. - Add
GetStale()to get items even after they've expired. - Add
Pop()to get an item and delete it. - Add
Modify()to atomically modify existing cache entries (e.g. lists, maps). - Add
DeleteAll()to remove all items from the cache with onEvicted call. - Add
DeleteFunc()to remove specific items from the cache atomically. - Add
Rename()to rename keys, retaining the value and expiry. - Add
Proxytype, to access cache items under a different key. - Various small internal and documentation improvements.
ChangeLog
2.4.1 (2025-08-21)
- Don't extend expiry for existing keys in
ModifySet()
v2.4.0 (2025-08-20)
- Add
(Get|Touch)OrAdd(WithExpire)? - Fix
ModifySet()so the cache's default expiry is used when setting new keys
v2.3.0 (2025-08-11)
- Add
ModifySet()
v2.2.0 (2025-06-19)
- Add
ItemsAny()
v2.1.0 (2022-05-27)
- Add
Rename()
v2.0.0 (2022-03-28)
- Initial v2 release.
FAQ
How can I limit the size of the cache? Is there an option for this?
Not really; zcache is intended as a thread-safe map with time-based eviction. This keeps it nice and simple. Adding something like a LRU eviction mechanism not only makes the code more complex, it also makes the library worse for cases where you just want a map since it requires additional memory and makes some operations more expensive (unless a new API is added which make the API worse for those use cases).
So unless I or someone else comes up with a way to do this which doesn't detract
anything from the simple map use case, I'd rather not add it. Perhaps wrapping
zcache.Cache and overriding some methods could work, but I haven't looked at
it.
tl;dr: this isn't designed to solve every caching use case. That's a feature.