본문 바로가기

Study

[내일배움캠프 TIL] Redis 재고 차감 후 확인하는 이유 & Lua 스크립트 안 쓰는 이유

728x90
반응형

배운 것

선착순 쿠폰 발급에서 재고를 확인 후 차감이 아니라 차감 후 확인하는 이유와, Lua 스크립트를 쓰지 않는 이유.


왜 확인 후 차감은 안 되냐

코드

// ❌ 위험한 방식 — 확인 후 차감
long stock = getStock();      // Redis 명령어 1
if (stock > 0) {
    decrementStock();         // Redis 명령어 2
}

문제

Redis는 싱글스레드라 명령어 하나씩 순서대로 실행된다. 하지만 명령어가 2개라서 명령어 1과 명령어 2 사이에 다른 요청의 명령어가 끼어들 수 있다.

요청A: getStock()      → 1  (재고 있음, 차감하려고 함)
요청B: getStock()      → 1  ← A의 두 명령 사이에 끼어듦
요청A: decrementStock() → 0
요청B: decrementStock() → -1 ← 둘 다 통과!

재고가 1개인데 2명이 발급받는 문제가 생긴다.


차감 후 확인하면 왜 안전하냐

코드

// ✅ 안전한 방식 — 차감 후 확인
long remaining = decrementStock();  // Redis 명령어 1개
if (remaining < 0) {
    incrementStock(); // 롤백
    throw new CouponOutOfStockException();
}

이유

DECR 명령어 하나로 끝난다. 명령어가 1개라서 사이에 끼어들 틈이 없다.

요청1: DECR → 0   → 통과 ✅
요청2: DECR → -1  → 롤백 후 품절 ❌
요청3: DECR → -2  → 롤백 후 품절 ❌
...이하 동일

재고 1개일 때 10명이 동시 요청해도 정확히 1명만 통과한다.


Lua 스크립트로도 해결되는데 왜 안 쓰냐

Lua로 해결하면

local stock = redis.call('GET', KEYS[1])
if tonumber(stock) <= 0 then
    return -1
end
return redis.call('DECR', KEYS[1])

Lua 스크립트 실행 중엔 다른 명령어가 끼어들 수 없어서 GET + DECR 2개를 원자적으로 묶을 수 있다.

근데 안 쓰는 이유

DECR 하나로 해결되는데 굳이 Lua를 쓸 필요가 없다. 더 단순한 게 더 낫다.

DECR 1개  →  명령어 1개, 사이 없음, 원자적 보장
Lua (GET + DECR)  →  명령어 2개를 묶음, 더 복잡

Lua는 명령어 여러 개를 묶지 않으면 해결이 안 될 때 쓰는 것이다. 드롭 서비스처럼 중복체크 → 재고체크 → 선점 → 순번발급 4단계를 묶어야 할 때가 Lua가 필요한 케이스다.


핵심 정리

Redis 싱글스레드가 보장하는 건 명령어 하나의 원자성이다.

Java에서 명령어를 2번 날리면 그 사이는 보장 못한다.

그래서 명령어를 1개(DECR)로 줄이는 게 가장 단순하고 안전한 방법이다.

방식 명령어 수 안전한가

확인 후 차감 (GET → DECR) 2개 ❌ 사이에 끼어들 수 있음
Lua (GET + DECR 묶음) 2개 (원자적) ✅ 안전하지만 불필요하게 복잡
차감 후 확인 (DECR) 1개 ✅ 가장 단순하고 안전
반응형