In this guide
Your app works fine with 10 users. At 1,000 users the database starts sweating; at 10,000 it falls over. The usual fix is not a bigger database — it is asking the database less often. That is what caching does, and Redis is the tool the industry reaches for.
Redis is an in-memory data store: blazing fast because everything lives in RAM, with data structures (strings, hashes, lists, sets, sorted sets) that map naturally onto caching problems. This guide covers the strategies — the decisions about what to cache, when to invalidate, and which pattern fits which problem.
Why Redis for caching
- Speed — memory access is orders of magnitude faster than disk-backed database queries; sub-millisecond responses are normal.
- Data structures — not just key/value; sorted sets give you leaderboards, lists give you queues, hashes give you objects.
- Expiry built in — every key can have a TTL (time to live); Redis deletes it automatically.
- Atomic operations — INCR, SETNX and friends run atomically, which is what makes distributed locks and rate limiters correct.
Note: Redis is not a replacement for your primary database in student projects. It is a cache — a fast copy of data that also lives somewhere durable. Design so that losing the cache is an inconvenience (slower responses), never data loss.
The core patterns
Cache-aside (lazy loading)
The most common pattern. Your application code manages the cache explicitly:
1. App needs user profile 42
2. Check Redis: GET user:42
3. Cache HIT -> return it (fast path)
4. Cache MISS -> query database, then SET user:42 <data> EX 3600
5. Return data to client
- Pros: simple; only caches data that is actually requested; cache failures degrade gracefully to database reads.
- Cons: first request after expiry is slow (cache miss penalty); possible brief inconsistency between cache and database.
- Use for: read-heavy data with tolerance for slight staleness — user profiles, product listings, configuration.
Read-through / write-through
The cache sits in front of the database and the application only talks to the cache:
-
Read-through: cache miss → the cache layer itself loads from the database, stores, and returns.
-
Write-through: every write goes to cache and database together — the cache is never stale, but every write pays both costs.
-
Pros: application code stays simple; write-through guarantees consistency.
-
Cons: write-through slows every write; cold cache still hurts.
-
Use for: write-through fits data where stale reads are unacceptable and write volume is low.
Write-behind (write-back)
Writes go to the cache immediately and are flushed to the database asynchronously, later.
- Pros: extremely fast writes — great for counters, analytics events, activity feeds.
- Cons: data loss risk if Redis fails before the flush. Never use for data you cannot afford to lose (orders, payments).
- Use for: like counters, view counts, session activity — data that is nice to keep but safe to lose.
Refresh-ahead
The cache proactively refreshes keys that are about to expire and are frequently accessed, so users never experience the miss penalty.
- Use for: hot keys with predictable access patterns — homepage data, trending lists.
Invalidation: the hard part
Phil Karlton's famous line — "there are only two hard things in computer science: cache invalidation and naming things" — exists because of this section. Strategies:
| Strategy | How it works | Use for |
|---|---|---|
| TTL expiry | Keys auto-expire after N seconds | Data where brief staleness is fine; the simplest correct approach |
| Explicit invalidation | On write, delete/update the affected keys | Data that must be fresh after writes |
| Write-through | Cache updated synchronously with the DB | Low-write, consistency-sensitive data |
| Versioned keys | Key includes a version (user:42:v3); bump version instead of deleting |
Avoiding delete-then-repopulate stampedes |
The golden rule for student projects: start with TTL expiry. SET user:42 <data> EX 300 (5 minutes) is correct, simple, and self-healing — the worst case is 5-minute-old data. Add explicit invalidation only for the specific keys where staleness actually causes user-visible bugs.
The thundering herd
When a popular key expires, 1,000 simultaneous requests all miss the cache and all hit the database at once. Mitigations:
- Staggered TTLs — add small random jitter to expiries so keys do not expire simultaneously.
- Request coalescing — the first miss triggers the database load; other requests wait for that result instead of each querying.
- Refresh-ahead for your hottest keys.
Choosing TTLs
| Data | Typical TTL | Reasoning |
|---|---|---|
| User session/profile | 5–15 min | Changes rarely, but stale profiles confuse users |
| Product catalogue | 30–60 min | Changes on deploys or admin edits; invalidate on write |
| Leaderboard / trending | 1–5 min | Users expect freshness; recompute is cheap |
| ML inference results | Hours–days | Expensive to compute, inputs change slowly |
| Static config | Hours | Rarely changes |
Shorter TTL = fresher data + more database load. Longer TTL = less load + staler data. There is no formula — pick per data type, measure, adjust.
Beyond caching: what else Redis does well
Students often discover Redis does half their infrastructure:
- Sessions — shared session store across multiple app servers (with TTL = session timeout).
- Rate limiting — atomic counters with expiry, as shown in the rate limiting guide.
- Pub/Sub — lightweight messaging between services (e.g. fan-out in a WebSocket chat app).
- Leaderboards — sorted sets (
ZADD,ZRANGE) are a one-liner leaderboard. - Distributed locks —
SET key value NX PX 30000for leader election and cron-job mutual exclusion.
Eviction policies and persistence
Redis memory is finite. When it fills up, the maxmemory-policy decides what happens:
| Policy | Behavior | Good for |
|---|---|---|
allkeys-lru |
Evict least-recently-used keys (any key) | Pure caches — the standard choice |
volatile-lru / volatile-ttl |
Evict only keys with a TTL | Mixed use where some keys must never be evicted |
noeviction |
Return errors on writes when full | When losing data is unacceptable (not typical for caches) |
For a cache, allkeys-lru with a memory cap is the set-and-forget answer: hot data stays, cold data leaves automatically.
Persistence (RDB snapshots, AOF logs) matters less for caches than people assume. If Redis restarts and the cache is empty, your app just runs slower until it refills — that is the design working as intended. Enable persistence when Redis also holds sessions or rate-limit state you would rather not lose; skip it for a pure throwaway cache and enjoy the simplicity.
Common mistakes
- Caching without TTL. Keys that never expire become stale forever and eventually fill memory. Every cache key gets a TTL — no exceptions.
- Caching everything. Cache the expensive, frequently-read data. Caching a query that runs twice a day adds complexity for zero benefit.
- Treating cache as the database. If Redis restarts and your app breaks, your architecture is wrong. The database is the source of truth; the cache is expendable.
- Serializing badly. Storing huge JSON blobs per key wastes memory and network. Store what you need; consider hashes for objects you update field-by-field.
- No monitoring of hit rate.
INFO statsshows your hit/miss ratio. A cache with a 10% hit rate is decoration, not infrastructure — revisit what you cache and your TTLs. - Forgetting connection pooling. Opening a new Redis connection per request in Node/Python will hurt more than the cache helps. Use a client with pooling.
Quick checklist
- Redis running (local Docker or managed) with persistence configured sensibly
- Cache-aside for read-heavy data, TTL on every key
- Explicit invalidation only where staleness causes real bugs
- Jitter on TTLs for hot keys; refresh-ahead for the hottest
- App works correctly with an empty/flushed cache (cache is optional, not structural)
- Hit rate monitored; TTLs tuned from real numbers
Where to go from here
- Redis-backed counters power Rate Limiting APIs: Strategies Explained.
- Redis Pub/Sub scales the WebSocket chat app beyond one server.
- For the data underneath the cache: SQL vs NoSQL: which database for your project.
- Deploying it all: how to deploy a MERN app on a VPS.
- More backend topics in the Web Development branch hub.