기존 코드는 이런 흐름이었다:
1. DB에서 재고 조회
2. 재고 확인
3. 재고 차감
4. DB 저장
동시에 2명이 주문하면:
재고: 1개
A: 재고 조회 → 1개 있음
B: 재고 조회 → 1개 있음 (A가 아직 차감 안 함)
A: 재고 차감 → 0개
B: 재고 차감 → -1개 💥 overselling 발생!
재고가 마이너스가 되는 overselling 문제가 발생한다.
Redis의 increment 연산은 원자적(Atomic) 으로 처리된다.
동시에 100명이 요청해도 Redis는 순서대로 하나씩 처리
→ 재고가 절대 음수가 되지 않음
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
spring:
data:
redis:
host: localhost
port: 6379
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}
Redis에 재고를 관리하는 서비스.
키 형식은 stock:{productId} 로 상품별로 관리한다.
@Service
@RequiredArgsConstructor
public class StockService {
private final RedisTemplate<String, String> redisTemplate;
private String getStockKey(Long productId) {
return "stock:" + productId;
}
// 재고 초기화 (상품 등록 시 호출)
public void initStock(Long productId, int quantity) {
redisTemplate.opsForValue().set(getStockKey(productId), String.valueOf(quantity));
}
// 재고 차감 (원자적 연산 핵심!)
public Long decreaseStock(Long productId, int quantity) {
return redisTemplate.opsForValue().increment(getStockKey(productId), -quantity);
}
// 재고 증가 (차감 실패 시 롤백용)
public void increaseStock(Long productId, int quantity) {
redisTemplate.opsForValue().increment(getStockKey(productId), quantity);
}
// 재고 조회
public int getStock(Long productId) {
String value = redisTemplate.opsForValue().get(getStockKey(productId));
return value == null ? 0 : Integer.parseInt(value);
}
}
public void createProduct(ProductCreateRequestDTO dto) {
Product savedProduct = productRepository.save(product);
stockService.initStock(savedProduct.getId(), savedProduct.getStockQuantity());
}

@Transactional
public void createOrder(Long memberId, CreateOrderRequestDTO dto) {
// Redis에서 원자적으로 재고 차감
Long remainStock = stockService.decreaseStock(
dto.getProductId(),
dto.getQuantity()
);
// 재고 부족 시 롤백
if (remainStock < 0) {
stockService.increaseStock(dto.getProductId(), dto.getQuantity());
throw new IllegalArgumentException("재고가 부족합니다.");
}
}
왜 차감 후 체크하나?
Redis increment는 차감 후 결과를 반환한다.
음수가 되면 재고 초과이므로 즉시 롤백하고 예외를 던진다.
재고 7개 상품에 10개 동시 요청:
for i in {1..10}; do
curl -s -X POST http://localhost:8080/order \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"productId": 1, "quantity": 1}' &
done
wait
redis-cli GET stock:1
시작 재고: 7개
동시 요청: 10개
결과 재고: 0개 ✅
성공: 7개 / 실패(재고 부족): 3개 ✅

재고가 음수가 되지 않고 정확히 0에서 멈췄다.
"Redis의
increment연산은 싱글스레드 기반의 원자적 연산으로, 동시 요청이 몰려도 순서대로 처리되어 overselling을 방지합니다. 10개 동시 요청 테스트에서 재고 초과분은 정상적으로 차단됨을 확인했습니다."
increment 원자적 연산으로 동시성 문제 해결