Case Study 1 — Redis Where It Belongs: Cache, Not Replacement

NoSQL done right is usually NoSQL used alongside a relational database for the specific thing it's great at. A team added Redis as a caching layer in front of PostgreSQL — solving a read-load problem without abandoning the relational core that held their actual data.

Background

An e-commerce site's product pages were read enormously — millions of views per hour, far more reads than writes. Each page render hit PostgreSQL for the product, its category, its current price, and a few computed bits. PostgreSQL handled it, but at peak the read load was straining the database, and page latency crept up. Someone suggested "move products to MongoDB for scale."

The team paused and asked the right question (Chapter 37's framework, informally): what's actually the problem? It wasn't the data model — products are relational (categories, suppliers, orders reference them), and they relied on transactions and joins. It was read volume on hot, slowly-changing data. That's not a "switch databases" problem; it's a caching problem.

The right tool: Redis as a cache

They added Redis — a key-value store — as a cache in front of PostgreSQL, not a replacement for it:

   request → check Redis for "product:1234"
              ├─ hit  → return cached JSON (sub-millisecond, no DB hit)
              └─ miss → query PostgreSQL, store result in Redis (with a TTL), return it
def get_product(pid):
    cached = redis.get(f"product:{pid}")
    if cached:
        return json.loads(cached)                 # fast path: served from Redis
    product = db_fetch_product(pid)               # slow path: PostgreSQL (source of truth)
    redis.set(f"product:{pid}", json.dumps(product), ex=300)   # cache for 5 minutes
    return product

PostgreSQL remained the source of truth — all writes go there, with full integrity, transactions, and joins. Redis served the hot reads from memory at sub-millisecond speed, absorbing the bulk of the traffic. On a product update, the app invalidated (deleted) that product's cache key so the next read refreshed it. (Slight staleness within the TTL was acceptable for a product page — a deliberate trade, Chapter 15/20.)

The result: PostgreSQL's read load dropped dramatically (most reads never reached it), page latency fell, and the relational core — products, orders, integrity — was untouched. They scaled reads without sacrificing anything the relational model gave them.

The analysis

  1. Diagnose the actual problem before switching databases. The instinct was "move to NoSQL for scale," but the problem was read volume, not the data model. The data was, and should stay, relational. The right fix targeted the actual bottleneck (reads on hot data) with the right tool (a cache).

  2. Redis is a complement, not a replacement. Key-value stores excel at "fetch X by key, fast" — caching, sessions, rate limits, queues. They are not general databases (no joins, no rich queries). Used in front of a relational database, Redis is one of the most effective scaling tools there is; used instead of one, it's the wrong tool for relational data.

  3. Keep a single source of truth. PostgreSQL stayed authoritative; Redis was a derived, disposable copy with a TTL. If Redis vanished, no data was lost — just a temporary performance dip. That separation (durable source of truth + fast disposable cache) is the safe caching architecture.

  4. Caching is a deliberate staleness trade (Chapters 15, 20). A 5-minute TTL means product data can be up to 5 minutes stale — fine for a product page, with cache invalidation on write to keep it fresh. As always, the staleness must be a conscious, acceptable choice.

  5. Polyglot, but minimal. They added one specialized system for one clear need (read caching), keeping the architecture simple. That's healthy polyglot persistence — each tool for what it's genuinely best at — versus sprawl that adds operational cost without clear benefit.

Discussion questions

  1. Why was "move products to MongoDB" the wrong diagnosis? What was the real problem?
  2. Why is Redis a complement to PostgreSQL here rather than a replacement?
  3. What makes "PostgreSQL = source of truth, Redis = disposable cache" a safe architecture?
  4. What staleness trade did the TTL introduce, and why was it acceptable?
  5. ⭐ How would you handle cache invalidation when a product's price changes, to avoid showing a stale price for up to the TTL?