DistCache is a distributed read-through cache engine built in Go.
In a read-through cache, the cache sits between your application and the data source. When the application requests data:
- If the data is in the cache (cache hit), it is returned immediately.
- If the data is not in the cache (cache miss), DistCache fetches it from the primary data source (database, API, etc.), stores it in the cache, and returns it to the caller.
This reduces direct load on your backend, lowers latency, and improves scalability.
The caching engine is powered by the battle-tested groupcache-go.
- Automatic fetch on miss: data is loaded into the cache only when requested.
- Distributed architecture: data is sharded across nodes for scalability and availability.
- Reduced backend load: frequent reads are served from the cache instead of the database.
- TTL and LRU eviction: per-entry and per-keyspace TTL; bounded by per-keyspace
MaxByteswith LRU eviction provided by groupcache. Optional negative caching viaWithKeySpaceNegativeTTL. - Automatic node discovery: nodes automatically react to cluster topology changes.
- KeySpace overrides: per-keyspace TTL, timeouts, max bytes, warm keys, and protections.
- Dynamic keyspace updates: replace keyspaces at runtime via
UpdateKeySpace. - Warmup and hot key tracking: prefetch hot keys on join/leave events, with optional periodic refresh-ahead via
warmup.Config.RefreshInterval. - DataSource protection: rate limiting and circuit breaking, globally or per keyspace.
- Cluster events: subscribe to peer-joined / left / updated notifications via
Engine.Events(). - Admin diagnostics: JSON endpoints for peers and keyspace stats, plus
/healthzand/readyzprobes. - Observability: OpenTelemetry tracing and metrics for engine operations, cache misses, and DataSource fetch latency.
- TLS and gossip auth: end-to-end encrypted communication between nodes; optional symmetric
WithGossipSecretto authenticate cluster membership. - Discovery providers: built-in support for:
- Kubernetes: discover peers via the Kubernetes API.
- NATS: discover peers via NATS.
- Static: fixed list of peers, ideal for tests and demos.
- DNS: discover peers via Go's DNS resolver.
- Standalone: single-node, no cluster discovery.
go get github.com/tochemey/distcacheIntegrate DistCache by implementing two interfaces:
DataSource: fetches data from your backend on cache misses.KeySpace: defines a cache namespace, storage limit, and expiration behavior.
Then create a config and start the engine:
-
Implement
DataSource: provide aFetch(ctx, key) ([]byte, error)method that retrieves data from your backend (database, API, etc.) when a cache miss occurs. -
Implement
KeySpace: define a cache namespace by returning its name, maximum byte capacity, theDataSourceto use on misses, and an optional per-key expiration time. -
Create a config: use
NewStandaloneConfigfor single-node setups orNewConfigwith a discovery provider for distributed clusters. -
Start the engine: call
distcache.NewEngine(cfg)followed byengine.Start(ctx). -
Read and write: use
engine.Get/engine.Put(and their batch variants) to interact with the cache.
For a distributed setup, use NewConfig and supply a discovery provider
(e.g., NATS, Kubernetes,
Static, or DNS).
Two runnable examples are provided:
example: a distributed setup using NATS for peer discovery.example/advanced: a single-node walkthrough of the optional features: negative caching, periodic refresh-ahead, cluster event subscription, gossip authentication, and the admin server.
All capabilities are exposed through the Engine:
| Method | Description |
|---|---|
Put |
Store a key/value pair in a keyspace |
PutMany |
Store multiple key/value pairs |
Get |
Retrieve a key/value pair |
GetMany |
Retrieve multiple key/value pairs |
Delete |
Remove a key/value pair |
DeleteMany |
Remove multiple key/value pairs |
DeleteKeySpace |
Delete a keyspace and all entries |
DeleteKeyspaces |
Delete multiple keyspaces |
UpdateKeySpace |
Replace a keyspace definition at runtime |
KeySpaces |
List all keyspaces |
Events |
Subscribe to cluster-membership events |
Note: DistCache is eventually consistent. It is built for fast reads with bounded staleness, not for linearizable or transactional workloads.
Per-operation contract:
Get: returns the most recently observed value at the queried node. That value may briefly lag the writer or the source-of-truth.Put/PutMany: returns once the key's owner has accepted the write. The new value is then asynchronously fanned out to every other peer. Failures on non-owner peers are logged and not retried.Delete/DeleteMany: RPCs the owner and every other peer. Returns a multi-error if any peer is unreachable. Surviving peers may serve the stale value until TTL or LRU eviction.DeleteKeySpace/UpdateKeySpace: local to the calling node. Re-issue the call on every node to roll out cluster-wide.
What this means in practice:
- After a
Put, a read on the same node sees the new value immediately. A read on a different peer sees it within milliseconds in steady state, later if the fan-out RPC failed. - During a network partition, each side accepts reads and writes independently. There is no quorum and no fencing. Set TTLs short enough to bound the staleness window after the partition heals.
- If you write to your source-of-truth out of band, the cache continues to
serve the old value until TTL expires or until you call
Engine.Delete.
DistCache is a good fit for read-heavy workloads that tolerate seconds of staleness. It is not a fit for linearizable reads, counters, or anything requiring a strict order of writes.
Contributions are welcome.
This project follows Semantic Versioning and Conventional Commits.
- Fork the repository.
- Create a feature branch.
- Commit your changes using Conventional Commits.
- Submit a pull request.