# eko/gocache: a Go cache library that puts a chain, a loadable layer and metrics in front of your store

> eko/gocache wraps ten cache backends behind one interface and adds chain, loadable and metric layers on top. The store table in the README is the part worth reading before you adopt it.

**eko/gocache** — ☔️ A complete Go cache library that brings you multiple ways of managing your caches

- Repository: https://github.com/eko/gocache
- Website: https://vincent.composieux.fr/article/i-wrote-gocache-a-complete-and-extensible-go-cache-library/
- Stars: 2,884 · Forks: 222
- Language: Go
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/eko-gocache

## The problem eko/gocache solves, and the Go teams it fits

Most Go services start with one cache and one client library. Then the shape changes. A single-process memory cache stops being enough once you run more than one replica, so a Redis layer appears behind it. Later someone wants a callback that repopulates an entry instead of returning a miss. Later still, someone asks how often the cache hits. At that point the service has three cache libraries, four call sites and no shared interface.

eko/gocache is a library for that second stage. The README describes it as "an extendable cache library that brings you a lot of features for caching data", and the feature list is the pitch: multiple stores, a chain cache, a loadable cache, a metric cache, a marshaler, default values per store, invalidation by expiration or tags, and generics. The library ships as separate Go modules: the core under lib/v4 and one module per backend under store/, so pulling in the Redis store does not drag in the Hazelcast client.

It is aimed at application developers, not infrastructure teams. Nothing here runs as a service. You import it, you hand it a store, and you get a cache.Cache value. The value-add is the layer above the backend, not the backend itself: eko/gocache does not implement its own eviction or its own wire protocol, it wraps bigcache, ristretto, go-cache, freecache, memcache, Redis, Redis cluster, rueidis, Valkey, Hazelcast and Pegasus.

## How the store, cache and chain layers fit together

There are two levels. A store implements the low-level interface and talks to the actual backend. A cache wraps a store and exposes Set, Get, Delete and Clear. The generic parameter on cache.New is the value type the cache handles.

The README states that cache.Cache converts between string and []byte for you, so cache.New[string](bigcacheStore) and cache.New[[]byte](redisStore) both work. That conversion matters because the backends disagree about types. The store support table in the README lists Bigcache as accepting string or []byte in Set() and returning []byte from Get(); Redis accepts anything go-redis can marshal and returns string; Go-cache accepts any value and returns it as stored; Ristretto is the odd one out, where the key type is a generic parameter rather than always a string.

When a store only handles bytes and you want to store a struct, the marshaler wrapper handles the encoding. Above the cache level sit the composite caches. A chain cache holds several caches in priority order, so a memory cache can be consulted first and a shared Redis cache second. A loadable cache takes a callback that repopulates the value on a miss. A metric cache records hits, misses, set successes and set errors. These compose: the README's own framing is "memory then fallback to a redis shared cache for instance".

Two behaviours in that table deserve attention because they leak upward. Bigcache has no per-key TTL, so GetWithTTL() reports a fixed 5 minutes, a choice the README explains as necessary to keep Bigcache usable inside a Chain cache. And Ristretto is allowed to drop a Set() when its buffers are full, which is documented as by design in its own FAQ; in that case Set() returns an error, and store.WithSynchronousSet() is the option to use when the write must be visible immediately.

## Installing eko/gocache and running a first cache

The library is installed with go get. The core module is lib/v4, and each backend is a separate module you fetch only if you use it. The README warns that if you run into errors, run go mod tidy to clean your go.mod file.

```bash
go get github.com/eko/gocache/lib/v4
go get github.com/eko/gocache/store/bigcache/v4
```

After that, the imports point at the same two paths. Note that the store package is imported under an alias in the README's examples, so the constructor reads as bigcache_store.NewBigcache rather than bigcache.NewBigcache.

```go
import (
	"github.com/eko/gocache/lib/v4/cache"
	bigcache_store "github.com/eko/gocache/store/bigcache/v4"
)
```

A minimal use is a store, a cache over it, and a Set. The README shows the same pattern with Memcache, including an override of the store-level default expiration at the call site.

```go
cacheManager := cache.New[[]byte](bigcacheStore)
err := cacheManager.Set(ctx, "my-key", []byte("my-value"),
	store.WithExpiration(15*time.Second),
)
if err != nil {
	panic(err)
}

value := cacheManager.Get(ctx, "my-key")
cacheManager.Delete(ctx, "my-key")
cacheManager.Clear(ctx)
```

The Memcache example in the README sets a default expiration of 10 seconds on the store and then overrides it to 15 seconds on the Set call, which is the pattern for all stores that accept per-key expiration. Clear() flushes the entire cache, so it is the one call to keep away from a shared Redis instance that other services also use.

## Where eko/gocache gets in the way

The store support table is the honest part of this project, and it is also the part that decides whether the abstraction helps you. The wrappers promise uniformity, but the backends do not deliver it, so the table has columns for what Set() accepts, what Get() returns, per-key expiration, tags and SetIfNotExists(). Bigcache, Freecache, Pegasus and Rueidis have no SetIfNotExists(); Bigcache has no per-key expiration at all. If your code needs a conditional write, you are choosing a backend by that column, not by the abstraction.

The Ristretto behaviour is the sharper edge. A Set() can be dropped when internal buffers are full, and the README points at Ristretto's own FAQ to say this is by design. A cache that silently declines a write is a reasonable design for an admission-controlled cache and a bad one for anything treating Set() as a durability boundary. store.WithSynchronousSet() exists precisely because the default is not synchronous.

Bigcache's fixed 5 minute TTL report is the other leak. GetWithTTL() does not return what you set, because Bigcache has no per-key TTL. The value is a constant chosen so the store remains usable inside a Chain cache. Any logic that reads the reported TTL to decide whether to refresh or to extend will be wrong on Bigcache, and it will be wrong in a way that looks correct in tests, because the constant is stable.

There is also a structural cost. The core and each store are separate modules, and the Makefile contains a target that rewrites the lib/v4 version inside every store's go.mod. That is a maintenance signal for the maintainers, and for you it means the store module you import pins a lib/v4 version you may need to reconcile with the one your application uses.

## Choosing a backend versus choosing a wrapper

The realistic alternative is not one library but two decisions.

For the backend, the projects eko/gocache wraps are all usable directly. If your cache is a single-process memory cache, importing bigcache, ristretto or patrickmn/go-cache on its own gives you their full API, their own tuning knobs and no translation layer. The difference in approach is that eko/gocache exposes the intersection of these APIs plus its own additions, while the backend exposes everything it has. If you need a Ristretto knob that the store wrapper does not surface, the wrapper is in your way.

For the wrapper layer, the alternative is writing the composition yourself. A two-level cache with a memory tier and a Redis tier is not a large amount of code, and neither is a hit counter. What you would be reproducing is the chain ordering, the loadable callback, the metric cache and the marshaler, all of which already exist here and are tested. The trade is control over a few hundred lines against a dependency on a library whose composite behaviours you would otherwise have to reason about from its documentation.

One more comparison from the README itself: Pegasus is marked deprecated in the store list. The list also names Redis cluster, rueidis and Valkey as separate stores rather than one Redis store with options, which tells you the project treats client libraries as distinct integration points rather than hiding them behind a single Redis abstraction.

## Versioning, licence and the cost of upgrading

eko/gocache is MIT licensed. That is permissive and short, and it places no condition on how you distribute your own binary. This is not legal advice; read the LICENSE file in the repository for the actual terms.

The versioning layout is the thing to plan around. The core lives at lib/v4 and each store has its own module path with its own v4 suffix. The recent release list shows lib/v4.4.0 and lib/v4.2.5 both tagged on 2026-09-08, twenty minutes apart, plus lib/v4.2.4 on 2026-07-26. Store modules carry their own tags under store/, which the Makefile confirms: store-latest-versions lists the latest tag per store directory, and store-increment-patch-version walks the store directories and tags the next patch for each, printing "Skipping store/<name>: no version tagged yet" when a store has never been tagged.

The practical consequence is that an upgrade is not one version bump. You upgrade lib/v4, then check whether the store modules you depend on have a release that targets it. The Makefile target update-stores-version exists to rewrite the lib/v4 version inside every store's go.mod, which is a maintainer-side operation, not something an application runs. The last push to the repository was on 2026-09-13.

## Conclusion

Adopt eko/gocache if you already run a cache backend and want a chain layer, a loadable wrapper and Prometheus metrics without writing that plumbing yourself. Do not adopt it if you need a single backend with fine-grained control over its eviction, because the wrapper adds a second set of rules on top of the store's own. Before writing code, check the store support table for your chosen backend: whether Set() accepts your value type, whether Get() returns []byte or string, and whether SetIfNotExists() exists at all. Then decide whether Bigcache's fixed 5 minute TTL report and Ristretto's dropped writes are acceptable in your read path.

## FAQ

### What is eko/gocache?

It is a Go cache library that wraps multiple cache backends behind one interface and adds chain, loadable and metric caches on top. The core module is github.com/eko/gocache/lib/v4, with one module per store under store/.

### How do I delete the Go cache?

In eko/gocache you call Delete on the cache manager with a key, or Clear to flush the entire cache. The README shows both calls after a Set: cacheManager.Delete(ctx, "my-key") and cacheManager.Clear(ctx).

### Which stores can eko/gocache use?

The README lists Bigcache, Ristretto, go-cache, Memcache, Redis, Redis cluster, rueidis, Valkey, Freecache, Hazelcast and Pegasus. Pegasus is marked deprecated in the store list, and a custom store is also possible.

### Why does GetWithTTL() return 5 minutes with Bigcache?

Bigcache has no per-key TTL, so the README states that GetWithTTL() reports a fixed 5 minutes in order to stay usable in a Chain cache. The value is a constant, not the expiration you set.

### Can eko/gocache store structs instead of bytes?

Yes, through the marshaler wrapper, which the README lists as a feature for automatically marshaling and unmarshaling cache values as a struct. For stores that only handle bytes, this is the documented route.

## Sources

- [eko/gocache on GitHub](https://github.com/eko/gocache)
- [License: MIT](https://github.com/eko/gocache/blob/master/LICENSE)
- [Project website](https://vincent.composieux.fr/article/i-wrote-gocache-a-complete-and-extensible-go-cache-library/)
- [README](https://github.com/eko/gocache/blob/master/README.md)
- [Releases](https://github.com/eko/gocache/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/eko-gocache
