CASE 01 / 05FURNITUREOPS
VERIFIEDDEPLOYEDOne invariant, defended end to end.
Next.js 14 · TypeScript · Supabase Postgres · Upstash Redis · Playwright · 2026
THE PROBLEM
A naive stock decrement is a race condition: two buyers read the same count, both succeed, and the shop oversells. Handling it at the API layer alone does not survive retried requests, duplicate submissions, or a worker that crashes mid-transaction.
THE INVARIANT
Stock is never oversold — under concurrent purchases, retries, and partial failures.
THE APPROACH
Purchases are accepted at the edge, checked for idempotency, and enqueued to Redis instead of written synchronously. A separate worker drains the queue through a Postgres RPC that row-locks the item, validates stock, and writes the audit log in the same transaction — with retries, a circuit breaker, and a dead-letter queue around it.
VERIFICATION SCRIPTS
ATOMIC STOCK RPC
ACCESS CONTROL IN THE DATABASE
ADMIN JWT MAX AGE
DECISION RECORDS
ADR-001Why a queue, not a synchronous write?
CONTEXT — The obvious design writes stock changes inside the request handler. Under load, that couples user latency to database contention and makes retry semantics ambiguous.
- Synchronous decrement in the API route— retries and duplicate submissions double-decrement; a crash mid-request loses the order
- Optimistic locking in application code— does not survive retried requests or a worker dying between read and write
- Idempotency check → Redis queue → single worker
DECISION — Accept at the edge, deduplicate by idempotency key, enqueue, and let one consumer apply changes through the locking RPC.
ACCEPTED COST — Purchases are eventually consistent (queue latency), which the UI must communicate. In exchange: exactly-once effects, calm failure handling, and a database that can be busy without users feeling it.
ADR-002Why row locks over application-level checks?
CONTEXT — The invariant must hold even if every process above the database misbehaves.
- Check-then-write in the worker— TOCTOU race between check and write
- SELECT … FOR UPDATE inside a Postgres RPC
DECISION — decrement_stock_atomic() locks the row, validates available stock, applies the change, and appends the audit entry — one transaction, one place where the invariant lives.
ACCEPTED COST — Throughput on a single hot item is serialized by design. Jobs are sorted by item id before processing to avoid deadlocks across items.
ADR-003Rate limiter fails open — a documented compromise.
CONTEXT — Edge rate limiting depends on Redis. What happens when Redis itself is unreachable?
- Fail closed: reject all traffic without Redis— turns a cache outage into a full outage; protection becomes self-inflicted denial of service
- Fail open with a logged warning
DECISION — Availability wins for the rate limiter; the stock invariant never depends on it.
ACCEPTED COST — A Redis outage temporarily removes rate protection — accepted and logged, because correctness is enforced a layer down.
⚠ LIMITATION — Single-region queue and database; regional failover is untested. The chaos scripts cover process crashes, not datacenter loss.