cacheflight
cacheflight deduplicates concurrent async work for the same cache key and stores the result in a user-provided cache backend. It supports flat TTL, stale-while-revalidate (SWR), and probabilistic early expiration (XFetch) — all without forcing a specific cache store or serialization format.
Features
- Cold-miss deduplication — when N callers race for the same missing key, only one recomputes; the rest share the result.
- Stale-while-revalidate — serve a stale value immediately while a background refresh runs.
- Probabilistic early expiration (XFetch) — trigger background refreshes before the TTL expires, proportional to compute time, smoothing out thundering herds.
- Type-state API — invalid configurations are caught at compile time (e.g. calling
stale_for()on a flat-TTL instance won't compile). - Plugable backends — implement
CacheBackendfor any store, or use the built-inMemoryCacheorRedisCache. - Observability hooks —
MetricsHookstrait with no-op defaults, re-implement for production metrics. - Safe fallback — cache read failures degrade to recompute instead of failing the caller.
- Cache invalidation —
invalidate(key)andinvalidate_prefix(prefix)to evict entries programmatically.
Installation
[]
= "0.1"
Redis support is included by default. To use only the in-memory backend:
[]
= { = "0.1", = false }
Quick start
use ;
use Duration;
async
Backends
MemoryCache (built-in, no extra dependencies)
An in-process cache backed by DashMap. Supports failure injection for testing:
use MemoryCache;
let cache = new;
cache.fail_one_get; // next get() fails with a cache read error
cache.fail_one_set; // next set() fails with a cache write error
cache.insert_raw; // bypasses wire format
RedisCache (behind the redis feature, enabled by default)
use RedisCache;
use ConnectionManager;
let conn = new.await?;
let cache = new;
Custom backend
Implement CacheBackend for any store:
use ;
use Duration;
Expiry strategies
The API uses Rust's type system to guide configuration:
Flat TTL
let cf = new
.ttl;
Once the TTL expires, the entry is treated as expired and the next caller recomputes.
Stale-while-revalidate
let cf = new
.stale_while_revalidate;
Callers within the fresh window get a CacheHit. During the stale window, callers get the stale value immediately while a single background refresh runs. If multiple callers arrive during the stale window, they all share the same in-flight refresh.
Probabilistic early expiration (XFetch)
let cf = new
.ttl
.probabilistic_expiry;
XFetch may trigger a background refresh before the TTL expires, based on the smoothed compute duration (delta EMA) and the beta parameter. Higher beta makes early refreshes more likely. Callers see CacheHit while the refresh runs in the background.
XFetch also works on top of SWR:
let cf = new
.stale_while_revalidate
.probabilistic_expiry;
Per-call overrides
The run() method returns a RunBuilder that supports per-call overrides:
cf.run
.ttl // flat TTL only
.await?;
cf.run
.fresh_for // SWR only
.stale_for // SWR only
.await?;
cf.run
.beta // XFetch only
.await?;
Cache invalidation
// Invalidate a single key.
cf.invalidate.await?;
// Invalidate all keys matching a prefix.
let count = cf.invalidate_prefix.await?;
println!;
Metrics
Implement MetricsHooks to observe cache behavior. All methods have no-op defaults — override only what you need. See docs.rs for the full trait definition.
use ;
;
let cf = with_metrics.ttl;
License
Licensed under either of MIT or Apache 2.0 at your option.