# Flash Sales A flash sale is a time-boxed discount with a hard unit cap. The interesting engineering is not the discount — it is making sure that when a thousand shoppers all hit "buy" on the last ten units, **exactly ten orders succeed.** ## The oversell problem The naive approach — read the sold count, compare to the limit, then increment — has a race: two requests both read "9 of 10 sold", both decide they may proceed, and both write "10". Eleven units sold against a cap of ten. This platform solves it with **atomic, Redis-backed inventory counters.** ## Atomic claims with Redis + Lua `FlashSaleInventoryService` keeps the authoritative sold count in Redis while a sale is live: ``` flash_sale:{id}:inventory:sold → "42" ``` Every mutation runs as a **single Lua `EVAL`**, so the read-check-write sequence is atomic on the Redis side — no `WATCH`/`MULTI`/`EXEC` dance: ```lua -- claim qty units against limit, atomically local sold = tonumber(redis.call('GET', KEYS[1]) or '0') local limit = tonumber(ARGV[1]) local qty = tonumber(ARGV[2]) if sold + qty > limit then return -1 -- sold out end return redis.call('INCRBY', KEYS[1], qty) ``` A claim either returns the new sold count or `-1` for "sold out". There is no window in which two callers can both see the last unit as available. ## Graceful degradation Redis is a [fail-open dependency](/architecture/redis-resilience/) here too. If Redis is unavailable, callers fall back to a PostgreSQL **conditional `UPDATE`** in `FlashSale#claim_unit!` — slower, but still correct and still impossible to oversell. The DB path is kept intact and tested precisely so the Redis fast path can fail safely. PostgreSQL (`flash_sales.quantity_sold`) is kept in sync asynchronously by `FlashSaleUnitsUpdateJob` after every order, so admin and reporting views stay accurate without sitting on the hot path. ## Cart reservations Before checkout, `FlashSaleReservationService` places a **time-bounded hold** so a shopper's cart item is not snatched away mid-purchase: - For **unlimited sales**, reservations are purely advisory — they drive urgency UX ("reserved for 10:00") without consuming capacity. - For **quantity-limited sales**, `reserve!` checks available capacity inside a **pessimistic row lock**, so concurrent requests cannot double-book the last unit. Availability is computed as `limit − sold − reserved-by-everyone-else`. ## Notifications When a sale goes live, background jobs fan out alerts — including to shoppers who have a sale product sitting in a [wishlist](/features/wishlist/) — over email and web push. ## Key files | Concern | Files | |---------|-------| | Models | `FlashSale`, `FlashSaleProduct`, `FlashSaleReservation`, `FlashSaleNotification` | | Services | `FlashSaleInventoryService`, `FlashSaleReservationService`, `FlashSaleService`, `FlashSalePreflightValidator` | | Jobs | `FlashSaleActivationJob`, `FlashSaleUnitsUpdateJob`, `FlashSaleExpirationJob`, `FlashSaleWishlistNotificationJob` |