FluxBuy Drop engine
A flash-sale backend where hundreds of buyers hit a small stock count at the same second, against a payment provider that is slow and fails on purpose.
- Role
- Solo, backend
- Timeline
- Jun – Oct 2026
- Stack
- NestJS · Redis · PostgreSQL · BullMQ
- Status
- Complete, load-tested
Many buyers few units
Sellers list a product with a fixed quantity and a drop time. At that second it becomes buyable by everyone at once. The CRUD around that is easy. What isn't: hundreds of requests reading and decrementing the same stock counter, and a payment step that takes one to four seconds and fails 20% of the time.
- Nothing gets oversold, at any concurrency.
- Nobody gets charged twice, including when a client retries.
- Stock never disappears if a worker dies in the middle of a charge.
- The mock payment provider runs in the same process, so after a crash there is nothing external to ask. Recovery can only apply a policy.
- 900
- Concurrent
winners - 0
- Units
oversold - 1
- DB query per
cold burst
Off the hot path
Stock lives in Redis
A Lua script checks and decrements the stock key in one atomic step. Postgres is an async projection: a winning request touches it twice, one read and one insert, and never for stock. A sync worker writes the Redis counts back every 60 seconds.
Payment is a queue, not a call
The order is created as
PENDINGand the client gets an answer in milliseconds. A BullMQ worker charges the card, and on failure puts the stock back and notifies anyone on the restock waitlist.Every actor claims before it acts
The payment worker and the cleanup worker can both decide the fate of the same order. Each one moves it to
PROCESSINGwith a conditional update and only continues if that update matched a row.Built from scratch
A Redis HTTP cache with stampede protection and stale-while-revalidate, a sliding-window rate limiter in one Lua script, and idempotent orders keyed by an idempotency key.
Two workers one order
Early versions of both workers wrote the final status unconditionally, keyed only by order id. Whichever ran second overwrote the first, which meant either stock restored twice or a real oversell.
The fix keeps the claim in Order.status. Nothing else needs to stay in sync with it, so losing Redis entirely still leaves a working state machine. A test script starves the payment queue so jobs sit past the cleanup window and the collision has to happen. At 400 buyers against 400 units, cleanup swept 127 orders mid-queue, the payment worker backed off on all 127, and the books closed at 192 left plus 208 paid.
const { count } = await prisma.order.updateMany({
where: { id: order.id, status: 'PENDING' },
data: { status: 'PROCESSING' },
});
if (count === 0) return; // cleanup got here firstWhere it landed
The first synchronous version held a row lock on the stock row inside a transaction and stopped at about 70 concurrent buyers. The current one has been run at 900 concurrent winners against 900 units with zero oversold. Every number on this page comes from a script in the repo.
What it doesn't do yet is written down in the README: locks are single-Redis with no Redlock quorum, the cache has no invalidation on write, and correctness is proven by load scripts rather than a test suite.