SYSTEM DESIGN · CACHE · ~12 MIN READ

Cache Strategies

Caching is not just “put Redis in front of the database.” The way the application reads, writes, updates and invalidates cached data completely changes the system's behavior.

Hand-drawn diagram showing user, app, cache and database, with hit, miss and TTL highlighted under the cache.
CACHE · DATABASE · READ LAYERS

SECTION 01

What cache is

A cache is a fast storage layer used to temporarily keep data that would be more expensive or slower to fetch again.

WITHOUT CACHEFLOW
User
   ↓
Application
   ↓
Database
WITH CACHEFLOW
User
   ↓
Application
   ↓
Cache
   ├── data found → return quickly
   └── data missing → query database

Imagine 10,000 people reading the same product. Without cache, that can mean 10,000 database queries. With cache, the first request loads the value, stores it, and the following requests are served by the fast layer.

Cache protects the database by reducing repeated reads, latency and load. But it introduces a new question: how do we keep the cache synchronized with the original source of truth?

Sequence diagram comparing cache-aside and read-through, showing whether the application or cache loads from the database on a miss.
CACHE-ASIDE X READ-THROUGH · WHO LOADS THE DATA?

SECTION 02

1. Cache-aside

Cache-aside is one of the most common strategies. In it, the application is responsible for talking to both the cache and the database.

CACHE-ASIDEREAD
Application
   ↓
Cache
   ├── Cache hit → return
   └── Cache miss
           ↓
        Database
           ↓
      Save in cache
           ↓
        Return

The application looks for the data in cache. If it finds it, it returns. If not, it queries the database, stores the result in cache and returns the value.

EXAMPLEPYTHON
def get_user(user_id):
    user = cache.get(f"user:{user_id}")

    if user is None:
        user = database.get_user(user_id)

        if user is not None:
            cache.set(f"user:{user_id}", user, ttl=600)

    return user

The first request may be slower because it needs the database. The next ones are served by the cache. This is also called lazy loading, because only requested data is stored.

Updates in cache-aside

When a value changes, the usual flow is: update the database, delete the cached item, and let the next read reload the fresh value.

INVALIDATIONPYTHON
def update_user(user_id, values):
    database.update_user(user_id, values)
    cache.delete(f"user:{user_id}")
  • Advantages: simple implementation, memory is used only for accessed data, and database reads drop significantly.
  • Disadvantages: the application must manage cache and database, the first read is always a miss, and stale data can remain if invalidation fails.

SECTION 03

2. Read-through

Read-through looks similar to cache-aside, but the responsibility moves. The application only talks to the cache. When the value is missing, the cache itself loads it from the database.

READ-THROUGHREAD
Application
   ↓
Cache
   ├── found → return
   └── not found
           ↓
      Cache queries database
           ↓
      Cache stores the data
           ↓
          return

The application calls something like cache.get("user:123"). Internally, the cache layer knows how to load the missing data.

CENTRAL DIFFERENCE

In cache-aside, the application queries the database on a miss. In read-through, the cache queries the database.

  • Advantages: simpler application code, centralized loading logic and consistent cache behavior.
  • Disadvantages: requires a smarter cache layer, increases coupling with the cache solution, and not every tool supports it natively.

SECTION 04

3. Write-through

In write-through, every write goes through the cache. The cache stores the value and synchronously writes it to the database before confirming the operation.

WRITE-THROUGHSYNC WRITE
Application
   ↓
Cache
   ↓
Database
   ↓
Confirmation

When the user updates their name, the application sends the update to the cache. The cache writes to the database, keeps the new value and only then returns success.

Write-through flow: app writes to cache, cache writes synchronously to database, then returns OK.
WRITE-THROUGH · CACHE UPDATED, DATABASE CONFIRMED

Because the database must be updated before the response, writes can be slower. But once the operation completes, the cache already contains the fresh value.

  • Advantages: cache and database stay synchronized, stale reads are less likely, and reads right after a write are fast.
  • Disadvantages: writes still depend on database speed, each cache write generates a database write, and data that may never be read can occupy cache memory.
PRACTICAL RULE

Write-through is not meant to reduce database writes. Its main goal is consistency between cache and database.

SECTION 05

4. Write-behind

Write-behind, also called write-back, prioritizes write speed. The application writes to the cache and gets confirmation immediately. Persistence to the database happens later, asynchronously.

Write-behind flow: app writes to cache and returns fast, while queue and worker persist to the database later.
WRITE-BEHIND · FAST RESPONSE, PERSISTENCE LATER
WRITE-BEHINDASYNC WRITE
Application
   ↓
Cache
   ↓
Response to user

Meanwhile:

Cache or queue
   ↓
Worker
   ↓
Database

This strategy is useful when the system needs to respond quickly and can accept eventual consistency. It can also absorb write spikes: requests land in cache and a queue, while the database processes them at a sustainable pace.

Multiple changes can also be consolidated. A view counter may be updated many times in cache and persisted as one aggregated database update.

  • Advantages: fast response, reduced write spikes, batch processing, higher throughput and decoupling between application and database speed.
  • Disadvantages: risk of data loss, temporary divergence between cache and database, more operational complexity, retries, monitoring and backlog control.
DANGEROUS SCENARIO

Cache updated, database not yet updated, cache fails: the data may be lost. Robust implementations use durable queues, persistence, idempotency and reprocessing.

SECTION 06

5. Refresh-ahead

Refresh-ahead updates cached data before its TTL expires. The goal is to keep popular data hot.

WITHOUT REFRESH-AHEADTTL
10:00 → product saved in cache
10:10 → cache expires
10:11 → next user requests it
10:11 → application queries database
WITH REFRESH-AHEADTTL
10:00 → product saved in cache
10:08 → system sees it is still popular
10:09 → refreshes early
10:10 → item remains available

As long as the data keeps being accessed, it can stay warm in the cache. That reduces misses and avoids making a user wait right after expiration.

  • Advantages: fewer cache misses, popular data stays available and predictable reads get lower latency.
  • Disadvantages: it may refresh data that will not be used again, increase database reads and depend on good popularity prediction.
Refresh-ahead timeline: popular item is refreshed before expiration to avoid a cache miss.
REFRESH-AHEAD · REFRESH BEFORE THE MISS

SECTION 07

Comparing the strategies

Side by side, it becomes easier to see what each strategy optimizes.

StrategyWho queries the database?Write momentMain goal
Cache-asideApplicationDirectly in databaseSimplicity and fewer reads
Read-throughCacheDoes not define writesSimplify loading
Write-throughCacheSynchronousConsistency
Write-behindWorker or cacheAsynchronousSpeed and spike control
Refresh-aheadAutomatic processBefore expirationAvoid misses for popular data

SECTION 08

Which strategy should you choose?

There is no universally best strategy. Cache-aside is often a good starting point for read-heavy applications. Write-through fits when the application must guarantee cache and database are synchronized before confirming a write.

Write-behind makes sense when low latency and high write volume matter more, as long as the system can deal with eventual consistency. Refresh-ahead works best for popular and predictable data. Read-through is useful when infrastructure can hide loading logic from the application.

Strategies can also be combined: cache-aside for reads, invalidation after updates and refresh-ahead for popular items. Or write-through for critical data and write-behind for metrics and counters.

Visual decision guide for cache-aside, read-through, write-through, write-behind and refresh-ahead.
DECISION GUIDE · WHICH STRATEGY SHOULD YOU CHOOSE?
CHECKLIST

What is the read volume? What is the write volume? Can the system accept eventual consistency? What is the impact of losing an update? How long can the data remain stale?

Cache is not just a tool for temporary storage. It is part of the architecture and directly affects consistency, latency, availability and database pressure.

The best strategy is not the one that looks fastest in a diagram. It is the one that offers the right balance between performance, consistency and complexity for the system's context.

THE END · THANKS FOR READING

Good cache is an architecture decision.

When the system grows, the question stops being “do we have cache?” and becomes “what contract does this cache have with the database?”.

READ SYSTEM DESIGN BACK TO CONTENTS