The Insight
stampedes happen AT expiry because everyone waits for death,
then races. XFetch (probabilistic early recomputation) asks:
why expire at all? refresh EARLY, by lottery:
on each read of value with age a and ttl T:
fetch_probability = exp( -k × Δ × (a/T)² )
where Δ = expected refetch time as fraction of T
behavior:
age ≈ 0 → probability ~0 never refresh early
age → T → probability climbs smoothly toward 1
one request "wins" the lottery slightly before expiry
→ value refreshed BEFORE the herd moment exists
Why the Math Works
the quadratic term shapes everything:
early in life: (a/T)² tiny → almost nobody refreshes
near expiry: curve steepens → SOMEONE refreshes soon
first refresher resets age → race vanishes
across many reads per key, expected number of "early birds"
converges to ~1-2 — the herd becomes a trickle WITHOUT:
- distributed locks
- waiting/queueing
- stale-serving tradeoff (value stays fresh!)
Implementation Sketch
store alongside value: fetched_at timestamp
def get(key):
entry = cache.get(key)
if entry is None: return refill(key) # true miss
age = now() - entry.fetched_at
if random() < exp(-K * DELTA * (age/TTL)**2):
async_refill(key) # non-blocking!
return entry.value # still fresh
return entry.value
K tunes eagerness (typical 1-10);
DELTA ≈ refetch_time / ttl (often ~0.01)
async_refill itself wrapped in singleflight for belt+suspenders.
vs The Alternatives
| Defense | Coordination | Serves stale? | Blocks callers? |
|---|---|---|---|
| Fill mutex/locks | Distributed lock | no | waiters wait |
| Stale-while-revalidate | none | yes (briefly) | no |
| TTL jitter | none | no | only at true expiry |
| XFetch | NONE | no | no |
XFetch's unique selling points: zero infrastructure
(pure client logic) and it PREVENTS rather than absorbs.
costs: slight extra background load (~1-2x refresh rate),
randomness in metrics (refresh timing varies).
Where It Shines
✓ very-hot keys where even ONE db hit at expiry is expensive
(homepage payload, config, feature flags)
✓ systems avoiding distributed-lock complexity/correctness risk
✓ combined with jitter + singleflight as the no-locks trifecta
less critical for long-tail keys (misses are cheap there —
the herd needs a crowd, rare keys don't draw one).
Interview Framing
Most candidates know fill-locks; mentioning probabilistic early refresh BY NAME and its property set — “no locks, no staleness, prevents instead of absorbs” — is a genuine differentiator in caching discussions. Sketch the formula shape verbally (probability rises quadratically toward expiry), state the async-refresh detail, and place it in the toolkit stack.
Premium Content
Unlock Probabilistic Early Refresh and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans