Cache Invalidation — One of the Two Hard Problems
Keeping cache in sync with source of truth — choose your strategy based on how stale data harms you.
When to use
- TTL: staleness acceptable (product catalog, configs)
- Event-driven: near-real-time required (inventory counts, pricing)
- Versioned keys: static assets, CDN-cached content
Tradeoffs
- TTL = stale window risk; event-driven = coupling to write path; versioned keys = storage growth
- All strategies have failure modes — none is universally safe
- Go
- Python
// TTL: expire after fixed duration on write
redis.Set(ctx, "product:123", data, 60*time.Second)
// Event-driven: invalidate on write event
func OnProductUpdated(id string) {
redis.Del(ctx, "product:"+id)
}
// Versioned key: embed version, no invalidation needed
func VersionedKey(id, version string) string {
return fmt.Sprintf("product:%s:v%s", id, version)
}
# TTL: expire after fixed duration on write
redis_client.setex("product:123", 60, data)
# Event-driven: invalidate on write event
def on_product_updated(product_id: str):
redis_client.delete(f"product:{product_id}")
# Versioned key: embed version, no invalidation needed
def versioned_key(product_id: str, version: str) -> str:
return f"product:{product_id}:v{version}"
Gotcha: Versioned keys are the most robust — never overwrite, never invalidate. Old versions are just ignored. Best for assets, configs, feature flags.