Spring 숙련 (Lua Script)

KimGwangmin·2026년 9월 16일

Lua Script

Redis에서 사용하는 언어

MySQL이 SQL을 실행하듯, Redis에서는 Lua Script를 실행한다.

Redis에서의 트랜잭션

문제 상황

Redis에서는 @Transactional을 사용할 수 없다. (@Transactional은 RDB를 기준으로 작동)

@Service
@RequiredArgsConstructor
public class UnsafeStockService {
    private final StringRedisTemplate redisTemplate;

    public boolean decrease(
            Long productId
    ) {
        String key = "stock:" + productId;
        String value = redisTemplate.opsForValue().get(key);
        int stock = Integer.parseInt(value);
        if (stock <= 0) {
            return false;
        }
        // 조회 이후 다른 요청이 재고를 차감했어도, 읽어 둔 stock 값은 그대로입니다.
        redisTemplate.opsForValue().decrement(key);
        return true;
    }
}

해결: Lua Script로 원자적 실행

@Service
@RequiredArgsConstructor
public class StockService {
    // 재고를 확인한 뒤 차감하는 과정을 Redis 안에서 한 번에 실행합니다.
    private static final DefaultRedisScript<Long> DECREASE_SCRIPT = new DefaultRedisScript<>("""
            local stock = tonumber(redis.call('GET', KEYS[1]))
            if stock <= 0 then
                return 0
            end
            redis.call('DECR', KEYS[1])
            return 1
            """, Long.class);
    private final StringRedisTemplate redisTemplate;

    public boolean decrease(
            Long productId
    ) {
        String key = "stock:" + productId;
        // key는 스크립트의 KEYS[1]로 전달됩니다.
        Long result = redisTemplate.execute(DECREASE_SCRIPT, List.of(key));
        // 스크립트가 반환한 1은 차감 성공, 0은 재고 부족입니다.
        return result == 1L;
    }
}

DB 단에서 실행되는 코드는 원자적이다.
MySQL에서 하나의 SQL 쿼리는 원자적으로 처리되고, Redis에서 하나의 Lua Script는 원자적으로 처리된다.

0개의 댓글