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.
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.
User
↓
Application
↓
Database
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?
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
10:00 → product saved in cache
10:10 → cache expires
10:11 → next user requests it
10:11 → application queries database
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.
SECTION 07
Comparing the strategies
Side by side, it becomes easier to see what each strategy optimizes.
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.
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?”.