The short answer
Quick answer: A cache stores copies of data somewhere faster than the original source. The strategies differ in who keeps the cache and the database in step. With cache-aside, the application checks the cache, and on a miss reads the database and fills the cache itself. With read-through, the cache library does that loading for you. With write-through, every write goes to the cache and the database together, so the cache is always fresh. With write-back (also called write-behind), writes go to the cache first and reach the database later, which is fastest but can lose data. Most systems use cache-aside with expiry times.
Why cache
- Speed. Memory is far faster than disk or a network call.
- Load. Each cache hit is a query your database did not have to run.
- Cost. Expensive computations and paid API calls are done once.
Caching works because access is uneven: a small share of the data receives most of the requests. Caches exist at every layer, from the browser to CDNs to the database's own memory. This article is about the application-level cache that sits between your code and your database, typically Redis or Memcached. See why Redis is fast.
Reading strategies
Cache-aside (lazy loading)
The application manages the cache directly.
def get_user(user_id):
user = cache.get(f"user:{user_id}")
if user is None: # miss
user = db.query_user(user_id)
cache.set(f"user:{user_id}", user, ttl=300)
return user
- Only requested data is cached.
- Resilient: if the cache is down, the application can still read the database.
- The first request is slow (a miss costs three steps).
- Data can be stale until it expires or is invalidated.
On writes, the usual approach is to update the database and then delete the cache key, so the next read reloads it.
Read-through
The same flow, but the cache layer itself loads from the database on a miss. Application code only ever talks to the cache. It is tidier, at the cost of needing a cache library or service that supports loaders.
Refresh-ahead
The cache refreshes popular entries in the background shortly before they expire, so users rarely see a miss. It helps for hot keys but wastes work if predictions are wrong.
Writing strategies
Write-through
Every write updates the cache and the database as one operation, before returning.
- The cache is never stale.
- Writes are slower, since each does two operations.
- Data that is never read is cached anyway. Add an expiry to clear it.
Write-through is usually paired with read-through or cache-aside for reads.
Write-back (write-behind)
The write goes to the cache and returns immediately. A background process writes to the database later, often in batches.
- Very fast writes, and many updates to the same key collapse into one database write.
- Risk of data loss: if the cache fails before flushing, those writes are gone.
- The database lags, so anything reading it directly sees old data.
It suits high-volume, loss-tolerant data such as view counters, analytics and activity timestamps. It does not suit payments.
Write-around
Write only to the database and let reads populate the cache later. It avoids caching data nobody reads, but recently written data always causes a miss.
| Strategy | Read speed | Write speed | Freshness | Risk |
|---|---|---|---|---|
| Cache-aside | Fast after the first miss | Normal | Can be stale until TTL or invalidation | Low |
| Read-through | Fast after the first miss | Normal | Same as cache-aside | Low |
| Write-through | Fast | Slower | Always fresh | Low |
| Write-back | Fast | Fastest | Database lags | Data loss if the cache fails |
| Write-around | Miss after each write | Normal | Fresh on reload | Low |
The AWS guide to caching patterns covers the same patterns with further detail.
Invalidation: the hard part
There is a well-known saying that the two hard things in computer science are cache invalidation and naming things. See why naming things is hard for the other half.
The difficulty is that the cache and the database are two separate systems with no shared transaction.
Time to live (TTL)
Give every entry an expiry. Stale data is tolerated for at most that long. It is simple and self-healing, and it should be your baseline even if you also invalidate explicitly.
Delete on write
After updating the database, delete the key. Deleting is safer than writing the new value into the cache, because two concurrent updates can write to the cache in the opposite order from the database and leave the wrong value there permanently.
Even delete-on-write has a race:
- Request A misses the cache and reads the old value from the database.
- Request B updates the database and deletes the cache key.
- Request A now writes the old value into the cache.
Mitigations include short TTLs, deleting again after a brief delay, or versioning entries. With read replicas, replication lag can produce the same effect.
Event-driven invalidation
Publish change events from the database (change data capture) and have a consumer invalidate or update cache entries. It keeps application code simple and catches changes made by any writer.
When the cache is full: eviction
A cache has limited memory, so it must discard something.
| Policy | Evicts | Good for |
|---|---|---|
| LRU (least recently used) | The entry unused for longest | General purpose default |
| LFU (least frequently used) | The entry used least often | Stable popularity patterns |
| FIFO | The oldest entry | Simple cases |
| TTL-based | Entries closest to expiry | Time-sensitive data |
| Random | Any entry | Very low overhead |
Failure modes
Cache stampede (thundering herd)
A popular key expires. Hundreds of requests miss at once and all run the same expensive query, which can overwhelm the database.
Fixes:
- Locking: only one request recomputes; the others wait or serve the old value.
- Stale-while-revalidate: serve the expired value while refreshing in the background.
- Early refresh: refresh probabilistically before expiry.
- TTL jitter: add randomness to expiry times so keys do not expire together.
Cache penetration
Requests for keys that do not exist miss the cache every time and always hit the database. Attackers exploit this. Cache the "not found" result briefly, or use a Bloom filter to reject impossible keys.
Cache avalanche
Many keys expire at once, or the cache server restarts empty. Stagger TTLs, warm the cache before taking traffic, and make sure the database can survive a cold cache.
Hot keys
One extremely popular key overloads a single cache node. Replicate it or add a small in-process cache in front.
Practical guidelines
- Measure first. Cache what is slow and frequently read.
- Start with cache-aside plus a TTL. It is simple and fails safely.
- Choose TTLs by tolerance for staleness, not by habit.
- Never treat the cache as the source of truth unless you have designed for its loss.
- Use clear, versioned key names, such as
user:v2:42. - Track the hit ratio. A low one means the cache is not earning its keep.
- Plan for the cache being unavailable.
Frequently asked questions
What is the difference between cache-aside and read-through?
In cache-aside, application code loads from the database on a miss and populates the cache. In read-through, the cache layer does it automatically.
What is the difference between write-through and write-back?
Write-through writes to the cache and the database before returning. Write-back writes to the cache and updates the database later, asynchronously.
How long should a cache TTL be?
As long as stale data is acceptable for that item. Seconds for fast-changing data, minutes or hours for slow-changing data.
Should I update or delete the cache entry after a write?
Deleting is usually safer. The next read reloads the correct value, and you avoid concurrent writers leaving an out-of-date value in the cache.
Conclusion
Caching trades freshness for speed, and each strategy places that trade differently. Cache-aside with a sensible TTL handles most cases. Write-through keeps data fresh at the cost of slower writes, and write-back buys speed with risk. Whichever you choose, the real work is invalidation and guarding against stampedes.
Related articles
- How Redis Is So Fast
- How CDNs Make Websites Load Faster Worldwide
- How Database Replication Works
- Why Is Naming Things So Hard in Programming?
- How to Design a URL Shortener Like Bitly
