The trend has moved away from "cache everything in Redis" toward "cache at the right layer." Modern applications often have 4–6 different caches, each solving a different problem.
For personalized apps, I'd think about it like this:
| Layer | Best for | TTL |
|---|
| Browser | Static assets, user-specific API responses | Seconds to days |
| CDN/Edge | Public content, images, HTML, API responses | Seconds to hours |
| Application memory | Hot objects, config, feature flags | Milliseconds to minutes |
| Redis | Shared cache, sessions, expensive computations | Seconds to minutes |
| Database cache | Query plans, buffer pools | Automatic |
The mistake I see most often is using Redis as the first answer instead of asking what should actually be cached.
Public vs. personalized content
Suppose you're building an e-commerce homepage.
Instead of treating the whole page as personalized:
Home Page
├── Hero banner (same for everyone)
├── Featured products (same)
├── Trending products (same)
├── Your recommendations (personalized)
├── Cart count (personalized)
└── Recently viewed (personalized)
Cache each piece independently.
The public sections can often be cached at the edge for minutes or hours.
The personalized pieces are either:
- fetched separately after page load
- server-rendered with shorter-lived caches
- computed from cached intermediate data
This usually gives much higher cache hit rates than caching whole pages.
Redis is still valuable—but differently
Redis shines when you need shared state across servers.
Examples:
- session storage
- rate limiting
- distributed locks
- expensive query results
- leaderboard data
- queue metadata
- feature flags
Less ideal:
- every database query
- every API response
- objects that change constantly
- massive blobs that are rarely reused
If an item is requested only once before expiring, caching it may add overhead without improving performance.
Edge caching has become much more powerful
Modern CDNs can cache more than images and JavaScript.
They can cache:
- API responses
- rendered HTML
- GraphQL responses
- search results
- image transformations
Often with cache keys like:
/products?page=2
instead of
/products?page=2&session=abc123
The key is varying the cache only on inputs that actually affect the response.
Personalized content doesn't mean "can't cache"
A common misconception is:
User-specific = impossible to cache.
Instead, cache the expensive parts.
Example recommendation pipeline:
User
↓
Recommendation IDs
↓
Product details
↓
Rendered cards
You might cache:
- recommendation model outputs for 15 minutes
- product metadata for an hour
- inventory for 30 seconds
- prices for 5 minutes
Only the final assembly is unique per request.
Cache invalidation is often more important than TTL
Rather than relying on a long expiration:
Instead of:
TTL = 1 hour
Use:
Product updated
↓
Invalidate product cache
↓
Next request rebuilds it
Event-driven invalidation typically gives fresher data while maintaining high cache hit rates.
Avoid cache stampedes
If a hot key expires and thousands of requests arrive simultaneously:
10,000 requests
↓
Cache miss
↓
10,000 DB queries
Better approaches include:
- request coalescing (only one request regenerates the value)
- stale-while-revalidate (serve slightly stale data while refreshing in the background)
- randomized TTLs (jitter) to avoid many keys expiring at once
- background warming for predictable hot keys
Think in terms of data volatility
A useful heuristic is:
| Data | Strategy |
|---|
| Static assets | Cache aggressively at the edge |
| Product catalog | Edge + Redis |
| Search results | Short edge cache |
| User profile | Short Redis or in-process cache |
| Shopping cart | Redis or database, little/no edge caching |
| Real-time notifications | Usually don't cache; push updates |
The faster data changes, the more selective caching should be.
One pattern I like
For many SaaS and consumer applications:
Browser cache
↓
CDN / Edge cache
↓
Application server
↓
In-process memory cache
↓
Redis
↓
Database
Each layer should answer progressively fewer requests. A cache miss at one layer falls back to the next, reducing load on the database.
For personalized content, I'd avoid choosing between "Redis everywhere" and "edge caching everywhere." A layered strategy usually works best: cache public and semi-public content at the edge, keep hot shared data in Redis, use in-process caches for extremely frequent lookups, and reserve direct database reads for data that's highly dynamic or unique to a user. This approach tends to balance latency, scalability, and operational complexity better than relying on any single caching technology.