- Docs
- Data and storage
- Cache
Cache
Cache values with a TTL, in memory or in Redis, with typed helpers and compare-and-swap.
cache.Cache stashes slow-to-compute values with a TTL, keyed by something stable such as a user ID or query hash. It is not a primary store: entries expire, evict under max_entries pressure, and vanish on restart for memory.
Quick start
Section titled “Quick start”Pick memory for one process (default, no infra); pick redis when instances or web and worker processes must share entries.
CACHE_ADAPTER=memoryCACHE_SWEEP_INTERVAL=1mCACHE_MAX_ENTRIES=10000CACHE_ADAPTER=redisCACHE_ADDR=localhost:6379CACHE_POOL_SIZE=20CACHE_MIN_IDLE_CONNS=2File durations are integer nanoseconds; env accepts Go durations such as "1m". File keys are strict snake_case, so sweepinterval is rejected; env matching ignores case and _ and unknown CACHE_* variables. See the cache options for every key.
Cache a value with a TTL
Section titled “Cache a value with a TTL”Register the adapter, then resolve from the container, which resolves lazily, caches, and retries after an error; container.New(nil) is valid.
package main
import ( "context" "fmt" "time"
cachememory "github.com/zenta-dev/zever/adapters/cache/memory" "github.com/zenta-dev/zever/config" "github.com/zenta-dev/zever/container")
func main() { cachememory.Register() ctx := context.Background() c := container.New(config.Default()) defer c.Close(ctx)
ch, err := c.Cache() if err != nil { panic(err) } if err := ch.Set(ctx, "space:42", []byte("loft"), 5*time.Minute); err != nil { panic(err) } raw, err := ch.Get(ctx, "space:42") if err != nil { panic(err) } fmt.Println(string(raw))}loftGet on a missing key wraps cache.ErrNotFound; the interface also has SetIfAbsent, Delete, Increment, Decrement, and Exists.
Cache structs with typed helpers
Section titled “Cache structs with typed helpers”cache.NewTyped wraps any backend with a codec, so call sites skip manual JSON.
import ( "github.com/zenta-dev/zever/core/cache" "github.com/zenta-dev/zever/shared/codec")
type SpaceCard struct { Title string `json:"title"` PriceCents int64 `json:"price_cents"`}
typed := cache.NewTyped[string, SpaceCard](ch, codec.JSONCodec[SpaceCard]{})err := typed.Set(ctx, "space:card:42", SpaceCard{Title: "Loft", PriceCents: 12000}, 5*time.Minute)card, err := typed.Get(ctx, "space:card:42")fmt.Println(card.Title, card.PriceCents) // Loft 12000Keys can be strings or numeric types. Adapters implementing cache.CompareAndSwapCache add CompareAndDelete and CompareAndExtend, which act only when the live value equals the expected one.
Choose an adapter
Section titled “Choose an adapter”| Adapter | Package | Notes |
|---|---|---|
memory |
adapters/cache/memory |
Default, process-local, closes instantly |
redis |
adapters/cache/redis |
Shared, pool-backed, Close(ctx) drains the pool |
db |
adapters/cache/db |
Over shared/kvstore; empty dsn is a private in-memory sqlite, or use a postgres URL. Counters are read-modify-write and not linearizable under contention |
All three serve one interface, so switching is config-only. The db adapter shares one pool per exact DSN unless dedicated_pool: true. Size max_entries (memory default 1000, sweep every 1m) for the working set, and on Redis set pool_size, min_idle_conns, pool_timeout, max_conn_idle_time, and max_conn_lifetime explicitly. Keep TTLs explicit and short for user-facing reads. Version keys when the cached type changes (space:card:v2:42): typed Get decodes at read time, so old JSON fails after a rename. See the adapters matrix.
Troubleshooting
Section titled “Troubleshooting”Typed Get returns decode errors after a deploy
Cause: stale entries hold the old struct shape. Fix: bump the key version and let old keys expire, or Delete the old prefix.
Instance A writes, instance B misses
Cause: memory in a multi-instance deploy. Fix: set cache.adapter: redis with a shared addr and confirm the host with RedactedServices.
Load fails with unknown field sweepinterval
Cause: strict file decoding. Fix: rename to sweep_interval with integer nanoseconds; use "1m" strings in env only.
Shutdown hangs or logs pool timeouts
Cause: no close deadline while Redis drains, or the cache closed before workers finished. Fix: pass context.WithTimeout to c.Close(ctx).