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개 | ✅ 가장 단순하고 안전 |
반응형