samber/hot in-process caching
Treat a hot cache as process-local state. Start by reading go.mod, the NewHotCache builder chain, loader implementations, invalidation sites, metrics registration, and shutdown. Verify builder and method names against the selected module version.
Define the cache contract
Record the decisions that callers can observe:
- key dimensions, including tenant, identity, locale, permissions, and schema version;
- value ownership and whether returned pointers may be mutated;
- freshness, stale, and hard-expiry windows;
- how absence differs from a loader failure;
- capacity in entries and the expected memory envelope;
- how writes and multi-replica deployments invalidate cached reads.
Build with the needed features
The common entry point is hot.NewHotCache[K, V](algorithm, capacity), followed by builder options and Build(). The selected release may expose:
| Concern | Representative API |
|---|---|
| Expiration | WithTTL, WithJitter, SetWithTTL |
| Background expiry | WithJanitor, StopJanitor |
| Read-through | WithLoaders, GetWithLoaders, batch variants |
| Stale revalidation | WithRevalidation, revalidation error policy |
| Negative caching | WithMissingCache, WithMissingSharedCache, SetMissing* |
| Contention | WithSharding and a stable Hasher[K] |
| Mutable values | WithCopyOnRead, WithCopyOnWrite |
| Startup and telemetry | WithWarmUp*, WithPrometheusMetrics |
Get returns a value, a found flag, and a loader error in versions with loader support. Handle the error before interpreting found. Peek and Has have different loader, recency, and expiry effects; use them only after checking the exact contract.
Select eviction from workload evidence
Available constants can include LRU, LFU, TinyLFU, WTinyLFU, S3FIFO, ARC, TwoQueue, SIEVE, and FIFO. Confirm availability in the pinned release.
- Recency policies fit locality-dominated workloads but can suffer from scans.
- Frequency and admission policies can protect repeatedly accessed keys but retain more history.
- Adaptive or segmented policies trade simpler behavior for workload adaptation.
- FIFO-like policies are easier to reason about when recency and frequency are not useful signals.
Compare candidates with representative traces, hit and eviction metrics, loader latency, and retained memory. Capacity is an entry limit, not a byte budget.
Loaders, absence, and races
The loader form is version-sensitive but commonly batches keys as func([]K) (map[K]V, error). Check loader-chain ordering, whether later loaders receive unresolved keys, and whether concurrent misses are coalesced.
Keep cancellation reachable even when the library's loader type has no context parameter, usually through a project wrapper or captured request-independent lifecycle context. Define behavior for invalidation racing with a load, refresh failure, and a key known to be absent. Negative-cache TTLs should reflect how quickly absence can change.
Copy values when callers need isolation. A cached pointer, slice, or map otherwise shares mutable state across hits.
Lifecycle and tests
WithJanitor owns background work and must pair with StopJanitor in the application's shutdown owner. Check incompatibilities such as janitor use with a no-lock configuration in the selected release. Register metrics once per collector name and registry.
Test hit and miss paths, loader errors, batch partial hits, negative entries, expiry and stale transitions, eviction, concurrent same-key loads, delete/load races, copies, and janitor shutdown. Use a controllable clock exposed by a local wrapper when possible and run race-enabled tests for concurrency changes.
Measure memory and backend load with realistic key/value distributions before reporting a cache improvement.