Caching Strategies — Where and When to Write
The position of cache relative to your DB and the direction of writes determines consistency, complexity, and performance.
When to use
- Cache-aside: most common, general purpose — app controls all cache logic
- Write-through: cache and DB always in sync, at cost of write latency
- Write-behind: write-heavy workloads tolerating async DB persistence
Tradeoffs
- Write-behind risks data loss on cache crash before async write completes
- Write-through doubles write latency (cache + DB in the same request)
- Go
- Python
// Cache-aside: check cache, on miss fetch from DB, populate cache
func GetUser(ctx context.Context, id string) (*User, error) {
cached, err := redis.Get(ctx, "user:"+id).Result()
if err == nil {
var u User
json.Unmarshal([]byte(cached), &u)
return &u, nil
}
user, err := db.QueryUser(ctx, id)
if err != nil {
return nil, err
}
data, _ := json.Marshal(user)
redis.Set(ctx, "user:"+id, data, 5*time.Minute)
return user, nil
}
# Cache-aside: check cache, on miss fetch from DB, populate cache
import json
def get_user(user_id: str) -> dict:
key = f"user:{user_id}"
cached = redis_client.get(key)
if cached:
return json.loads(cached)
user = db.query_user(user_id)
redis_client.setex(key, 300, json.dumps(user))
return user
Gotcha: Cache-aside is lazy loading. Cache flush + high traffic = thundering herd on your DB. Use probabilistic early expiry or request coalescing.