Mechanism 01Memory and DataInteractive · 6 min

Why does the same request sometimes take 5 ms and sometimes 150 ms?

Because the application may never have to reach the database. Redis keeps a copy of the answer in memory, and the speed comes with new ways to be wrong.

All timings on this page are illustrative. They show the shape of the difference, not what your system will do.

The cache starts empty, so the first request takes the long way round and leaves a copy behind.

01ReadySend a request and watch the path it takes.

Ready. Send a request and watch the path it takes.

step 1/1
Send a request
Time · t = 0 s
Source of truthInvalidation
FailureIf Redis is down
Each request, to scale0–200 ms

No requests yet. Each one is drawn here to scale.

networkRedisPostgrestimeout
Counters
0Requests
0Hits
0Misses
0DB queries
0Stale reads
0Failed
Redis keyspacein memory

(empty keyspace)

PostgreSQL userson disk
idnameplan
42Ashafree
7Bikrampro
Sheet 01 · Cache-aside read pathOne app, one Redis, one PostgresTimings illustrative, not to scale of any system
Under the hood

The cache is a decision in your code

Live: the line running nowcache-aside.js

async function getProfile(id) {  const key = `user:${id}`;  try {    const cached = await redis.get(key);    if (cached) return JSON.parse(cached);  } catch (err) {    if (STRICT) throw err;   // otherwise carry on without the cache  }  const row = await db.getUser(id);  redis.set(key, JSON.stringify(row), { EX: TTL }).catch(log);  return row;} async function updateProfile(id, patch) {  await db.updateUser(id, patch);  await redis.del(`user:${id}`);}

Redis doesn't sit in front of Postgres, and it has never heard of your database. This pattern is called cache-aside: the application reads the cache, falls through on a miss, and writes the result back. Every behaviour above is a line in that function.

Note that an update deletes the key rather than overwriting it. Two concurrent writers can apply their cache writes in the wrong order and leave an old value behind for a full TTL. Deleting narrows that window, and the next read repopulates from the truth.

On the wire

  • (no commands yet)
When it breaks

A cache hides load until it can't

ReadySend a request and watch the path it takes.

A cache stampede happens when a popular key expires and every waiting request misses together. Postgres suddenly sees the traffic the cache was absorbing. Run it both ways and watch the query counter on the database.

Common defences are a short lock so one request rebuilds the entry, adding random jitter to TTLs so keys don't expire together, and refreshing popular keys before they expire.

Scenario E

An outage can be slower than a miss

The bypass path here takes 198 ms against 150 ms for a miss, because the application waits out a timeout before giving up. Short timeouts and circuit breakers exist for this reason.

Scenario D

A TTL is a bound, not a fix

Without invalidation, a value can be wrong for as long as its TTL. A short TTL shrinks that window and costs more misses. There's no setting that gives you neither.

Capacity

Bypassing assumes Postgres can cope

If the cache was absorbing most reads, “fall back to the database” can turn a Redis outage into a database outage. Failing fast is sometimes the safer choice.

Test yourself

Predict it first

user:42 was cached with a 30 s TTL. 31 seconds later, the same key is requested. What happens?

Fine print

What this model leaves out

Expiry doesn't happen at a precise moment in memory

The struck-through row shows this. Redis removes expired keys when they're accessed, and with a background sampler. Either way an expired key is never returned to a client, so the logical behaviour is exact even though memory is reclaimed later.

Restarting Redis doesn't always empty it

The simulated restart loses everything, as a pure cache with persistence turned off would. With RDB snapshots or an AOF, keys can return after a restart, including ones whose invalidation failed during the outage.

Redis is more than a cache

This page covers its most common role. It also offers lists, sets, sorted sets, streams and pub/sub. Replication, clustering and eviction under memory pressure (maxmemory-policy) are all out of scope here.

The timings are invented

They were picked to give 5 ms and 150 ms. Real numbers depend on network distance, payload size, query plans and load. A well-indexed Postgres read can be very fast. What holds up is the shape: a network round trip to memory usually costs far less than a query that has to do real work.

Keep going

Connected mechanisms