Implementing High-Concurrency Seckill Systems with Redis

Core Characteristics of Seckill Systems

  1. Extreme Concurrent Load: Seckill events generate massive instantaneous traffic, often reaching hundreds of thousands or millions of QPS. Traditional databases typically support only thousands of concurrent connections, whereas a single Redis node can handle tens of thousands. This makes Redis suitable for processing the bulk of requests, with only a small subset proceeding to the database layer.

  2. Read-Intensive, Write-Sparse: The scenario involves a vast number of users competing for a limited inventory. Consequently, read operations (checking product details and stock) far outnumber successful write operations (placing orders). Queries are typically simple key-value lookups based on product IDs, which Redis handles efficiently.

Managing Enventory Synchronization

Product inventory is stored entirely in Redis. Stock levels are read directly from Redis. During the order placement phase, inventory deduction occurs directly in Redis. The deduction event is then published to a message queue for asynchronous synchronization to the backend database, thereby preventing instantaneous pressure on the database.

Key-Value Structure for Inventory

  • Key: Product ID.
  • Value: A string or hash, e.g., {"total": 1000, "reserved": 50}. total represents the total available stock, and reserved indicates the quantity already claimed.

Ensuring Atomic Inventory Operations

Using Lua Scripts

Inventory verification and deduction are combined into a single, atomic Lua script executed on the Redis server.

-- Retrieve current inventory data
local stockData = redis.call("HMGET", KEYS[1], "total", "reserved");
local totalStock = tonumber(stockData[1])
local reservedStock = tonumber(stockData[2])
local requestQty = tonumber(ARGV[1])

-- Check if requested quantity is available
if reservedStock + requestQty <= totalStock then
    -- Increment the reserved count
    redis.call("HINCRBY", KEYS[1], "reserved", requestQty)
    return requestQty -- Success
end
return 0 -- Failure, insufficient stock

The client checks the script's return value: a positive number indicates success, while 0 indicates failure.

Using Distributed Locks

A Redis-based lock can also serialize access to the inventory check-and-update block.

import redis

redis_client = redis.Redis()
product_key = "item_123"
client_id = "unique_client_identifier"
requested_qty = 1
lock_timeout = 10  # seconds

def acquire_lock(key, value, timeout):
    return redis_client.set(key, value, nx=True, ex=timeout)

def release_lock(key, value):
    # Use Lua to ensure only the lock owner releases it
    script = """
    if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("DEL", KEYS[1])
    else
        return 0
    end
    """
    redis_client.eval(script, 1, key, value)

# Attempt to acquire the lock
if acquire_lock(product_key, client_id, lock_timeout):
    try:
        # Perform inventory check and deduction under the lock
        current_reserved = redis_client.hget(product_key, "reserved")
        total = redis_client.hget(product_key, "total")
        if int(current_reserved) + requested_qty <= int(total):
            redis_client.hincrby(product_key, "reserved", requested_qty)
            # Proceed with order processing...
        else:
            # Handle insufficient stock
            pass
    finally:
        release_lock(product_key, client_id)
else:
    # Failed to acquire lock, handle retry or failure
    pass

Consistency of Inventory Deduction

The design prioritizes Redis deduction, followed by asynchronous DB sync via message queue, achieving eventual consistency. However, edge cases must be handled:

  1. Redis Deduction Succeeds, Message Queue Fails:

    • Option A: Roll back the Redis deduction and report order failure.
    • Option B: Treat the operation as a failure, potentially resulting in slightly fewer sales (under-selling).
  2. System Crash After Successful Deduction & Message Send:

    • The calling service can invoke a "restore stock" API. This API should use a unique trensaction ID (passed during both deduction and restoration attempts) to determine if restoration is necessary, preventing double-counting.

Restoring Inventory

The restore operation should first update the database to ensure inventory accuracy, then synchronize the change back to Redis. Since restorations are rare in seckill scenarios, direct DB writes are acceptable. If the sync to Redis fails later, it only causes minor under-selling, which is tolerable.

A race condition can occur where a restore request arrives before its corresponding deduction request (which is still in the async queue). The system should temporarily store such restore requests and retry them periodically. If a restore cannot be finalized after a significant period (e.g., one hour), an alert should trigger for manual intervention.

Inventory Monitoring

After a seckill event concludes, compare the actual number of successful orders with the total inventory deducted in Redis. This monitoring helps quickly identify inconsistencies, especially critical over-selling issues.

Tags: Redis Seckill High Concurrency Distributed Systems Inventory Management

Posted on Fri, 21 Aug 2026 16:22:25 +0000 by mcirl2