# allegro/bigcache: a Go cache that keeps entries off the garbage collector's path

> BigCache stores byte slices in a sharded, evicting in-memory cache so that millions of entries do not slow down Go's garbage collector. It suits read-heavy services with a known key size; it is a poor fit if you want typed values, iteration over live data, or a cache that frees memory back to the OS.

**allegro/bigcache** — Efficient cache for gigabytes of data written in Go.

- Repository: https://github.com/allegro/bigcache
- Website: http://allegro.tech/2016/03/writing-fast-cache-service-in-go.html
- Stars: 8,163 · Forks: 613
- Language: Go
- License: Apache-2.0
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/allegro-bigcache

## The problem BigCache solves: heap entries that Go's GC has to walk

A plain map[string][]byte in a Go service with tens of millions of entries becomes a liability. Every key and every value is a pointer the garbage collector must trace during each cycle, and the pause grows with the number of live objects. BigCache's README states the design goal directly: it keeps entries on the heap but omits GC for them, which means operations on byte slices take place and entries need (de)serialization in front of the cache in most use cases.

That trade is the whole product. You give up typed values and get a cache whose contents the collector does not scan. The README points at the Go 1.5 optimization tracked in golang/go issue 9477 as the basis for this. The intended user is a Go service holding gigabytes of cached data where GC pause, not raw lookup speed, is the thing that hurts. A service caching a few thousand small structs has no reason to accept the serialization cost.

## Shards, byte slices and why the cache cannot hand you a typed value back

The repository layout shows the mechanism: shard.go sits next to bigcache.go, and the Config struct exposes a Shards field documented as the number of shards, which must be a power of 2. Keys are hashed (fnv.go, hash.go) and routed to a shard, so concurrent writers contend on separate locks rather than one. That is where the parallel benchmark numbers in the README come from, though the README's own benchmark table is dated (go1.13, i7-6700K) and should be read as historical rather than as a claim about current hardware.

Values are bytes. The README's simple example passes []byte("value") to Set and receives a byte slice from Get, and it says serialization in front of the cache will be needed in most cases. There is no generic type parameter in the module path github.com/allegro/bigcache/v3. If your code wants a struct back, you own the encode and decode on both sides.

Two clocks govern lifetime, and the README is explicit that they are different things. LifeWindow is the time after which an entry can be called dead but not deleted. CleanWindow is the interval at which dead entries are actually removed; entries that still have life are not touched. Setting CleanWindow below one second is described as counterproductive because bigcache has a one second resolution.

## Installing bigcache and running a first cache in Go

The module is published as github.com/allegro/bigcache/v3, and go.mod declares go 1.22, so a modern toolchain is expected even though the README still says Go 1.12 or newer is required. Fetch it with the standard module command:

```bash
go get github.com/allegro/bigcache/v3
```

The README's simple initialization uses bigcache.DefaultConfig with a LifeWindow and a context, then Set and Get with byte slices. The value printed is the string form of the stored bytes:

```go
import (
	"context"
	"fmt"
	"time"
	"github.com/allegro/bigcache/v3"
)

cache, _ := bigcache.New(context.Background(), bigcache.DefaultConfig(10 * time.Minute))
cache.Set("my-unique-key", []byte("value"))
entry, _ := cache.Get("my-unique-key")
fmt.Println(string(entry))
```

For a real service the README recommends custom initialization when the cache load can be predicted, because it avoids additional memory allocation. The fields that matter at that point are Shards (a power of 2), LifeWindow, CleanWindow, MaxEntriesInWindow and MaxEntrySize, which the README says are used only for the initial memory allocation, plus HardMaxCacheSize in MB. Verbose prints information about additional memory allocation, which is worth turning on once while you tune the numbers:

```go
config := bigcache.Config{
	Shards:             1024,
	LifeWindow:         10 * time.Minute,
	CleanWindow:        5 * time.Minute,
	MaxEntriesInWindow: 1000 * 10 * 60,
	MaxEntrySize:       500,
	Verbose:            true,
	HardMaxCacheSize:   8192,
}
cache, initErr := bigcache.New(context.Background(), config)
if initErr != nil {
	log.Fatal(initErr)
}
```

After that, Set and Get behave as in the simple example. If a Get returns nothing, the entry is either absent or has passed LifeWindow and is waiting for the next CleanWindow sweep.

## What happens when the cache is full, and other ways bigcache bites

HardMaxCacheSize is a ceiling in MB, and the README describes the consequence plainly: if the value is reached, the oldest entries can be overridden by new ones. A value of 0 means no size limit. So a cache under memory pressure silently loses data rather than refusing writes or returning an error, which is exactly the behaviour you want for a cache and exactly the behaviour that will confuse you if you treat it as a store. The same eviction path fires for expiration and for explicit Delete, and both OnRemove and OnRemoveWithReason are callbacks for those events. The README notes that a nil callback prevents unwrapping the oldest entry, so leaving them unset is not free of consequences if you later need visibility into eviction.

Memory reporting is the second trap. The README warns that system memory can appear to grow exponentially, and explains why: the Go runtime allocates memory in spans and marks them idle rather than returning them, so process resource usage stays high until the OS repurposes the address. A container memory limit set from a naive reading of RSS will kill the process for memory the cache no longer considers live.

Iteration is limited. iterator.go exists, but the README does not document iteration semantics, ordering, or whether deleted-but-unswept entries appear. Do not build a feature that depends on enumerating the cache until you have read that file. Finally, the README does not document a rollback procedure for a bad configuration change, and there is no persistence: restart the process and the cache is empty.

## bigcache against freecache and a plain concurrent map

The README's own comparison set is bigcache, freecache and map, with benchmark source in a separate repository, allegro/bigcache-bench. On the numbers printed there, BigCacheSetParallel lands at 148 ns/op against 268 ns/op for FreeCacheSetParallel, and BigCacheGetParallel at 86.1 ns/op against 147 ns/op for FreeCacheGetParallel. The GC pause comparison over 20 million entries reports 1.506077 ms for bigcache, 5.594416 ms for freecache and 9.347015 ms for map on amd64, with a much wider spread on arm64. The README itself concludes that bigcache and freecache have very similar GC pause time, which is a fair reading: freecache is the closer alternative in mechanism and intent, and the difference is in the access-path numbers rather than in collector behaviour.

The plain map is the honest baseline. It is fast for small working sets and needs no serialization, and the README notes that writes to map are the slowest of the three in its table. If your cache holds thousands of entries, use the map and skip the dependency. If it holds millions, the allocs and pause figures are the reason to move.

Redis and other out-of-process caches are a different category, not a substitute: bigcache lives in your process and disappears with it, so it cannot serve two replicas or survive a restart. Ristretto is commonly mentioned alongside it in search results, but the README does not discuss it, so any comparison there has to come from reading that project's documentation rather than this one.

## Licence, versioning and the cost of staying current

The repository is Apache-2.0, which permits commercial use and modification with the usual notice and attribution conditions; this is not legal advice, and if you redistribute a modified copy, read the LICENSE file at the repository root rather than this paragraph.

Version cadence deserves attention. The releases listed are v3.2.0 on 2026-08-14, v3.1.0 on 2022-11-04 and v3.0.2 on 2022-02-14. That is a nearly four-year gap between v3.1.0 and v3.2.0, so a team pinning to v3.1.0 has been running an old tag for a long time. Upgrade cost inside v3 should be small because the module path does not change, but the go.mod directive now reads go 1.22, and a project still building with an older toolchain will have to move its own go directive before it can take v3.2.0. The last push to the default branch was on 2026-09-21, so the repository is not dormant, but the release tags are the thing to watch, not commit activity.

## Conclusion

Adopt bigcache if you run a Go service with read-heavy, byte-slice-shaped entries and you have measured GC pauses as the constraint; the v3 module path and a Config with Shards, LifeWindow and HardMaxCacheSize are all you need to start. Do not adopt it if you need typed values, live iteration, or memory returned to the OS on eviction, and do not treat it as a distributed cache next to Redis. Before writing production code, verify three things in your own environment: that your values fit MaxEntrySize closely enough that the initial allocation is not wasted, that CleanWindow is at least one second because the resolution is one second, and what your OnRemoveWithReason callback does when HardMaxCacheSize forces the oldest entry out.

## FAQ

### What happens if my bigcache cache is full?

The README states that when HardMaxCacheSize is reached, the oldest entries can be overridden by new ones, and that a value of 0 means no size limit. Writes are not rejected, so the cache quietly drops the oldest data instead of returning an error.

### How do I install and start using allegro/bigcache in a Go project?

Add the module with go get github.com/allegro/bigcache/v3, then call bigcache.New with either bigcache.DefaultConfig(lifeWindow) or a custom bigcache.Config. Set and Get take and return byte slices, so values usually need encoding and decoding around the cache.

### What is the difference between LifeWindow and CleanWindow in bigcache?

The README defines LifeWindow as the time after which an entry can be called dead but not deleted, and CleanWindow as the interval after which dead entries are actually removed. Entries that still have life are not removed by a cleanup pass, and intervals below one second are counterproductive because the resolution is one second.

### Does bigcache store typed values or only byte slices?

Only byte slices. The README's examples call Set with []byte("value") and print the result of Get as a string, and it notes that entries will need (de)serialization in front of the cache in most use cases.

### Why does bigcache memory usage keep growing in system tools?

The README says this is expected: the Go runtime allocates memory in spans and marks them idle instead of returning them to the OS, so process resource usage stays elevated until the address is repurposed. It links to further reading on Go not freeing memory.

## Sources

- [allegro/bigcache on GitHub](https://github.com/allegro/bigcache)
- [License: Apache-2.0](https://github.com/allegro/bigcache/blob/main/LICENSE)
- [Project website](http://allegro.tech/2016/03/writing-fast-cache-service-in-go.html)
- [README](https://github.com/allegro/bigcache/blob/main/README.md)
- [Releases](https://github.com/allegro/bigcache/releases)

---

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