c3e is a high-performance, dependency-aware caching layer for Go built on top of Valkey (Redis-compatible). It adds dependency tracking with cascading invalidation, stale-while-revalidate, thundering-herd protection, TTL jitter, and type-safe operations on top of a plain cache.
- π Dependency tracking & cascading invalidation β invalidate one entity and every entry that depends on it is dropped, transitively.
- β‘ Stale-while-revalidate β serve cached data instantly while refreshing in the background.
- π‘οΈ Thundering-herd protection β singleflight ensures one fetch per key under concurrent load.
- π― Type-safe operations β generics give compile-time safety.
- π² TTL jitter β randomized expiry prevents synchronized stampedes.
- π₯ Fast fallback β a short query timeout falls back to your source when the cache is slow or down.
- π Observability β an injectable
sloglogger plus metrics/tracing hooks.
go get github.com/slashdevops/c3eRequirements: Go 1.26+ Β· valkey-go v1.0.76+.
package main
import (
"context"
"time"
"github.com/slashdevops/c3e"
"github.com/valkey-io/valkey-go"
)
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Owner string `json:"owner"`
}
func main() {
// 1. Valkey client.
valkeyClient, err := valkey.NewClient(valkey.ClientOption{
InitAddress: []string{"localhost:6379"},
DisableRetry: true, // fail fast instead of queueing when the cache is down
})
if err != nil {
panic(err)
}
defer valkeyClient.Close()
// 2. Cache stack.
cacheManager, err := c3e.NewCacheManager(valkeyClient, false)
if err != nil {
panic(err)
}
cache, err := c3e.NewSafeCacheManager(cacheManager, c3e.SafeCacheManagerConfig{
HardTTL: 5 * time.Minute, // absolute expiration
SoftTTL: 3 * time.Minute, // stale-after (β60% of HardTTL)
JitterPercent: 0.1, // 10% TTL jitter
})
if err != nil {
panic(err)
}
// 3. Read through the cache. On a miss, the fetcher runs and its result is
// cached together with the entities it depends on.
ctx := context.Background()
id := c3e.CacheIdentifier{Type: "project", ID: "123"}
project, err := c3e.GetSafe(ctx, cache, id,
func(ctx context.Context) (Project, []c3e.CacheIdentifier, error) {
p := Project{ID: "123", Name: "Eagle", Owner: "user-456"}
deps := []c3e.CacheIdentifier{{Type: "user", ID: p.Owner}}
return p, deps, nil
})
if err != nil {
panic(err)
}
_ = project
// When the owner changes, every entry depending on it is invalidated.
_ = cache.Invalidate(ctx, c3e.CacheIdentifier{Type: "user", ID: "user-456"})
}c3e layers a low-level Valkey primitive under an application-facing,
stale-while-revalidate API:
flowchart TD
App["Your application"]
subgraph c3e["c3e"]
direction TB
S["<b>SafeCacheManager</b><br/><i>stale-while-revalidate Β· singleflight Β· jitter Β· hooks</i>"]
C["<b>CacheManager</b><br/><i>data + dependency graph</i>"]
R["<b>CacheRepository</b><br/><i>Valkey/Redis client</i>"]
S --> C --> R
end
App -->|"Get / Invalidate"| S
R --> VK[("Valkey / Redis")]
S -. "miss Β· stale Β· error" .-> DB[("Source of truth")]
Every Get resolves to one outcome, reported to the OnGet hook as a Result:
flowchart TD
G(["Get(id, fetcher)"]) --> L{"lookup<br/>(bounded by QueryTimeout)"}
L -->|"fresh Β· age β€ SoftTTL"| H["serve cached<br/><b>β hit</b>"]
L -->|"stale Β· SoftTTL < age β€ HardTTL"| ST["serve stale now<br/>+ refresh in background<br/><b>β stale</b>"]
L -->|"not cached"| M["fetch Β· cache Β· serve<br/><b>β miss</b>"]
L -->|"slow / error"| E["fetch Β· serve<br/><b>β timeout / error</b>"]
Fresh and stale both serve immediately; miss/timeout/error fall back to the source with concurrent callers collapsed into a single fetch. For the full picture β the key scheme and the dependency reverse index β see docs/architecture.md.
Full documentation lives in docs/:
- Architecture β layers, read path, key scheme, cascade.
- Usage β worked examples and composition patterns.
- Configuration β every config field and how to tune it.
- Observability β the logger and the hooks.
- Best practices β do/don't and troubleshooting.
- Limitations & semantics β the invalidation contract and consistency model. Read this before production use.
API reference: pkg.go.dev/github.com/slashdevops/c3e.
Contributions are welcome. Please make sure the suite is green before opening a pull request:
go test -race ./... # unit tests
docker run -d --rm -p 6379:6379 valkey/valkey:latest # for the integration tag
go test -race -tags=integration ./...Licensed under the Apache License 2.0 β see LICENSE.
- Built for Valkey, the high-performance Redis alternative.
- Inspired by HTTP
Cache-Controlstale-while-revalidate. - Thundering-herd protection via golang.org/x/sync/singleflight.