There is a well-worn engineering heuristic that goes something like this: if you have a high-throughput, low-latency problem, reach for a cache. Redis, in particular, has become synonymous with that instinct. It is fast, it is in-memory, it is battle-tested across an enormous range of industries, and for a certain class of problem it is genuinely the right call. But a case study from Shopify's engineering team challenges the assumption that Redis is automatically the right tool for every high-concurrency scenario — and the lesson is worth sitting with, because it runs counter to what most of us have been trained to reach for.

According to reports from Shopify's engineering blog, the company replaced Redis with MySQL for managing inventory reservations and found that the relational database not only held up under load, but scaled more reliably than the Redis-based approach it replaced. The reason comes down to a concurrency model that MySQL has quietly had for decades, one that is frequently underestimated: multi-version concurrency control, or MVCC.

The Problem: Inventory Under Flash-Sale Conditions

Inventory reservation is a deceptively hard problem at scale. The naive version looks simple enough: when a customer adds an item to their cart, decrement a counter. When they check out, decrement it again. When they abandon the cart, increment it back. In a single-threaded system processing requests one at a time, this is trivial. In a distributed system handling thousands of concurrent requests during a flash sale — where the same limited-quantity product is being added to thousands of carts simultaneously — the naive version falls apart immediately.

The failure mode is overselling: more customers believe they have reserved a unit than actually exist. The root cause is a classic read-modify-write race condition. Two requests read the same inventory count at nearly the same time, both see enough stock, both decrement, and both believe they succeeded. The result is negative inventory and a customer service nightmare. Avoiding this requires some form of coordination: either serialising all writes to a single queue, acquiring a distributed lock, or using a datastore that can enforce atomicity and isolation at the storage level.

Redis is a common choice for this kind of coordination. Its single-threaded command execution model means that certain operations — like the atomic DECR command or Lua scripts that combine a read and a write — are inherently serialised. Two clients cannot interleave in the middle of an atomic command. This gives Redis a strong story for the inventory problem, and it is presumably why Shopify had been using it in the first place.

Why Redis Became the Bottleneck

The trouble with Redis's serialisation model is that it is, by design, sequential at the command level. That single-threaded execution is precisely what makes it safe for atomic operations, but it is also what limits its throughput ceiling for workloads that are write-heavy with high contention on a small number of keys. During a major flash sale — a product drop where tens of thousands of customers hammer a single SKU in the space of minutes — every reservation attempt queues up behind every other one. Even with Redis's impressive raw throughput, the latency profile under that level of contention on a hot key can become unpredictable.

There are mitigations: sharding, pipelining, client-side queuing, Lua scripting to minimise round-trips. But each mitigation adds complexity, and complexity in a critical path is a liability. According to Shopify's reporting, the Redis-based approach was functional but presented scaling challenges that prompted the team to look more carefully at whether a different storage model might fit the problem better.

How MySQL's MVCC Changes the Equation

Multi-version concurrency control is the mechanism that allows a database to serve reads without blocking writes, and writes without blocking reads, by maintaining multiple versions of a row simultaneously. When a transaction begins, it receives a consistent snapshot of the database as it existed at that moment. Other transactions can modify the same rows concurrently, and each sees its own consistent view. Conflicts are detected at commit time rather than at read time.

For inventory reservations, the key insight is that MySQL's InnoDB storage engine — which implements MVCC — can handle a large volume of concurrent write transactions against the same row far more gracefully than its reputation for being "slow" might suggest. When multiple transactions attempt to update the same inventory row, InnoDB uses row-level locking rather than table-level locking. A transaction that is in the process of decrementing a counter holds a lock on that specific row, but only for the duration of the update. Other transactions queue briefly, the lock releases in microseconds, and the queue drains quickly. The database engine manages this internally and efficiently.

Critically, InnoDB's row-level locking under MVCC is not the same as the blunt table-scan locking of older MySQL configurations or the MyISAM storage engine. Decades of optimisation have gone into making InnoDB's lock contention handling efficient at exactly this kind of hot-row workload. The serialisation still happens — it must, to preserve correctness — but it happens inside a storage engine that has been tuned for this pattern for a very long time.

The Operational Simplicity Argument

Beyond the raw concurrency story, the Shopify case study surfaces an equally important engineering argument: operational complexity. Running Redis as a critical component of an inventory pipeline means operating a separate stateful service, managing its replication and persistence configuration carefully (Redis's durability guarantees depend heavily on how it is configured), and reasoning about what happens during a Redis failover at the exact moment a flash sale is underway. Redis persistence modes involve tradeoffs between durability and performance that require deliberate choices — options like RDB snapshotting and AOF logging each carry different reliability profiles.

MySQL, by contrast, is a transactional database with durable writes by default. Shopify's engineering infrastructure already relies heavily on MySQL for its core data. Using MySQL for inventory reservations means one fewer external dependency, one fewer failure domain, and leverages the battle-hardened operational tooling — replication, backup, monitoring, failover — that the team had already invested in. When something goes wrong at 3am during a major sale, the fewer moving parts the better.

The Broader Lesson: Fashion vs. Fitness

What makes this case study worth discussing beyond its technical specifics is what it says about how infrastructure decisions are made in the industry. Redis has accumulated a powerful reputation, and reputations — especially deserved ones — have a tendency to become defaults. Engineers reach for Redis not because they have carefully modelled their access patterns and concluded that an in-memory key-value store is optimal, but because it is the established answer to "I need something fast and concurrent." That is not an unreasonable default, but it is a default, and defaults are not always correct.

The Shopify result is a reminder that MySQL — and relational databases in general — are not the legacy technology that a certain strain of engineering culture has treated them as. InnoDB's MVCC implementation is genuinely sophisticated. Transactional semantics that are "just there" by default are genuinely valuable, especially in domains like inventory where correctness matters more than raw throughput. The fact that a relational database can, in the right circumstances, outperform a cache for write-heavy concurrent workloads is not a paradox — it is a reflection of thirty years of engineering investment in a technology that is very easy to underestimate.

The right response to this case study is not to swing the pendulum the other way and argue that Redis is overrated or that you should always use MySQL. The right response is the more boring and more useful one: model your workload, understand the concurrency properties of your tools, and test your assumptions under realistic load before committing to an architecture. Shopify did that work and found a surprising answer. It is worth doing the same.