ubgo is in early alpha: libraries and APIs may still change.

Skip to content
ubgo Alpha

cache

The core cache interface with generics, single-flight Remember, a distributed locker and a shared conformance suite.

go get github.com/ubgo/cache
Language
Go
Go version
1.24
Core deps
0 direct
Latest tag
v0.1.0
License
Apache-2.0
Go modules
6

How it fits together

passes cachetestdoes not pass

cachetest is the shared conformance suite: each one marked green has a test that runs it. cache-memcached does not pass by design, because Memcached cannot read a key's TTL, scan by prefix or list keys.

Highlights from the README

  • One interface, every backend — Cache is bytes-in/bytes-out; adapters for in-memory, Redis, Postgres/SQLite, tiered, and Memcached.
  • Typed generics — Remember[T], GetT[T], SetT[T], Typed[T], RememberMulti[T], Warm[T], Memoize — no manual (de)serialization at call sites.
  • Single-flight Remember — concurrent misses for the same key collapse to one loader call.
  • Cache patterns built in — refresh-ahead, stale-while-revalidate (SWR), stale-if-error, negative caching, TTL jitter.
  • Pluggable codecs — JSON, gob, raw; EncryptedCodec (AES-GCM) for caching PII/secrets in shared stores; msgpack/zstd/protobuf in contrib/.
  • Namespace isolation — Namespaced prefixes keys so many services share one backend safely.
  • Portable distributed lock — Locker built on SetNX, works on any adapter with no backend-specific code.
  • Cross-process invalidation — best-effort fan-out bus so L1 caches drop stale copies.

Code from the README

c := memcache.New(memcache.WithMaxEntries(100_000))
defer c.Close()

// Bytes in, bytes out. Get returns (nil, cache.ErrNotFound) on miss — never (nil, nil).
_ = c.Set(ctx, "k", []byte("v"), time.Minute)
b, err := c.Get(ctx, "k")

// Typed load-through with stampede protection and the differentiator options:
u, err := cache.Remember(ctx, c, "user:42", 5*time.Minute,
    func(ctx context.Context) (User, error) { return db.LoadUser(ctx, 42) },
    cache.WithJitter(0.1),                 // spread expiry across a batch of keys
    cache.WithStaleIfError(time.Minute),   // serve last-good if the loader fails
    cache.WithRefreshAhead(0.8),           // refresh hot keys before they expire
)

// Multi-tenant isolation:
tenant := cache.Namespaced(c, "tenant:42")

// Portable distributed lock (works on any backend):
l := cache.NewLock(c, "cron:nightly", 30*time.Second)
if err := l.Acquire(ctx); err == nil {
    defer l.Release(ctx)
    // ... only one holder runs this ...
}

From the cache README.

At a glance (from the README)

ModuleBackend
cache-memin-memory, sharded — Adaptive W-TinyLFU (default) or LRU; snapshot + periodic checkpoint
cache-redisRedis 6+ (+ Pub/Sub invalidation)
cache-pgPostgres 14+ / SQLite (portable database/sql)
cache-tieredL1/L2(/L3) composer with read-promotion + cross-process invalidation
cache-memcachedMemcached (partial — protocol-limited)
cache-clusterclient-side sharding across nodes
cache-cliinspector binary