Library / SDK
coocood/freecache avatar
coocood/freecache

coocood/freecache: a Go cache with 512 pointers and preallocated memory

A cache library for Go with zero GC overhead.

5,410 stars410 forksGoMIT

At a glance

What is it?
FreeCache trades the flexibility of Go's built-in map for a fixed memory budget and almost no garbage collector work. Here is how the segments, ring buffer and index fit together, and where the design stops being the right answer.
Who is it for?
Adopt FreeCache when you need a large in-process cache with a hard memory ceiling and you can live with an approximate LRU eviction order and second-granularity expiration. Do not adopt it when you need runtime resize, persistence, or object storage that survives a restart, because the README lists dump-to-file and runtime resize as open TODO items.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem FreeCache solves: long-lived objects and the Go garbage collector

A large in-process cache is a hostile workload for Go's garbage collector. The entries stay alive for a long time, they are reachable through pointers, and the collector has to walk them on every cycle. The README frames the tradeoff bluntly: long lived objects in memory introduce expensive GC overhead, and the library exists so you can cache a large number of objects without increased latency and degraded throughput.

That makes FreeCache a fit for services that want to keep hundreds of megabytes or more of hot data in the process, in front of a database or an upstream API, without the process spending a growing share of its CPU on collection. It is not a general-purpose map replacement. The API is byte-oriented: keys and values are []byte, there is no generic type parameter, and there is no serialization layer. If you want to store structs, encoding them is your job.

The intended audience is therefore narrow but real: Go services where the cache is large, long-lived, and read-heavy, and where a latency spike caused by GC is more expensive than the convenience of a typed map.

How the 256-segment ring buffer keeps the pointer count at 512

The mechanism is a pointer-count reduction, not a collector trick. According to the README, no matter how many entries are stored, FreeCache holds only 512 pointers. The data set is sharded into 256 segments by the hash value of the key, and each segment owns exactly two pointers: one to a ring buffer holding keys and values, and one to an index slice used to look up an entry.

Because the collector sees a fixed, small set of roots rather than one pointer per entry, the cost of a GC cycle stops scaling with the number of cached items. The ring buffer is the storage; the index slice is the lookup structure. Each segment carries its own lock, which is what allows concurrent access without a single global mutex serializing every operation.

The eviction policy is described as nearly LRU, not LRU. That wording matters. A strict LRU list would require pointer-chasing per entry, which is exactly what this design avoids, so the ordering is an approximation produced by the ring buffer layout. If your workload depends on exact recency ordering, this is an approximation you have to accept.

The dependency list is small: go.mod requires github.com/cespare/xxhash/v2 for hashing, and the rest is the standard library. The implementation lives in a handful of files: cache.go, segment.go, ringbuf.go, iterator.go and timer.go.

Installing FreeCache and running a first cache

FreeCache is a Go module, so installation is a normal module fetch. From a project that already has a go.mod, run:

bash
go get github.com/coocood/freecache

The README's example builds a 100 MB cache, writes a key with a 60 second expiry, reads it back, deletes it, and prints the entry count. Note that the size argument is in bytes, and that memory is preallocated at construction time.

go
cacheSize := 100 * 1024 * 1024
cache := freecache.NewCache(cacheSize)
debug.SetGCPercent(20)
key := []byte("abc")
val := []byte("def")
expire := 60 // expire in 60 seconds
cache.Set(key, val, expire)
got, err := cache.Get(key)

After the Get, err is nil and got holds the bytes you stored. The README's own example prints the value, then calls cache.Del(key) and prints the number of affected entries and cache.EntryCount().

One line in that snippet is easy to skim past. The README advises setting debug.SetGCPercent() to a much lower percentage when you allocate a large amount of memory, which is why the example uses 20. Because the cache preallocates its full size up front, a large cacheSize changes the shape of your heap immediately, and the default GC target may no longer suit it.

The toy Redis server in the server directory

The repository ships a server/ directory alongside the library, and the feature list describes it as a toy server that supports a few basic Redis commands with pipeline. It is presented as a toy, not as a production Redis replacement, and the README does not document its command coverage, its configuration, or its deployment story.

Treat it as a way to exercise the cache over a network protocol, or as a reference for how to wire the library into a command loop. Anything beyond that is undocumented ground. If you need a real network cache with replication, persistence and a documented command set, this component is not the answer, and the README does not claim otherwise.

The same caution applies to the iterator support listed in the features. iterator.go exists in the repository, but the README gives no example of iterating and no statement about whether iteration holds a lock or sees a consistent view across segments. That is a gap you would have to close by reading the source.

Where FreeCache is the wrong tool

The most concrete limitation is documented under Notice: if you set a key to expire in X seconds, the effective duration falls in the range (X-1, X] seconds. The README explains that sub-second time is ignored when computing expiration, and gives an example: at 8:15:01.800, where 800 milliseconds have passed since 8:15:01, the actual duration becomes X-800ms. Expiry granularity is one second, and it rounds down. A cache used to enforce a hard TTL boundary, for example a rate limiter or a token store, needs to account for that window.

The second limitation is structural. Memory is preallocated, and the README's TODO list still contains support for dumping to a file and loading from a file, plus resizing the cache size at runtime. Neither exists. You choose the size at construction and you live with it until the process restarts. A workload whose working set grows unpredictably will either waste the preallocated memory or start evicting sooner than expected, and the README does not describe a way to detect which is happening.

Third, the byte-oriented API means FreeCache is a poor fit if you want typed storage, reflection-based serialization, or a cache that behaves like map[K]V. You pay the encoding cost on every Set and every Get.

Finally, the benchmark numbers in the README are single threaded and compare against the built-in map: Set is about 2x faster, Get about 1/2x slower. The README argues FreeCache should be many times faster in a multi-threaded environment because a built-in map needs a single lock, but that claim is stated as an expectation, not shown as a benchmark.

FreeCache compared with BigCache, Groupcache and FastCache

The related searches around this project are dominated by other Go cache libraries, which is a fair reflection of how the choice is usually framed. The difference worth understanding is where each design puts its complexity.

BigCache, which appears repeatedly in those searches, is the closest analogue in intent: an in-process Go cache built to avoid GC pressure on large heaps. Both projects accept an approximate eviction policy and a byte-oriented API in exchange for predictable collector behaviour. The distinction to check is the sharding and eviction mechanism each one documents, because that is what determines your worst-case latency under eviction.

Groupcache comes at the problem from a different direction. It is a distributed cache and a library for filling caches, so its concern is coordinating multiple nodes and avoiding duplicate upstream fetches. FreeCache has no distribution layer at all; it is one process, one preallocated buffer, 256 segments. If you need several nodes to share a cache, FreeCache only helps you as the local tier.

FastCache targets a similar niche with its own segmenting strategy and a different API surface. Cache2go and TTLCache sit at the opposite end: they are small, convenient, entry-oriented caches where the entries are ordinary Go objects. Those are easier to use and exactly the shape that produces the GC overhead FreeCache was written to avoid.

The honest summary is that FreeCache's distinguishing feature is not its API, which is minimal, but its memory model: a fixed allocation, a fixed pointer count, and a hard ceiling. If that memory model is not what you need, one of the simpler libraries will be less friction.

Maintenance, licence and the cost of upgrading

The repository is not archived. The last push was on 2026-03-19, and the most recent release, v1.2.7, carries the same date; v1.2.6 landed earlier that day and v1.2.5 on 2026-02-10. The release cadence over that period is patch-level, which is consistent with a library whose core mechanism has been stable for a long time.

FreeCache is licensed under the MIT License, and the LICENSE file sits at the top level of the repository. MIT is permissive: it allows use in closed-source software and imposes no copyleft obligation on your own code. That is a summary of the licence text, not legal advice; if your organisation has rules about third-party dependencies, route it through them.

The upgrade cost is unusually low for a dependency of this kind. The module has one direct requirement, github.com/cespare/xxhash/v2, and go.mod declares go 1.21. Because the API is a small set of methods on a cache object, and because the memory model is fixed at construction, patch upgrades are unlikely to change your integration. The real cost is not upgrading the library; it is sizing it correctly the first time, since the README does not offer runtime resize and you cannot grow the cache without a restart.

Editorial conclusion

Adopt FreeCache when you need a large in-process cache with a hard memory ceiling and you can live with an approximate LRU eviction order and second-granularity expiration. Do not adopt it when you need runtime resize, persistence, or object storage that survives a restart, because the README lists dump-to-file and runtime resize as open TODO items. Before committing, verify the effective expiry window on your own keys, since the README states the duration falls in the range (X-1, X] seconds, and check whether your allocation size forces a lower debug.SetGCPercent.

Frequently asked questions

What is coocood/freecache used for?

It is a Go cache library for keeping a large number of entries in memory without the GC overhead that normally comes with long-lived objects. The README describes it as storing hundreds of millions of entries with strictly limited memory usage.

How do I create a FreeCache cache in Go?

You call freecache.NewCache with a size in bytes, for example 100 * 1024 * 1024 for 100 MB. The README notes that memory is preallocated, so the size is reserved when the cache is created.

Is FreeCache a general replacement for a Go map?

No. Keys and values are []byte, there are no type parameters, and the README's single-threaded benchmark shows Get is about 1/2x slower than the built-in map, while Set is about 2x faster.

Does FreeCache support dumping the cache to a file or resizing it at runtime?

Not according to the README. Both support for dump to file and load from file and support for resizing the cache size at runtime are listed as TODO items.

How accurate is FreeCache expiration?

If you set an expiry of X seconds, the README states the effective duration falls in the range (X-1, X] seconds because sub-second time is ignored when the expiration is calculated.

Official sources

  1. coocood/freecache on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/coocood-freecache.svg)](https://hysenlabs.com/projects/coocood-freecache)