2026-06-15

Atomic Inventory Deduction with Redis + Lua for High-Concurrency Seckill

A deep dive into using Redis cache preheating and Lua scripting to combine inventory checking, purchase limits, and stock deduction into a single atomic operation, significantly reducing database row lock pressure.

Background

In high-concurrency seckill (flash sale) scenarios, inventory deduction is the core bottleneck of the entire pipeline. Direct database operations cause severe row lock contention. This article covers how to use Redis + Lua for high-performance atomic inventory deduction.

The Problem with Traditional Approaches

The most straightforward approach executes SQL:

UPDATE stock SET count = count - 1 
WHERE product_id = ? AND count > 0;

But in seckill scenarios, single-row hot updates cause severe lock contention, causing database CPU spikes and RT increases.

Redis Cache Preheating

Before the seckill event starts, preload inventory data into Redis:

public void warmUpStock(Long activityId) {
    List<ProductStock> stocks = productStockMapper
        .selectByActivityId(activityId);
    
    for (ProductStock stock : stocks) {
        String key = "seckill:stock:" + stock.getProductId();
        redisTemplate.opsForValue().set(key, stock.getCount());
    }
}

Lua Script for Atomic Operations

The core optimization: package inventory check + purchase limit check + stock deduction into a single Lua script executing atomically on Redis:

-- seckill.lua
local stockKey = KEYS[1]
local boughtKey = KEYS[2]
local userId = ARGV[1]
local limitPerUser = tonumber(ARGV[2])

local stock = tonumber(redis.call('get', stockKey) or '0')
if stock <= 0 then
    return -1  -- out of stock
end

local bought = tonumber(redis.call('get', boughtKey .. ':' .. userId) or '0')
if bought >= limitPerUser then
    return -2  -- purchase limit exceeded
end

redis.call('decr', stockKey)
redis.call('incr', boughtKey .. ':' .. userId)

return 1  -- success

Data Consistency Guarantees

  1. Idempotency: requestId + Redis Set prevents duplicate orders
  2. Eventual Consistency: MQ consumer async order creation with retry + DLQ fallback
  3. Inventory Rollback: Order timeout cancellation triggers Lua-based stock replenishment

Performance Comparison

ApproachQPSAverage RT
Direct DB~500200ms+
Redis + Lua~5000+<10ms

Summary

The combination of Redis preheating + Lua atomic scripts + RabbitMQ async peak-shaving effectively handles high-concurrency seckill challenges. Lua scripts execute single-threaded on Redis server, naturally guaranteeing atomicity — the best practice for seckill inventory deduction.

Return to the blog index for more entries.