The four strategies
Every caching pattern answers two questions: who populates the cache, and when is the source of truth updated relative to it.
Cache-aside
The application checks the cache, and on a miss reads the database and populates the cache itself. The cache has no knowledge of the database.
def get_user(user_id):
cached = cache.get(f"user:{user_id}")
if cached is not None:
return cached
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
cache.set(f"user:{user_id}", user, ttl=300)
return user
This is the default for a reason: if the cache is down, every request simply becomes a database read. Degraded, not broken.
Read-through
The cache sits inline. The application only talks to the cache, which fetches from the database on a miss. Logic moves out of the application and into the cache layer.
Cleaner call sites, but the cache is now on the critical path — if it is unavailable, reads fail entirely rather than falling back.
Write-through
Writes go to the cache, which synchronously writes to the database before acknowledging. The cache is never stale.
The cost is write latency: every write pays for both hops. Worth it when reads vastly outnumber writes and stale reads are unacceptable.
Write-behind
Writes land in the cache and are flushed to the database asynchronously.
Fastest writes available, and the only strategy that can lose acknowledged data. A crash between acknowledgement and flush loses whatever was buffered.