Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,105visitors

Built for engineers who want to go deep.


← System Design Fundamentals

Scalability Fundamentals

  • Horizontal vs Vertical Scaling
  • Load Balancing
  • Caching Strategies

Databases at Scale

  • CAP Theorem
  • Database Sharding
  • Replication & Consistency
  • Consistent Hashing
Chaturmind
← System Design Fundamentals

Scalability Fundamentals

  • Horizontal vs Vertical Scaling
  • Load Balancing
  • Caching Strategies

Databases at Scale

  • CAP Theorem
  • Database Sharding
  • Replication & Consistency
  • Consistent Hashing
HomeLearnSystem DesignSystem Design FundamentalsScalability Fundamentals
✓ FreeIntermediate· 6 min read

Caching Strategies

Cache-aside, write-through, write-behind, TTL, cache eviction — Redis in production.

Published September 21, 2026


Caching Strategies

A cache keeps a copy of data somewhere faster or closer than its source, so that repeated requests skip the expensive work. Reading from local memory takes nanoseconds, a Redis round trip about a millisecond, and a database query with disk access can take tens of milliseconds. Caching is usually the single largest lever for read latency and database load.

The famous difficulty is the other side: a cache is a copy, and copies go stale. Every caching design is a trade-off between speed and freshness.

Advertisement

Where caches live

 browser cache ─▶ CDN edge ─▶ load balancer / reverse proxy ─▶ app (in-process cache) ─▶ distributed cache (Redis) ─▶ database (its own buffer cache)
  • Client and CDN caches (HTTP Cache-Control, ETag): best for static assets and public, rarely changing content.
  • In-process caches (Caffeine, Guava): fastest, but each instance has its own copy, so they can diverge and memory is duplicated.
  • Distributed caches (Redis, Memcached): shared by all instances, and survive app restarts.
  • Database caches: the buffer pool keeps hot pages in memory, and materialized views precompute results.

Reading patterns

Cache-aside (lazy loading): the most common

The application checks the cache first. On a miss, it reads the database and populates the cache:

public Product getProduct(String id) {
    String key = "product:" + id;
    Product cached = redis.get(key);
    if (cached != null) return cached;                     // hit

    Product product = productRepository.findById(id).orElseThrow();
    redis.set(key, product, Duration.ofMinutes(10));      // populate with a TTL
    return product;
}
  • ✅ Only data that's actually requested gets cached. A cache failure degrades to database reads instead of breaking the app.
  • ❌ The first request for each key is slow, and data can be stale for up to the TTL.

Read-through

The cache itself loads missing entries from the source, through a configured loader. It's the same idea as cache-aside, but the logic lives in the cache layer (for example Caffeine's LoadingCache, or Spring's @Cacheable).

Writing patterns

PatternHow it worksProsCons
Write-around + invalidateWrite to the DB, then delete the cache key. The next read reloads itSimple, and the usual partner of cache-asideThe next read is a miss
Write-throughWrite to the cache and the DB synchronously, togetherThe cache is always warm and freshSlower writes, and caches data that may never be read
Write-behind (write-back)Write to the cache. It's flushed to the DB asynchronouslyVery fast writes, and batchingData loss if the cache fails before flushing. Complex

Delete, don't update, on write. If two concurrent writers each update the cache after writing the database, they can finish in the wrong order and leave a stale value cached indefinitely. Deleting the key forces the next read to fetch the current value. A narrow race still exists (a slow reader re-populating an old value right after the delete). TTLs bound its impact, and some teams do a second, delayed delete.

Expiry and eviction

  • TTL (time to live): every entry expires after a set time. That bounds staleness even if an invalidation is missed. Choose TTLs per data type: seconds for prices, hours for profile pictures.
  • Eviction when memory is full:
    • LRU: evict the least recently used entry. A good default.
    • LFU: evict the least frequently used entry. Better when popularity is stable.
    • Modern caches such as Caffeine use W-TinyLFU, which combines recency and frequency with high hit rates.
  • Add jitter to TTLs (for example 10 minutes ± 60 seconds), so entries cached at the same moment don't all expire together.

Failure modes to design for

Cache stampede (thundering herd)

A hot key expires, and thousands of simultaneous requests all miss and hit the database at once. Mitigations:

  • Request coalescing / single-flight: only one request recomputes the value, and the others wait for its result, or get a lock and briefly serve the old value.
  • Early (probabilistic) refresh: refresh popular keys shortly before they expire.
  • Stale-while-revalidate: serve the stale value while refreshing it in the background.

Cache penetration

Requests for keys that don't exist (for example IDs chosen by an attacker) always miss and always hit the database. Mitigations: cache the "not found" result briefly, validate inputs, or use a Bloom filter of existing keys.

Hot keys

One extremely popular key overloads the single cache node that owns it. Mitigations: an in-process L1 cache in front of Redis for that key, or replicating it under several keys (key#1…key#N) and reading one at random.

Cache as a crutch

If the database can't survive the load without the cache, then a cache restart, or a cold cache after a deployment, causes an outage. Warm caches gradually, limit concurrent database fallbacks, and capacity-plan for realistic miss rates.

What to cache, and what not to

Good candidates:

  • Read-heavy, rarely changing data: catalogues, configuration, user profiles.
  • Expensive computations: aggregations, rendered pages, recommendation results.
  • Session data and rate-limit counters, where Redis is the primary store.

Poor candidates:

  • Data that must always be exactly current: account balances at the moment of a transfer, inventory at checkout. Read those from the source of truth.
  • Data that's written far more often than read.
  • Highly personalized data with a low hit rate, where each entry is used once.

Measure: track the hit ratio, latency, evictions and memory. A cache with a 20% hit ratio adds complexity for little gain.

Follow-up questions this topic invites — and their answers

Q: Why delete the cache entry on update instead of writing the new value? A: Concurrent writers can apply cache updates in a different order than their database writes, leaving an old value cached. Deleting means the next read fetches the current value. TTLs cover the remaining small race windows.

Q: How do you prevent a cache stampede on a popular key? A: Make sure only one request recomputes it (a lock, or single-flight), serve the stale value while it refreshes, refresh popular keys before they expire, and add jitter to TTLs so many keys don't expire together.

Q: In-process cache or Redis? A: In-process for tiny, extremely hot, read-mostly data where nanosecond access matters and brief inconsistency between instances is acceptable. Redis for data shared across instances, larger datasets, or anything that needs central invalidation. Many systems use both: a small L1 in-process cache in front of a Redis L2.

Q: How do you keep a cache consistent with the database? A: Perfect consistency isn't practical. Use invalidate-on-write, bounded TTLs, and for critical flows either bypass the cache or invalidate via change data capture events from the database, so no code path is forgotten.

Q: What's a good cache hit ratio? A: It depends on the workload. For hot read paths, 90%+ is common and valuable. What matters is whether the hit ratio meaningfully reduces latency and backend load compared with the cost and complexity of the cache.

Advertisement

Previous

Load Balancing

Next

CAP Theorem

AI Tutor

Lesson: Caching Strategies

Quick actions

AI responses can be inaccurate. Verify critical information.