Redis를 재고 캐싱에 사용하는 경우, 가장 흔하게 구현하는 메서드 중 하나가 setQuantity()입니다. 예를 들어, 다음과 같이 현재 재고를 TTL과 함께 덮어쓰는 방식입니다.
public void setQuantity(final Long productId, final Long quantity, final Duration ttl) {
redisTemplate.opsForValue().set(getKey(productId), String.valueOf(quantity), ttl);
}
이 방식은 직관적으로 간단해 보입니다. 하지만 실제 운영 환경에서 setQuantity()는 심각한 동시성 문제를 유발할 수 있습니다.
문제 시나리오
다음과 같은 상황을 생각해봅시다.
- 어떤 상품의 Redis 재고가 3으로 설정되어 있습니다.
- 사용자 A가 해당 상품을 주문해 재고가 2로 감소합니다.
- 거의 동시에 백오피스에서 관리자가 잘못된 재고를 수정하려고 setQuantity(productId, 4) 호출
- Redis의 재고는 2에서 4로 되돌아가며 덮어쓰기 됩니다.
즉, 없는 재고를 다시 팔 수 있는 상태가 됩니다.
이 문제는 특히 재고를 Redis에 캐시해 빠르게 감소시키고, DB 동기화를 비동기로 처리하는 시스템에서 자주 발생합니다.
백오피스의 수동 조작이 실시간 동기화 로직과 충돌하면서 잘못된 상태를 만들어버리는 것입니다.
해결 방법
Redis는 INCRBY, DECRBY 명령을 제공합니다. 이들은 원자적 연산으로 동시 접근 시에도 안전하게 작동합니다.
따라서 운영 환경에서는 다음과 같이 수정하는 것이 좋습니다.
private final String DECREASE_SCRIPT = """
local key = KEYS[1]
local decrease = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key))
if current and current >= decrease then
return redis.call('decrby', key, decrease)
else
return -1
end
""";
public void increase(final Long productId, final Long amount) {
redisTemplate.opsForValue().increment(getKey(productId), amount);
}
public Long decrease(final Long productId, final Long amount) {
return redisTemplate.execute(
new DefaultRedisScript<>(DECREASE_SCRIPT, Long.class),
List.of(getKey(productId)),
String.valueOf(amount)
);
}
increment()는 내부적으로 Redis의 INCRBY 연산을 사용하며, 하나의 키에 대해 순차적으로 처리되므로 race condition이 없습니다.
admin 처리 방법
관리자가 수동으로 재고를 조정해야 한다면 단순히 덮어쓰지 않고, 증감 기준으로 조정하는 방식이 더 안전합니다.
예를 들어, 입고를 하는 경우라면 increase(productId, amount) 메서드를 사용하고, 잘못된 정정이라면, decrease(productId, amount)를 사용하고, 재고 초기화만 예외적으로 setQuantity() 사용합니다.
결론
setQuantity()는 단순하지만 위험한 방법입니다.
실시간 재고를 다루는 시스템에서는 데이터의 일관성과 동시성 문제를 항상 고려해야 합니다.
운영 환경에서는 반드시 increase() / decrease() 방식으로 대체하고, 백오피스 조작도 신중하게 설계해야 합니다.
'Project' 카테고리의 다른 글
[whatda] ArgumentResolver로 토큰 인증하기 (0) | 2025.05.27 |
---|---|
[s-market] Kafka 중복 메시지 소비로 인한 재고 이중 차감 (0) | 2025.04.30 |
[s-market] Kafka 파티션 키 전략으로 이벤트 순서 보장하기 (0) | 2025.04.29 |
[s-market] Kafka Consumer Redis 복구방식 개선 (0) | 2025.04.29 |
[s-market] Redis 이벤트성 상품 재고 차감 전략 (0) | 2025.04.29 |