Shopify Advanced #databases#concurrency#mysql#transactions

Shopify · Inventory Reservation at Scale

How Shopify stops the last item selling to two people at once — and why the fix evolved from a Redis counter to per-unit rows with SKIP LOCKED. Run a flash sale and watch each design break.

Note: this is an illustrative client-side model — the numbers teach the why, they aren't from a real database.

Step 1 of 7

One unit, two buyers, the same millisecond

A flash sale drops. 2,000 people hit checkout for 50 units.

Checkout has to hold stock while payment clears, so the last unit never sells twice. That hold is the entire problem, and four designs solve it four different ways.

Buyers→Checkout
2,000 buyers · 50 units in stockready
Sold correctly
—
Oversold
—
Checkouts / sec
—
p99 wait (ms)
—
Reservation rows
—

Run this simulator free

Sign in once to unlock every simulator. Takes 20 seconds.

Questions people ask about this

What does SELECT ... FOR UPDATE SKIP LOCKED actually do?+

It locks the rows the query returns, and skips any row another transaction already locked instead of waiting for it. That turns a queue of buyers waiting on one row into many buyers taking different rows in parallel. It is the standard way to hand work out of a database table to competing workers.

Why not just use a transaction across Redis and MySQL?+

There is no shared transaction between two separate systems. A two-phase commit protocol can coordinate them, but it adds a coordinator, blocking on failure, and a lot of operational weight. Moving the hold into the same database as the sale is simpler and removes the failure mode entirely.

Why does one row collapse throughput so badly?+

A row lock is held until the transaction commits. If every checkout updates the same row, the database can only process them one at a time, so throughput is capped at roughly one divided by the transaction time, and the wait for the last buyer grows with the length of the queue.

What is a single-flight refill?+

When many requests notice the pool is low, only one of them performs the refill while the rest wait for that result. Without it, a popular item triggers hundreds of simultaneous refills that all insert rows, which is the stampede the cap was meant to prevent.

Are these numbers real measurements?+

No. They are an illustrative client-side model chosen to show the shape of each trade-off. The relationships are what matter: correctness under crash, throughput under contention, and row growth for a hot item.