Exercise 1 — Cache
- First call: heavy query; the rest: Redis (~sub-ms, 0 queries). The metric that matters: the endpoint's p99 drops from tens of ms to <2ms.
- Stale data ≤10s: acceptable for a public listing; NOT for checkout (there the DB with locks rules — Lesson 10). Defensible TTL: 5-10s for the listing; 0 (no cache) in the purchase flow.
- Invalidation gives instant freshness after a reservation; it introduces the stampede: all readers hit the DB at once after the DELETE. Antidotes: jitter, soft-TTL, worker regeneration (29).
Exercise 2 — Rate limit
1-2. count = r.incr(key, ex=60); if count == 1 the TTL was just set (ex in the same command: atomic). Requests 21+ receive 429.
- Without atomicity, two threads read the same 19, both write 20, both pass: N requests over the limit under concurrency. INCR is atomic because Redis is single-threaded: the "read-add-write" row is never interrupted (Lessons 03/10 applied to Redis).
Exercise 3 — Lock
- The loser waits up to 2s (blocking_timeout) and gets
LockError/LockNotOwnedError→ 409 "card in use". The winner redeems. - Without the TTL, the dead pod's lock lives forever: the card becomes unredeemable eternally (a ghost block). The TTL makes the lock self-cleaning — the price: if your live process takes longer than the TTL, two processes can believe they hold the lock (hence: short operations inside the lock + renew if needed).
- The Redis lock serializes the redemption; data atomicity comes from the DB transaction (10): ledger INSERT + UPDATE inside the transaction. Lock + transaction: two tools, each for its layer.
Exercise 4 — JSONB
- Migration: AddField JSONB default '{}' (safe) + the GIN RunSQL with CONCURRENTLY (11).
Event.objects.filter(metadata__contains={"venue_type": "stadium"})generates@>; the plan uses a Bitmap Index Scan over the GIN.- Deserves a real column: what you ALWAYS filter and/or constrain (
starts_atis already a column). Stays in JSONB: organizer preferences (decoration, optional, no constraints).
Exercise 5 — Stampede
- Without a hot cache: ~50 queries (all of them lose the race). 2. With jitter, expirations spread out and regeneration queries drop to 2-5 (those coinciding in a window). The regeneration lock takes it to 1 — but its complexity is only justified if the recomputation is expensive.
Professor's summary
- Postgres primary; Redis for latency/atomicity/TTL; JSONB for semi-structured; Mongo/ES only with a metric justifying them.
- INCR/SET NX are atomic because of single-threading: your concurrency is solved inside the data server.
- A lock without TTL = an eternal block after a crash. TTL + short operation.
- The stampede is avoided with jitter (free) and single regeneration (when recomputation hurts).
Data module closed. After the correction: Lesson 13 — REST done right (Module 3).