Tacnode
Back to Blog
Real-Time Architecture

Real-Time Inventory: Why Oversell Happens and How to Prevent It

Overselling isn’t a counting bug — it’s a concurrency problem. When several checkouts read the same availability before any of them decrements it, they all sell the last unit. Here’s the structural fix.

Alex Kimball
Alex Kimball
Product Marketing
8 min read
Three concurrent checkouts for the same SKU each reading availability as 1 from a cached read before any decrements it, all selling while units sold reaches 3 against an on-hand count of 1

TL;DR: Overselling happens when concurrent checkouts read the same “in stock” number before any of them decrements it — so all of them sell a unit that only one customer can actually get. It’s not a counting error; it’s a concurrency problem, and it’s worst exactly when traffic spikes (flash sales, drops, peak season). Caching availability for speed makes it worse, because a cached count is stale the moment stock moves. The structural fix is to decrement inventory atomically in the same system that serves the availability read — what a Context Lake does — so the second checkout reads what the first one already bought.

What “real-time inventory” actually has to guarantee

Real-time inventory isn’t just showing a current stock number — it’s making the availability a checkout reads reflect the purchases committing alongside it, so two customers can’t both buy the last unit. When availability is served from a cache or a read replica that lags the writes decrementing it, concurrent checkouts read the same count and oversell. The guarantee that matters isn’t freshness of a display number; it’s atomicity of the read-and-decrement under concurrent load.

Most teams think they have a real-time inventory problem when a product page shows the wrong stock count. That’s the visible symptom. The expensive version is invisible until fulfillment: two or three orders accepted for one physical unit, then cancelled days later — a refund, a support ticket, and a customer who won’t come back.

The decision a storefront makes thousands of times a minute is simple to state: is this item still available, and if so, claim one. That’s a read followed by a write. Overselling is what happens when many customers run the read before any of them runs the write. Each sees the item in stock. Each is allowed to buy. The decrement happens after the fact, and the warehouse can’t honor what the checkout already promised.

This is why oversell clusters around exactly the moments a business cares about most — a flash sale, a sneaker drop, Black Friday, a limited-edition SKU. Those are bursts of concurrent demand against shared, scarce state, which is the precise condition under which a read-then-write race fires reliably.

Why the usual inventory stack oversells

The standard pattern looks reasonable. The source of truth for stock lives in a transactional database. To keep product pages and the cart fast, availability is cached — in Redis, a CDN, a search index, or a read replica — and refreshed on a short interval or via change events.

That cache is where oversell is born. The cached availability is, by construction, a copy of the stock count from a moment ago. During a burst, the moment-ago count says “in stock” for every concurrent checkout, because none of their decrements have propagated yet. The system is fast and wrong at the same time.

Caching the count doesn’t fix it — it institutionalizes the lag. A 200ms-fresh cache still serves the same “1 available” to ten checkouts that arrive inside the same 200ms. Shrinking the refresh interval narrows the window; it never closes it, because the writes that should change the answer are racing the reads that depend on it.

Decrementing in application code reopens the race. Reading availability in the app, deciding to allow the sale, then issuing a separate decrement leaves a gap between the read and the write. Two requests both complete the read before either completes the write — the classic derived-state race, applied to stock.

Splitting inventory across systems creates divergence. When the storefront, the cart service, the search index, and the OMS each hold their own view of availability, they disagree about what’s left. An item shows in stock in search, sells out in the cart, and gets re-listed by the next index refresh — the retrieval gap between systems that can’t be read under one consistent snapshot.

None of these is a performance problem. They’re all the same structural issue: the availability a checkout reads doesn’t reflect the purchases happening beside it.

How a Context Lake prevents oversell

A Context Lake closes the gap by serving the availability read and committing the stock decrement in one coherent system, fast enough that you don’t have to move the count into a cache that drifts.

Atomic read-and-decrement. When the platform owns the inventory ledger as its system of record, the “claim one unit” operation is ACID-serialized: concurrent checkouts for the same SKU are ordered, so the first sale commits and the second reads the updated count — “0 left” — instead of a stale “1.” Two customers can’t both claim the last unit because the decrement and the availability check happen against the same committed state. This is the inventory case of context under concurrency: a shared, scarce resource that must be read and modified atomically under burst.

One availability number, not four. Because storefront, cart, search, and fulfillment read from the same Context Lake under one consistent snapshot, they stop disagreeing about what’s in stock. The divergence between a search index that says “available” and a cart that says “sold out” disappears — there’s one source of availability, served fresh to every surface.

Fast enough that you don’t cache around it. The reason teams move availability into Redis or a read replica is that the transactional source can’t serve the read fast enough at scale. A Context Lake uses hybrid storage — row-oriented for the point lookup, columnar for the analytical rollups — so the availability read is fast and current, and the decrement stays inside the transactional boundary that protects it. You get the speed that pushed you toward a cache without the staleness the cache introduced.

The result: during the flash sale, the second concurrent checkout for the last unit reads it as gone — because, in one coherent system, it was — and the order that the warehouse can’t fulfill is never accepted in the first place.

Frequently Asked Questions

The takeaway

Overselling looks like a counting bug and is actually a concurrency one. The product page showing the wrong number is cosmetic; the real cost is accepting orders you can’t fulfill, which happens whenever concurrent checkouts read availability that hasn’t caught up to the purchases beside them. Caching for speed and decrementing in app code both widen that gap.

Closing it means serving the availability read and committing the stock decrement in one coherent system — fast enough that you don’t have to cache around it. That’s what a Context Lake does for real-time commerce: the second checkout for the last unit reads it as gone, and the oversold order is never accepted.

Real-Time InventoryEcommerceOversellConcurrencyContext Lake
Alex Kimball

Written by Alex Kimball

Former Cockroach Labs. Tells stories about infrastructure that actually make sense.

Ready to see Tacnode Context Lake in action?

Book a demo and discover how Tacnode can power your AI-native applications.

Book a Demo