In our Flash Sale System Design article, we outlined a classic high-stakes scenario: 1,000 highly discounted iPhones and 2 million users aggressively clicking "Buy Now" at the exact same second. To prevent the system from collapsing, we shifted the primary transaction battlefield from a traditional Database to a Redis In-Memory Cache.
However, keeping the system online is only half the battle. The other half is absolute data integrity: How do you guarantee that you don't accidentally sell 1,001 iPhones when you only have 1,000 in stock? Overselling is an operational and customer service nightmare. This article dives deep into the root cause—"Race Conditions"—and explores how to leverage Atomic Operations, from database locks to caching mechanisms, to solve this problem permanently.
The Root Cause: Race Conditions in Read-Modify-Write Cycles
Consider the most basic inventory deduction logic a junior developer might write:
[User A] & [User B] both attempt to purchase 1 item at 00:00:01
User A reads DB: Stock = 1
User B reads DB: Stock = 1
User A calculates: 1 - 1 = 0. Writes to DB: Stock = 0.
User B calculates: 1 - 1 = 0. Writes to DB: Stock = 0.
=> Result: Both A and B successfully purchased the item, but inventory was only deducted by 1. The system has oversold!
This is a classic Race Condition. To resolve this, we must ensure that this Read-Modify-Write cycle becomes an Atomic Operation—a single, indivisible unit of execution that cannot be interrupted or interwoven with other concurrent requests.
This is the most traditional approach. Relational databases (like MySQL or PostgreSQL) offer Row-level Locking. By utilizing the SELECT ... FOR UPDATE statement, the system essentially places a physical lock on the inventory data row.
Any subsequent requests attempting to access that specific row are forced to wait in a queue until the first request completes its transaction (Commit or Rollback).
Trade-off: You achieve absolute data safety (0% oversell), but at the cost of catastrophic performance. In a Flash Sale scenario, millions of requests waiting on each other will rapidly exhaust the Database Connection Pool, skyrocket latency, and trigger a cascading failure that takes down the entire system.
Level 2: Optimistic Locking (Versioning)
Instead of locking the database, we introduce a version column to the inventory table.
The update logic shifts to a conditional state: UPDATE inventory SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = [previously_read_version].
If User A and User B both read version = 5, User A might successfully execute the update (changing the version to 6). When User B's UPDATE command executes immediately after, the condition version = 5 is no longer true. The database rejects the update, and User B's request fails and must be retried.
Trade-off: This eliminates database bottlenecks since there are no active locks. However, during a Flash Sale with massive concurrency, the rejection rate will be astronomical. Your application servers will burn massive amounts of CPU cycles just handling failed requests and executing infinite retries.
Level 3: Redis DECR (In-Memory Cache)
This is the baseline strategy used by giants like Amazon or Shopee to offload pressure from the primary database. Inventory numbers are pre-loaded into Redis. The DECR (Decrement) command in Redis is natively single-threaded and atomic. This means Redis naturally processes only one deduction at a microsecond.
Request 1001 arrives: Redis returns -1 (Purchase rejected, out of stock).
The Flaw: A pure DECR operation is incredibly fast, but if it returns -1, you have technically already deducted the inventory into negative numbers (an oversell at the cache layer). You are forced to immediately call INCR (Increment) to roll back the mistake. Adding this extra network round-trip introduces a critical risk: If the network drops or the application crashes before the INCR command is sent, your cache inventory is permanently corrupted.
To completely eliminate the vulnerability of the pure DECR method, we move the entire conditional logic (check stock -> if sufficient -> then deduct) directly into the Redis server using a Lua Script.
Lua Scripting allows you to package complex logic into a single, unbreakable atomic unit executed natively on the Redis server.
By utilizing this architecture, we eliminate the need for an INCR rollback, cut down on network round-trips, and entirely eradicate the possibility of application-layer overselling.
Strategy Comparison Matrix
Strategy
Execution Layer
Oversell Risk
Performance / Concurrency
Ideal Use Case
Pessimistic Lock
Database
0%
Extremely Low (High risk of DB crash)
Extremely high-value B2B products, low traffic.
Optimistic Lock
Database
0%
Medium (High retry overhead)
Moderate traffic, low collision frequency.
Redis DECR
Cache
Yes (If rollback fails)
Very High
Simple systems where minor discrepancies are acceptable.
Redis Lua Script
Cache
0%
Very High
Flash Sales, Mega Campaigns, massive traffic spikes.
Conclusion
In System Design, there is no single perfect solution, only the right trade-off for your specific context. If you are building a small-scale ticketing platform, Optimistic Locking might be perfectly adequate. But if you are a Technical Product Manager preparing for a Black Friday event, the combination of Redis Cache and Lua Scripts is an industry-standard pattern you must master.
Case StudyApr 11, 2026
Slack's "Green Dot" Problem: The Nightmare of Real-Time Architecture
Displaying an online status seems basic, but it becomes an infrastructure nightmare at scale. Discover how Slack restructured its Pub/Sub model to balance real-time accuracy with server costs.