It was 1 AM when the ops girl ripped me out of a dream with a phone call: “The flash sale page went completely white as soon as we launched!” I opened the monitoring dashboard and saw the database connection pool had burst, CPU was pegged at 95%, only 37 coupons had been issued, yet inventory still showed over 2000 remaining — overselling wasn’t prevented, and the service crashed first. Classic “the hotter the event, the more embarrassing the system.”

Breaking down the problem

We were running an “on-the-hour” flash sale with 10,000 coupons, hyped for two straight days. The moment it went live, we were hit with roughly 80,000 QPS instantly. The original design was dead simple: when a request arrived, check the stock field in the database; if stock > 0, execute UPDATE ... SET stock = stock - 1, then insert a claim record. Under concurrency, this completely fell apart — two requests simultaneously read stock = 5, both decided they could deduct, and we ended up issuing 6 coupons. Overselling by 1. Even worse, row locks and deadlocks dragged the entire database instance into the ground.

In plain terms, there were two root causes: the “read-check-write” sequence is not atomic, and disk + row locks can’t survive high concurrency. Optimistic locking with a version column fixes overselling, but at tens of thousands of QPS, the flood of conflict retries would still slam MySQL’s CPU to 100%. That’s treating a symptom. Serializing requests through a message queue prevents overselling too, but the real‑time feedback is gone — users wait several seconds for a result, and the experience tanks. We needed a solution that could atomically handle “check + deduct” and withstand memory‑speed reads and writes.