DrawLintDrawLint.ai
🧩Core Building Blocks·6 min read

Caching Systems (Redis & Memcached)

The infrastructure behind caching — write strategies, data structures, and HA.

Redis and Memcached are in-memory data stores used to answer hot requests before they reach a slower database or service. A cache is not just a speed trick; it is a way to protect scarce backend capacity, absorb read spikes, and turn repeated expensive work into cheap lookups. The danger is that fast stale data can be worse than slow correct data.

🔭Think of it like…
If your database is a warehouse across town, a cache is the shelf behind the counter. Popular items stay within arm's reach. That shelf must be stocked, expired, and protected from one viral item taking all the space, but it keeps customers from waiting on a truck for every purchase.

The problem: hot reads and expensive recomputation

Databases are excellent sources of truth, but many user requests ask the same question repeatedly: "What is product 123?", "Is this feature flag enabled?", or "What are the top posts?" Without caching, every repeated request consumes database CPU, locks, network round trips, and query planner work.

cache-aside shape
request -> app -> cache GET product:123
                 ├─ hit: return cached JSON in ~sub-ms
                 └─ miss: query database, compute response, SET cache with TTL
  • Latency: memory lookups are usually far faster than disk or complex database queries.
  • Capacity: repeated reads hit the cache, leaving database capacity for writes and uncached queries.
  • Failure isolation: a warm cache can keep read-only parts of a site alive during partial database trouble.
The cache is a copy
A cache creates a second place where data can exist. Every design must answer: who updates it, when does it expire, what happens when it is wrong, and can the source of truth rebuild it from scratch?

Redis vs Memcached

Memcached is a small, fast, distributed string cache. Redis is a richer data-structure server that can also act as a cache, lightweight queue, counter store, rate limiter, pub/sub broker, and coordination primitive.

DimensionMemcachedRedis
Core modelKey -> bytes/stringKey -> strings, hashes, lists, sets, sorted sets, streams, bitmaps
Best useSimple ephemeral cache at very high throughputCache plus richer atomic data operations
PersistenceNone; restart means cold cacheOptional RDB snapshots and AOF logs
Replication / HAUsually client-side sharding; no built-in failoverReplicas, Sentinel, Cluster, managed HA
Memory behaviorSlab allocator, simple evictionConfigurable eviction policies and per-key TTLs
Operational complexityLowHigher because Redis can become critical state
Rule of thumb
Use Memcached when you truly need a disposable key-value cache and want minimal features. Use Redis when you need atomic counters, sorted sets, distributed locks, streams, replication, or persistence. Most modern system designs choose Redis because those features appear quickly.

Redis data structures and real use cases

Redis is popular because operations run close to the data and are atomic on a single instance or shard. Instead of fetching a blob, changing it in app code, and writing it back, you ask Redis to perform a data-structure operation directly.

StructureCommandsUse cases
StringGET, SET, INCRCached HTML/JSON, counters, rate-limit tokens
HashHGET, HSETObject fields such as user session attributes
ListLPUSH, BRPOPSimple work queues, recent activity lists
SetSADD, SISMEMBERMembership checks, unique viewers, feature cohorts
Sorted setZADD, ZRANGELeaderboards, priority queues, expiring holds
StreamXADD, XREADGROUPDurable-ish event streams and consumer groups
leaderboard with a sorted set
ZADD leaderboard 8700 user:42
ZADD leaderboard 9100 user:99
ZREVRANGE leaderboard 0 9 WITHSCORES

# Redis maintains score order, so top-N does not require scanning every player.

Atomic operations matter

INCR lets many clients update a counter without lost updates. Sorted sets keep rankings ordered while scores change. Lua scripts or transactions can combine a few operations when a rate limiter or seat hold must be checked and updated as one unit.

Persistence, replication, Sentinel, and Cluster

A pure cache can disappear and be rebuilt. Redis often holds state that is expensive or temporarily important, so teams enable persistence and high availability. Be clear whether Redis is disposable cache or semi-durable operational state.

FeatureHow it worksTrade-off
RDB snapshotsPeriodic point-in-time dump to diskCompact and fast to restart, but can lose changes since last snapshot
AOFAppend every write command to a logBetter durability, more disk I/O and rewrite management
ReplicationReplica copies primary asynchronouslyRead scale and failover target, but replicas can lag
SentinelMonitors primary and promotes replicaHA for non-cluster Redis; clients must follow new primary
ClusterShards keyspace across masters with replicasScales memory/write load, but multi-key operations need same hash slot
Redis persistence is not the same as a database guarantee
Redis can be durable enough for many workflows, but configuration matters. AOF fsync policy, replica lag, failover timing, and disk corruption all affect loss windows. For money or irreplaceable records, keep a stronger source of truth.

Write strategies: cache-aside, write-through, write-back

The write strategy defines how the cache and source of truth stay aligned. Most bugs come from forgetting that an update must either invalidate or refresh every cached representation affected by the change.

StrategyRead pathWrite pathBest forRisk
Cache-asideApp reads cache; on miss reads DB and fills cacheWrite DB, then delete or update cacheDefault web-app patternStale data if invalidation is missed
Write-throughCache should already contain fresh valueWrite cache and DB togetherSmall objects needing fresh readsHigher write latency and coupling
Write-backReads cacheWrite cache first, flush DB laterVery high write bursts where loss is acceptableData loss if cache dies before flush
Refresh-aheadCache refreshes before expiryBackground job recomputes hot keysExpensive but predictable dataWasted work for keys that cool down
cache-aside with invalidation
def get_product(id):
    key = f"product:{id}"
    cached = redis.get(key)
    if cached:
        return decode(cached)

    product = db.query("SELECT * FROM products WHERE id = ?", id)
    redis.set(key, encode(product), ex=300)  # 5 minute TTL
    return product

def update_product(id, patch):
    db.update_product(id, patch)             # source of truth first
    redis.delete(f"product:{id}")            # force next read to refill
Delete is often safer than update
Updating one cache key sounds efficient, but products may appear in many cached views: detail page, search result, category page, and recommendation cards. Deleting affected keys and letting reads refill avoids partial refresh logic, as long as misses are protected from stampedes.

Eviction policies, stampedes, and hot keys

Cache memory is finite. When Redis or Memcached is full, it must evict something or reject writes. Good cache design treats eviction as normal, not exceptional: the database must still answer correctly when the cache is empty.

  • TTL: each key expires after a configured time. Add jitter so thousands of keys do not expire at the same second.
  • LRU / LFU: evict least-recently-used or least-frequently-used keys when memory is full.
  • noeviction: reject new writes instead of evicting. Useful when keys are not safely disposable.
avoiding a cache stampede
val = redis.get(key)
if val is not None:
    return val

# only one request should rebuild the hot key
if redis.set("lock:" + key, "1", nx=True, ex=10):
    val = recompute_from_db()
    redis.set(key, val, ex=300 + random_jitter())
    redis.delete("lock:" + key)
    return val

sleep_briefly()
return redis.get(key) or fallback_response()
Hot-key problem
If one key receives a huge share of traffic, sharding the cache may not help because all requests still route to the node holding that key. Use local in-process caching, request coalescing, key replication, CDN caching, or split the value into buckets when a single Redis node becomes hot.
Key takeaways
  • Memcached is a simple disposable string cache; Redis is a richer data-structure server with persistence, replication, and clustering options.
  • Redis structures such as hashes, sets, sorted sets, counters, and streams let you model rate limits, leaderboards, presence, queues, and seat holds atomically.
  • RDB snapshots and AOF logs improve Redis recovery, but configuration determines the real data-loss window.
  • Cache-aside is the default write strategy; every write must refresh or invalidate affected keys, and the database must remain the source of truth unless explicitly designed otherwise.
  • Eviction, stampedes, and hot keys are normal production problems; use TTL jitter, single-flight, replication, local caches, and careful eviction policies.
Choose Memcached for a purely disposable key-value cache when you only need simple string/blob lookups, minimal operational surface, and very high cache throughput. If you need counters, sorted sets, persistence, replication, or richer atomic operations, Redis is usually the better fit.
If a hot key expires, many requests miss at the same time and all recompute from the database. The database sees a sudden burst it was never sized for. Single-flight locks, TTL jitter, stale-while-revalidate, and refresh-ahead reduce the burst.
The application writes to the cache first and persists to the database later. That makes writes fast, but a cache crash before flushing can lose accepted user changes. Use it only when the loss window is acceptable or backed by a durable log.
Finished this lesson?

Mark it complete to track your progress through the workbook.