# Exercise 1: Size and Shard a Cache Cluster for a Flash Sale

`DealDrop` runs a daily flash sale. Its product-and-inventory cache currently runs on a **single Redis node** (32 GB RAM, ~120k ops/s measured ceiling). Use `cache_distributed_caching_theory.md` plus `04-caching_strategies/`, `05-cache_invalidation/` and `07-ttl/`.

## Facts

- Catalog: 6 million products. Average cached product JSON: 3 KB. Each product also has a small inventory counter key (~100 bytes with overhead).
- Only ~15% of products are read in any given day; during the sale, **1,000 "deal" products** receive **80%** of all reads.
- Peak traffic at sale start: **450k cache reads/s** and **20k writes/s** (inventory decrements), sustained for ~10 minutes.
- Redis memory overhead: assume +40% on top of raw value size for keys, metadata and fragmentation.
- At last year's sale the single node hit 100% CPU, latency went from 0.3 ms to 40 ms, the app timed out, and the cache-aside fallback sent ~200k req/s to PostgreSQL, which fell over.
- The team proposes: "Move to Redis Cluster with 6 primaries, `hash(key) % 6` in our client, no replicas to save money."

## Deliverables

1. **Memory sizing.** Estimate the working-set memory for (a) caching the full catalog and (b) caching only the daily-active 15%. Which do you recommend caching, and what TTL/eviction policy goes with it?
2. **Throughput sizing.** If one primary sustains ~100k ops/s comfortably, how many primaries does the *average* load need? Then explain why that number is still **wrong** for this workload (hint: where do the 1,000 deal products land, and what share of 450k reads/s do they carry?).
3. **Critique the proposal** point by point:
   - `hash(key) % 6` in the client vs Redis Cluster's slots — what happens when they add a 7th node next year?
   - no replicas — what happens to the database if one primary dies at sale start?
4. **Hot-key plan.** Design concrete mitigations for the 1,000 deal products for both **reads** (product JSON) and **writes** (inventory decrements). State how each mitigation affects consistency (e.g. can a user see "3 left" when it's actually 0? what stops overselling?).
5. **Warm-up and stampede plan.** The deal list is known 1 hour in advance. Describe how you'd pre-warm, which TTLs you'd use (with jitter), and how you'd prevent a stampede if a deal product's key is evicted mid-sale.
6. **Failure budget.** If a primary fails during the sale and failover takes 15 s, estimate the extra DB load for that window under your design, and say what protects PostgreSQL (e.g. single-flight, a DB-side concurrency limit, serving stale).

## Requirements Checklist

- [ ] Memory math is shown: full catalog ≈ 6M × 3.1 KB × 1.4 ≈ 26 GB; daily-active ≈ 15% of that ≈ 4 GB — and the recommendation uses an LRU/LFU policy plus TTLs instead of caching everything forever.
- [ ] Throughput answer identifies that 80% of 450k reads/s (≈ 360k/s) target 1,000 keys, which partitioning alone puts on a handful of slots/nodes — so adding primaries doesn't fix it.
- [ ] Modulo critique: adding a node remaps ~6/7 of keys (mass miss → DB stampede); Redis Cluster slot migration or consistent hashing moves ~1/N.
- [ ] Replica critique: losing a primary without replicas sends that node's whole share (incl. hot keys) to the DB.
- [ ] Hot reads: in-process L1 with a short TTL (seconds) and/or key replication / replica reads, with staleness explicitly bounded.
- [ ] Hot writes: inventory stays authoritative in **one** atomic place (e.g. a Lua `DECR`-if-positive on one key, or sharded stock buckets that never go below zero) — the plan never relies on a stale L1 copy to accept an order.
- [ ] Warm-up loads the 1,000 deals before the sale, uses jittered TTLs longer than the sale window, and combines single-flight with stale-while-revalidate for mid-sale misses.
