Shopify moved inventory reservations out of Redis and into MySQL, the same database that already held the inventory ledger. Black Friday 2025 was the test: $5.1 million in sales per minute at peak, up 11% on the previous year, with the database writer under 50% CPU and readers under 16%.
Moving a hot path from an in-memory store to a relational database sounds like the wrong direction. It was, if the goal was speed. The goal was correctness, and Redis could not give them that at any speed.
RelatedMicrosoft Ships Agent Framework 1.0 for .NET and Python
What was wrong with the Redis design?
The old model kept one quantity key per item and moved it with DECR and INCR. Fast, simple, and blind in two specific ways.
First, a single counter has no concept of location. A merchant selling the same SKU out of three warehouses cannot express that in one number, and the checkout path has no way to reserve from the right one. Second, and worse, reserving a unit and claiming it were separate operations across two separate systems. Redis held the reservation, MySQL held the ledger, and nothing wrapped both. Every gap between those two writes is a window where the two disagree, which shows up as overselling when the counter is optimistic and as underselling, stock quietly held back from customers who would have bought it, when it is pessimistic.
Underselling is the one that gets underrated. Overselling produces angry emails and a support ticket. Underselling produces nothing at all, which is precisely why it can run for months without anyone filing a bug.
Why one row per unit?
Instead of a number that goes up and down, the new model materialises a row for every individual sellable unit. Reserving means claiming a row. That turns a contended counter into a set of independent rows, and it is what makes SKIP LOCKED useful.
SKIP LOCKED tells MySQL to pass over rows another transaction is already holding rather than queueing behind them. With a single counter, every concurrent checkout for a hot item serialises on the same value. With one row per unit, each checkout grabs whichever unit is free and moves on. A thousand shoppers hitting the same product get a thousand different rows instead of a thousand-deep lock queue. Shopify also reworked the composite primary key specifically to reduce the number of locks each claim takes.
The 1,000-row cap per item and location pair is the pragmatic bit. Materialising a row per unit is unbounded if a merchant says they have 400,000 of something, so the model stops at 1,000 and treats anything beyond that as effectively infinite for the purposes of a checkout race. Nobody is contending over unit 4,000.
The bottleneck was not the queries
This is the part most writeups of a migration leave out, and it is the most transferable finding in the whole thing.
When Shopify went looking for scaling limits, the constraint was not query latency. It was connection exhaustion. They added per-caller attribution tags and started measuring how long each caller held a connection, and the data pointed somewhere unexpected: other parts of the checkout path, nothing to do with reservations, were holding database connections longer than they needed to. Fixing those produced a 50% reduction in reads and a 33% reduction in transactions.
The reservation system was not the thing to optimise. It was the thing that finally made the real problem measurable.
RelatedValkey 8.1 undercuts Redis on memory and price
| Redis counter | MySQL row per unit | |
|---|---|---|
| Unit of state | one integer per item | one row per sellable unit |
| Multi-location | not expressible | item and location pair |
| Reserve plus ledger | two systems, no transaction | single ACID transaction |
| Contention model | serialised on one key | SKIP LOCKED across rows |
| Failure mode | over and underselling | bounded at 1,000 rows |
Does this mean you should drop Redis?
No, and that is the wrong lesson to take. Redis did the job it was given correctly. It was given the wrong job.
Inventory reservation looks like a counter and is actually a transactional claim against a ledger that lives elsewhere. Once the invariant you need spans two datastores, the fastest datastore in the world cannot help you, because the problem is not throughput, it is that no transaction covers both writes. The general form: if your consistency requirement crosses a system boundary, moving the state to where the boundary disappears usually beats making either side faster.
The counter-case still stands too. If your hot path genuinely does not need to be transactional with anything else, a Redis counter remains the right call and this migration is not an argument against it.
- SKIP LOCKED shows up more. It has been in MySQL since 8.0 and stays underused. Expect more queue-and-claim workloads to move back into the primary database.
- Per-caller connection attribution. The tagging trick that found the real bottleneck is cheap and most teams do not do it. This is the copyable part.
- Black Friday 2026 is the real test. One peak day proves the design holds. Two proves it holds as volume grows.
Our take
The headline reads like a contrarian infrastructure take and the actual engineering is more ordinary than that, in a good way. Nobody at Shopify decided Redis was bad. They noticed the reservation system could not represent something the business needed, multi-location stock, and that fixing it properly required a transaction that spanned two stores. There is only one way to get that, and it is to stop having two stores.
What makes it worth reading is the connection-exhaustion detour. A team migrating a hot path built the instrumentation to prove the migration worked, and the instrumentation found a bigger problem somewhere else entirely. That happens far more often than migration writeups admit, and it is usually the part with the most reusable value.
- OfficialShopify Engineering: scaling inventory reservations the migration writeup, with the Black Friday 2025 numbers
- ReferenceMySQL manual: locking reads, SKIP LOCKED and NOWAIT what SKIP LOCKED actually guarantees
- ReferenceRedis DECR documentation the atomicity the old design relied on, and its limits
Original analysis by GenZTech, based on Shopify Engineering's published account of the reservations migration and its Black Friday 2025 peak figures.
