Shopify replaced Redis with MySQL for inventory reservations — at $5.1 million in sales per minute at Black Friday 2025 peak — and throughput went up. The Shopify Engineering blog post, published May 2026 and hitting Hacker News front page this week, documents how MySQL 8’s SKIP LOCKED replaced Redis for a problem Redis fundamentally couldn’t solve: maintaining ACID consistency across both the reservation and the inventory ledger in a single atomic operation. If Shopify can drop Redis at 14% of U.S. ecommerce traffic, the “always use Redis for ephemeral high-throughput state” assumption deserves serious scrutiny.
The Problem Redis Couldn’t Solve
The original architecture stored inventory reservation counts in Redis using simple INCR and DECR operations — fast, atomic on a single key, and widely considered the right tool for high-throughput ephemeral state. The problem was that reservations and the permanent inventory ledger lived in separate systems. When a checkout completed, updating both required two separate operations — one in Redis, one in MySQL — that couldn’t be wrapped in a single ACID transaction. If either update failed mid-checkout, inventory could drift. As the Shopify team put it: “If we get this wrong in one direction, two buyers purchase the same last unit.”
Redis INCR is atomic. A Redis-plus-MySQL write is not. That gap forced Shopify into dual-write complexity with no clean rollback path — a brittle design at any scale, and a genuinely dangerous one at Black Friday volume.
How MySQL SKIP LOCKED Handles Inventory at Scale
The solution inverts the conventional model. Instead of one row per item with a quantity counter, Shopify now stores one row per sellable unit — capped at 1,000 rows per item/location combination. Reserving three units means selecting and locking three rows in a single transaction, then deleting them. When the order completes, rows move to reserved_quantities. All in one atomic operation. No dual-write. No Redis.
SELECT id FROM inventory_units
WHERE inventory_item_id = 123
AND status = 'available'
FOR UPDATE SKIP LOCKED
LIMIT 3;
SKIP LOCKED is what makes this scale. If Transaction A has locked Row 1, Transaction B skips it automatically and grabs Row 2. No waiting, no blocking — concurrent checkouts claim distinct rows without fighting over the same counter. This feature has been available in MySQL since version 8.0.1 (2018). It is now the default mechanism behind Rails 8’s Solid Queue job backend. The industry has been reaching for Redis for this exact pattern without noticing MySQL already had the answer.
Two additional changes were critical. Switching the primary key from auto-increment to a composite key (shop_id, inventory_item_id, inventory_group_id, id) cut the lock count per reservation from two to one by eliminating the secondary-to-clustered index lookup. Switching isolation level from MySQL’s default REPEATABLE READ to READ COMMITTED eliminated gap locks that had been blocking replenishment inserts during flash sales and causing deadlocks. Neither change is obvious from the MySQL documentation — both only reveal themselves under production load.
Related: pgrust Hits 300x Faster Postgres Analytics — Here’s How
The Bottleneck Nobody Expected
After completing the migration, throughput still hit a ceiling. Writer CPU stayed under 50%. Reader CPU stayed under 16%. Latency looked fine. The numbers didn’t add up — and that mismatch is one of the most confusing signals in production systems.
Shopify tagged every SQL statement with a business process identifier and tracked connection hold time at the ProxySQL layer. The finding was not what anyone expected: the reservation queries themselves weren’t the problem. Other checkout processes held database connections for hundreds of milliseconds, exhausting the shared connection pool and starving the reservation system even when its own queries completed in milliseconds. As Emilie Noel from Shopify Engineering wrote: “If the numbers don’t add up — low CPU but high queuing — instrument the full path. The answer is often in the plumbing, not the engine.”
The fix wasn’t in the reservation system at all. Cleaning up the broader checkout path removed 50% of reads and 33% of transactions, which freed connections and unlocked the throughput headroom. They also reviewed InnoDB thread concurrency settings that hadn’t been touched in years despite significant workload evolution. Both are the kind of work that only surfaces when you instrument at the right layer.
When to Use This Pattern — and When Not To
This migration was right for inventory reservations: short-lived holds, ACID required, pool-bounded (1,000 rows per item), and Shopify already runs MySQL everywhere. Redis is still the correct choice for sub-millisecond key-value lookups, data structures MySQL doesn’t have natively (sorted sets, streams, geo), pub/sub fan-out patterns, and approximate counters where eventual consistency is acceptable. The mistake isn’t using Redis — it’s using Redis for atomicity you can get from your primary database for free.
The broader lesson is that SKIP LOCKED makes MySQL viable for a class of high-concurrency workloads that engineers instinctively reach for Redis to solve. If your team already runs MySQL and needs reservation semantics with guaranteed consistency, adding a Redis cluster for this specific purpose probably isn’t the right call.
Key Takeaways
- MySQL SKIP LOCKED enables concurrent reservation without row blocking — one transaction skips locked rows instead of waiting. Available since MySQL 8.0.1 (2018), underused for inventory-style patterns.
- The “Redis for ephemeral high-throughput state” assumption breaks when you need ACID across both the reservation and the ledger. Redis INCR is atomic on one key; a Redis-MySQL dual write is not.
- Low CPU plus throughput ceiling is not a performance problem — it’s usually a connection pool problem. Instrument hold time at the proxy layer, not just query execution time.
- READ COMMITTED over REPEATABLE READ eliminates gap locks that block replenishment inserts at scale. Composite primary keys cut per-row lock counts. Both are invisible without production load.
- SKIP LOCKED wins for bounded, ACID-required, short-lived reservations on an existing MySQL stack. Redis still wins for speed, complex data structures, and approximate-count patterns.













