cache-tiered
Stacks cache backends into L1/L2/L3 tiers with read promotion and per-tier TTL.
go get github.com/ubgo/cache-tiered
- Language
- Go
- Go version
- 1.24
- Core deps
- 2 direct
- Latest tag
- v0.1.0
- License
- Apache-2.0
- Go modules
- 1
Highlights from the README
- Read-promote. Probe L1→Ln; the first hit is copied into every shallower tier so the next read is faster.
Promotions()exposes the count for observability. - Write-through (default). Writes go to every tier. The L1 error is authoritative; deeper-tier errors are best-effort so a cold/slow backend cannot break the hot path.
WriteOnlyL1mode writes L1 only. - Per-tier TTL. Run a short L1 TTL (bounds cross-pod staleness) and a long L2 TTL from one
Setcall. - Cross-process invalidation. Plug any
cache.Invalidation(e.g. Redis Pub/Sub fromcache-redis); mutations publish, peers drop their local L1 copy. - Atomic ops stay correct.
SetNX/Incr/Decruse L1 as the source of truth and mirror the result to deeper tiers. - Drop-in. It is a
cache.Cache; swap any single backend for a tiered one with no call-site changes.
Code from the README
ctx := context.Background()
b, err := tc.Get(ctx, "k") // probe + promote
m, err := tc.GetMulti(ctx, []string{"a", "b"}) // per-key Get
ok, err := tc.Has(ctx, "k") // first tier that has it
d, err := tc.TTL(ctx, "k") // first tier's reported TTL
err = tc.Set(ctx, "k", []byte("v"), time.Minute) // write-through (per-tier TTL)
err = tc.SetMulti(ctx, map[string]cache.Item{"a": {Value: []byte("1")}})
created, err := tc.SetNX(ctx, "k", []byte("v"), time.Minute) // L1 decides; mirrored down
err = tc.Expire(ctx, "k", time.Hour) // cascades; per-tier not-found tolerated
err = tc.Touch(ctx, "k")
n, err := tc.Incr(ctx, "hits", 1) // L1 authoritative; deeper tiers mirrored to n
n, err = tc.Decr(ctx, "hits", 2)
err = tc.Del(ctx, "a", "b") // cascade + publish per key
err = tc.DeleteByPrefix(ctx, "user:") // cascade + publish InvalidateAll
err = tc.Flush(ctx) // cascade + publish InvalidateAll
it := tc.Iterate(ctx, cache.IterateOpts{Prefix: "user:"}) // deepest tier (fullest view)
defer it.Close()
for it.Next() { _ = it.Key(); _ = it.Value() }
err = tc.Ping(ctx) // first unhealthy tier
s := tc.Stats() // hits/sets summed; entries from deepest tier
p := tc.Promotions() // deeper-hit-copied-upward count
err = tc.Close() // idempotent; stops invalidation; closes each tier onceFrom the cache-tiered README.
At a glance (from the README)
| Adapter | Role | Latency | Cross-process invalidation | Best for |
|---|---|---|---|---|
| cache-tiered (this) | composes others | L1 ~ns, then L2/L3 | Yes (pluggable bus) | Hot-path latency + shared consistency |
| cache-redis | single backend | network | Yes (Pub/Sub source) | Shared L2 |
| cache-pg | single backend | network | No | Durable L3 |
| cache-memcached | single backend | network | No | Memcached interop tier |