pattern

In-Memory Reservation Service

also called Inventory Reservation Service, Single-Owner Counter, Memory-Speed Allocation

Moving a heavily-contended counter or inventory pool into a single-owner in-memory service that serialises decisions at memory speed and persists asynchronously, trading a small durability window for orders of magnitude more throughput.

flash-salehot-rowinventorydurabilitycontention

A limited resource — remaining stock, voucher redemptions, seats, rate-limit tokens — must be decremented atomically. In a database that means every request serialising on one row, giving a ceiling of roughly a thousand operations per second that no amount of hardware improves, because the bottleneck is the lock, not the capacity.

An in-memory reservation service owns that counter in a single process, decides on a single thread, and writes to durable storage asynchronously. Decisions happen at memory speed — hundreds of thousands per second — and the database sees a stream of already-decided outcomes rather than a contention point.

Why it matters

This is the only design that makes genuine flash sales work. When a hundred thousand people attempt to buy a thousand items in the same second, every database-resident approach either serialises them into a queue that outlasts the sale, or admits enough concurrency to oversell.

The insight is that the decision and the record of the decision have completely different requirements. The decision must be fast, exact and serialised. The record must be durable and can be written a moment later. Most architectures conflate them because a transaction does both, and separating them is what unlocks the throughput.

Implementation patterns

  • Single-writer ownership per resource, so no locking is required within the service — the serialisation is the thread, not a mutex.
  • Shard by resource across instances, so different products are owned by different processes and the design scales horizontally even though each counter is single-owner.
  • Append-only durable log written asynchronously, batched, so persistence is efficient and recovery is a replay.
  • Recovery by replaying the log plus reconciliation against the authoritative store, with a defined and accepted error boundary for the unpersisted window.
  • Reservation with a TTL rather than immediate commitment, so an abandoned checkout returns stock automatically without a compensating workflow.
  • Ownership transfer that guarantees a single owner — a lease with fencing, or a consensus-backed assignment. Two instances believing they own the same counter is the failure that oversells without limit, and it must be structurally impossible rather than unlikely.
  • A drain path, so a planned restart hands off cleanly instead of relying on recovery.
  • Back-pressure to callers when the request rate exceeds even memory speed, rather than unbounded queueing.

Industry example

The pattern is standard in large-scale flash-sale commerce — Southeast Asian and Indian marketplaces running voucher and limited-stock promotions, and ticketing platforms allocating seats under extreme contention. The common characteristic is demand that exceeds supply by orders of magnitude within seconds, which is exactly the regime where database contention becomes the binding constraint.

The same structure appears in rate limiting at scale, where token buckets held in memory with periodic synchronisation replace a shared counter that would otherwise be both a bottleneck and a single point of failure.

Failure scenarios

  • Two owners for one counter, from a failover that did not fence the previous owner — unbounded overselling.
  • Loss of the unpersisted window on a crash, with no reconciliation process defined, so the discrepancy is discovered by customers.
  • No TTL on reservations, so abandoned carts hold stock indefinitely and the sale appears sold out while inventory remains.
  • The service as a single point of failure for the purchase path, without a defined fallback.
  • Unbounded in-memory state, from reservations that are never expired or from tracking every request.
  • Reconciliation that is never run, so drift between memory and the ledger accumulates silently.
  • Adopting it before it is needed, carrying substantial durability and failover complexity for a workload that bucketed counters would have served.

Trade-offs

The central trade is a durability window in exchange for throughput. Decisions acknowledged to users but not yet persisted are lost on an ungraceful failure, which means the business must accept a small, bounded error rate — a handful of orders that may need reconciling or refunding. That acceptance is a commercial decision and must be obtained explicitly, not assumed.

It also introduces a stateful, single-owner service into an architecture that is otherwise stateless, with all the operational weight that implies: failover, ownership, drain, recovery, and a class of bug that only appears during transitions.

The trade is correctness-under-crash and operational simplicity in exchange for two or three orders of magnitude of throughput on a contended resource. It is right when the contention genuinely exceeds what bucketed counters can serve and when the business can tolerate a small error boundary. Where exactness is a legal requirement and throughput is merely inconvenient, the database is the correct answer and the queue is the price.

Interview question

"Design the inventory path for a sale where a hundred thousand people want a thousand units in the first second. Then tell me exactly what happens when the process holding the counter is killed mid-sale, and what you tell the twelve customers whose confirmed orders we cannot honour."