✎ Edit content·DAY 085 · POST 4 OF 5 · Code Example

Redis: Caching & Beyond

NoSQL Databases · 12 slides
DAY 085 · POST 4 OF 5
(REMINDER)
DAY 085
Caching with Redis, Step by Step
@saurav_dnj_24github.com/SauravDnj · linkedin.com/in/sauravdnj
1 / 12

Theme

Palette

Download

4K — sharpest, slowest
🎬 Video options
Preparing preview…
Live preview · loops the “none” effect
All rendering runs in your browser. No server, no cost, no upload. MP4/WebM = full motion + effects · GIF = lightweight loop · PNG/PDF = static for the Instagram & LinkedIn carousel.

Caption (tap to copy)

📖 Deep dive (full written explanation)

The slides stay clean and scannable. Here's the in-depth explanation behind each one — great for the blog version, show notes, or studying the topic properly.
Slide 1 · Caching with Redis, Step by Step

This cover sets the tone for a practical, copy-paste post. We're going to take the concept from earlier posts and turn it into working code: the cache-aside pattern, which is the strategy you'll reach for the overwhelming majority of the time. The promise is concrete — watch a slow database call become a microsecond memory hit, with the miss and hit paths shown explicitly.

Along the way you'll learn the four moves that make up real caching: reading through the cache with a database fallback, writing results back with a TTL, invalidating on updates, and choosing between a JSON blob and a Redis hash for structured data. Every snippet uses redis-py and runs against a local Redis.

Slide 2 · The pattern: cache-aside

Cache-aside, also called lazy loading, is the default caching strategy and the one worth mastering first. The flow is simple and the application owns all the logic: on a read, check Redis first; if the value is present, that's a cache hit and you return it immediately; if it's absent, that's a miss, so you read from the database, store the result in Redis with a TTL, and return it. Future reads of the same key are now hits.

The appeal of cache-aside is its predictability. The cache only ever holds data that has actually been requested, so memory isn't wasted on cold data. And because the app explicitly controls when to read, write, and invalidate the cache, the behavior is easy to reason about and debug — which is exactly why it's the pattern you should learn before more exotic ones like write-through or write-behind.

Slide 3 · 1. Connect from Python

This snippet establishes the connection. After installing redis-py, you create a client pointed at your Redis host and port. The db=0 selects one of Redis's numbered logical databases (most setups just use 0). The detail worth highlighting is decode_responses=True: without it, redis-py returns raw bytes, and you'd have to call .decode() everywhere; with it, you get Python strings back, which is almost always what you want for application code.

The r.ping() call is a quick health check — it returns True if Redis is reachable. In real applications you'd typically use a connection pool (redis-py creates one under the hood) and configure timeouts, but this minimal setup is enough to start issuing commands. With the client in hand, every Redis command becomes a method call on r.

Slide 4 · 2. The cache-aside read

This is the core of the post: the cache-aside read implemented as a function. It builds a namespaced key from the user id, then calls r.get(key). If the result is not None, it's a cache hit — we deserialize the stored JSON and return it immediately, never touching the database. This is the fast path that handles most traffic.

If the result is None, it's a cache miss. We fall back to the database with db_fetch_user, then store the result back into Redis with json.dumps and a 300-second TTL via the ex parameter, so the next read of this user is a hit for the next five minutes. This single function embodies the entire cache-aside pattern: check, fall back, populate, return. Everything else in the post refines around this skeleton.

Slide 5 · Hit vs miss path

This decision tree maps the cache-aside logic as an explicit flow, which is useful because the control flow is the pattern. The first question is whether the key exists in Redis. If yes, return the cached value — that's the hit, the cheap and common case. If no, attempt to read the database. If that read succeeds, cache the result with a TTL and return it — the miss path. If the database read fails, fall through to an error or a fallback response.

Drawing it as a tree makes the edge case visible: what happens when both the cache misses and the database read fails? A robust implementation needs an answer — return an error, a stale value, or a default — rather than crashing. Most cache bugs hide in the branches people don't draw, so making the full tree explicit is a good habit before you write the code.

Slide 6 · 3. Invalidate on write

This snippet handles the write side: keeping the cache consistent when data changes. The pattern is to first write to the database — the source of truth — then delete the cached key. Deleting rather than updating means the next read will miss, fetch the fresh value from the database, and re-cache it. The cache self-corrects on the next access.

This ordering matters: write the database first so that even if the delete fails, the truth is already updated and the cache will eventually expire via its TTL. The comment captures the elegance — by simply removing the stale key, you avoid the hard problem of computing what the new cached value should be, and you guarantee the next reader sees current data. The following slide explains why delete beats update in more depth.

Slide 7 · Why delete, not update

This slide justifies the 'delete, don't update' choice, which trips up many engineers. The instinct on a write is to update the cache with the new value to keep it warm. But that introduces a subtle race: if two updates happen close together, their cache writes can arrive out of order, leaving the cache holding an older value than the database — a stale cache that won't fix itself until expiry.

Deleting sidesteps the entire problem. After a delete, there's no value to be stale; the next read is forced to fetch the current truth from the database and re-cache it. You trade one extra cache miss (cheap) for a strong guarantee of correctness (valuable). The general principle: when in doubt, invalidate rather than update, because invalidation has no 'wrong value' failure mode — the worst case is a miss, which simply reloads.

Slide 8 · 4. Structured data with a hash

This snippet shows the alternative to storing a JSON blob: a Redis hash. hset with a mapping stores the object as a set of named fields under one key. The advantage appears in the next two lines. hincrby atomically increments a single field — here the login count — without reading and rewriting the whole object, and without any race between concurrent increments. hget reads just one field, no need to fetch and deserialize the entire object.

This is field-level access, and it's a genuine capability difference, not just a style choice. With a JSON blob, incrementing logins means GET, parse, increment, serialize, SET — five steps and a race window. With a hash, it's one atomic command. When your cached objects have fields that update independently or frequently, hashes are usually the better fit.

Slide 9 · Blob vs hash

This comparison weighs the two storage approaches. A JSON string is simple: one SET to store, one GET to read, trivial to serialize, and it travels as a single unit. Its weakness is that every read and write deals with the whole object — there are no partial updates, so bumping one field means rewriting everything. A Redis hash supports field-level reads and atomic per-field increments, avoids re-serializing the whole object on small changes, at the cost of slightly more setup and a less convenient single-blob model.

The practical guidance: use a JSON string when you almost always read or write the whole object at once and value simplicity. Use a hash when fields update independently, when you need atomic counters inside the object, or when objects are large enough that re-serializing on every small change is wasteful. Both are valid; match the structure to your access pattern.

Slide 10 · Always set a TTL

This slide states a rule that prevents a whole category of bugs: always set a TTL on cached data. A cache entry with no expiry lives until something explicitly deletes it. If an invalidation is ever missed — a code path that updates the database but forgets to delete the key, a deploy that introduces a bug — that stale value persists indefinitely, and users see wrong data with no self-correction.

A TTL is the safety net. Even if every explicit invalidation fails, the entry expires within minutes and the next read reloads fresh data. The distinction to internalize: cached copies of database data should always carry a TTL, because they're disposable and rebuildable. Only data that Redis genuinely owns — where Redis is the source of truth — should ever be permanent. Copies expire; originals can persist.

Slide 11 · The recipe

This recap turns the post into a reusable recipe: check Redis and fall back to the database; store every miss with a TTL; delete the key on writes to invalidate; reach for hashes when you need field-level access; and never cache without an expiry. Follow these five moves and you have a correct, self-healing cache-aside implementation.

The through-line is that good caching is mostly discipline, not cleverness. The pattern is simple; the bugs come from skipping a step — forgetting a TTL, updating instead of deleting, not handling the double-failure branch. Internalize the recipe and most caching just works.

Slide 12 · Save this. Follow for Day 86.

Post four showed the right way to cache. Post five flips to the wrong ways — the failure modes that survive testing and surface in production: the cache stampede that crashes the database, stale data that lingers, treating Redis as durable storage, missing eviction policies, and big keys that block the single thread.

🎨 AI image prompt (matches this theme + palette)

Paste into Midjourney, DALL·E, Ideogram, etc. to generate an on-brand image, then upload it on the Edit content page. The prompt updates automatically with the selected theme + palette.