Around Go: MongoDB and Redis · 3 MIN READ
Redis cache-aside: TTLs do not solve concurrent fills
Design expiry, invalidation, negative caching and stampede control deliberately.
A cache duplicates another source of truth, so freshness and ownership must be explicit. Cache-aside reads can miss together, all query the origin and then repopulate the same key. TTL bounds one kind of staleness; it does not prevent a stampede or make a read-modify-write sequence correct.
Around Go: data stores · A MAP OF THE IDEA
- Lookup
- Bounded origin fill
- Publish with TTL
- Invalidate or expire
Useful when
- Design expiry, invalidation, negative caching and stampede control deliberately.
Watch out
- In-process coalescing does not coordinate other replicas or make invalidation races disappear.
Implementation & operation cost: Caching adds duplicate state, invalidation and miss coordination; measure saved origin work against stale-data risk.
Set value and expiration together
Use one SET operation with expiry when creating an expiring value. A separate SET followed by EXPIRE leaves a failure window where the key can survive without the intended lifetime. An ordinary later SET can also change existing TTL behavior, so decide the overwrite policy.
redis-cli SET demo:product:42 '{"name":"tea"}' EX 60
redis-cli GET demo:product:42
redis-cli TTL demo:product:42Run against an isolated development Redis using the demo namespace. TTL reports remaining expiry state, not freshness relative to a database update that happened after the value was cached.
Bound concurrent misses
Coalesce identical in-process fills and independently bound the total number of origin calls. A per-key singleflight group only coordinates callers in this process; many service replicas can still miss together. Optional TTL jitter spreads expirations but does not guarantee a global rate limit.
_, err := rdb.Set(ctx, key, encoded, 60*time.Second).Result()
if err != nil { return fmt.Errorf("cache fill: %w", err) }
// A bounded loader owns misses; do not start one goroutine per waiting caller.The fragment shows the atomic value-plus-expiry client operation after an origin load. It does not implement coalescing or cross-process coordination; those policies belong to the loader boundary and must be tested separately.
Handle stale and missing data as distinct cases
Negative caching can reduce repeated absent-item lookups, but use a short deliberate lifetime and a representation distinct from a real value. A database update followed by cache deletion can race with an older in-flight fill. Use versioned keys or another coherent invalidation design when that stale-write window is unacceptable.
TAKE THIS WITH YOU
Set TTL atomically, bound origin fills and define the acceptable stale window. TTL, coalescing and invalidation address different failure modes.
Check your understanding
What extra protection does a cache-aside loader need during a burst of misses?
Go to the source
Progress stays in this browser. No account needed.