Cache
In-memory caching with Redis and Memcached - patterns, deployment, and how to avoid the classic cache pitfalls
Cache
A cache is a fast, usually in-memory store that holds copies of expensive-to-compute or expensive-to-fetch data so the next request doesn't pay the full cost. Cache the result; serve the next 1,000 reads from RAM. Done right, it's the cheapest way to scale a read-heavy system.
Why Cache
| Without cache | With cache |
|---|---|
| Every request hits the database | Most reads served from memory in < 1 ms |
| Database CPU scales with traffic | Database CPU scales with write traffic |
| Recomputing the same expensive query | Compute once, reuse for the TTL |
| Tight coupling between read latency and DB | Decouple: cache is fast even when DB is slow |
| Hard to handle traffic spikes | Cache absorbs bursts |
The Players
| System | Notes |
|---|---|
| Redis | The default. In-memory data structures (strings, hashes, sorted sets, streams), persistence, pub/sub, clustering. |
| Memcached | Older, simpler, multi-threaded, only key-value strings. Very fast at what it does. |
| DragonflyDB | Drop-in Redis replacement; multi-threaded, claims higher throughput. |
| KeyDB | Multi-threaded Redis fork. |
| Valkey | Linux Foundation fork of Redis (after Redis license change in 2024). |
| Hazelcast / Apache Ignite | Distributed in-memory data grids; richer than a cache. |
| ElastiCache / MemoryStore / Azure Cache | Cloud-managed Redis/Memcached. |
For most use cases: start with Redis (or Valkey, the OSS fork). Memcached is excellent if you only need key-value and want raw throughput.
Redis vs Memcached
| Aspect | Redis | Memcached |
|---|---|---|
| Data structures | Strings, hashes, lists, sets, sorted sets, streams, HyperLogLog, geo, bitmaps | Strings only |
| Persistence | Optional RDB snapshots + AOF | None |
| Replication | Built in (primary-replica, cluster mode) | Sharding only, no replication |
| Threading | Single-threaded core (multi-threaded I/O since 6.0) | Multi-threaded |
| Eviction policies | Multiple (LRU, LFU, TTL, random, no-eviction) | LRU |
| Pub/Sub | Yes | No |
| Transactions / scripting | MULTI/EXEC, Lua scripting | No |
| Use when... | Anything non-trivial; sessions, queues, leaderboards | Pure read-through cache with simple values |
The trend is clear: Redis is the broader, more flexible default. Memcached is great when its constraints fit your workload.
Learning Path
1. Getting Started
Run Redis with Docker, learn the core commands, integrate from a Node app
2. Patterns
Cache-aside, read-through, write-through, write-behind, TTL, invalidation, stampede prevention
3. Best Practices
HA topology, sizing, eviction, persistence, observability, common pitfalls
Where Caches Live
Browser ──► CDN cache ──► API gateway ──► App ──► Local cache ──► Distributed cache (Redis) ──► Database
(geo) (response) (in-memory LRU) (shared across pods) (source of truth)A typical request can hit cache at five layers before reaching the database. Each layer has different TTLs, invalidation rules, and sizing concerns. This page is about the distributed in-memory layer (Redis/Memcached) — for HTTP caching see CDN.
What to Cache (and Not To)
| Good cache candidates | Bad candidates |
|---|---|
| Computed query results | Per-request mutations |
| Per-user profile / session | Strongly consistent data (account balances) |
| Hot read paths (top product list) | Anything you can't reconstruct from source |
| Aggregations (counts, sums) | Tiny rows trivially fetched from DB |
| Third-party API responses (rate-limited) | Anything with PII you don't have a retention policy for |
A cache is not the source of truth. Anything in cache must be reconstructible from the database. If you'd cry when the cache server dies, you're using it as a primary store — and probably about to have a bad week.
Caches add a consistency problem. Stale data, dual writes, race conditions on invalidation — all the bugs that don't exist without a cache appear with one. Adopt a cache deliberately, not reflexively.