100명의 사용자가 하나의 상품을 1개씩 구매하면, 이 상품의 재고는 정확하게 100개가 차감되는 게 당연하다. 그런데 실제 코드를 실행시켜보면 오차가 발생하는 경우가 많다. 한 마디로, 재고가 100개가 차감되어야 하는데 95개, 92개 이런식으로 차감되는 상황이 발생한다는 뜻이다. 정말 이런 일이 일어나는 지 실습을 해보자.
✅ 실습 진행하기
이전 스케쥴러 로직 비활성화 해놓기
콘솔창에 로그가 계속 찍히는 걸 방지하기 위해, 이전 실습에서 활성화 시켜둔 스케쥴러 로직을 비활성화 해두자.
RedisIntermediateProjectApplication
@EnableScheduling
@SpringBootApplication
public class RedisIntermediateProjectApplication {
public static void main(String[] args) {
SpringApplication.run(RedisIntermediateProjectApplication.class, args);
}
}
재고 차감 API 살펴보기
효율적인 실습을 위해 재고 차감 API를 미리 만들어뒀다.
Stock
@Entity(name = "stocks")
@Getter
@NoArgsConstructor
public class Stock {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long quantity;
public Stock(Long quantity) {
this.quantity = quantity;
}
public void decrease(Long quantity) {
if (this.quantity - quantity < 0) {
throw new RuntimeException("재고는 0개 미만이 될 수 없습니다.");
}
this.quantity -= 1;
}
}
StockController
@RestController
@RequestMapping("/stocks")
@RequiredArgsConstructor
public class StockController {
private final StockService stockService;
@PostMapping("/{id}/decrease")
public void decrease(
@PathVariable Long id
) {
stockService.decrease(id);
}
}
StockService
@Service
@RequiredArgsConstructor
public class StockService {
private final StockRepository stockRepository;
@Transactional
public void decrease(Long id) {
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(id);
stockRepository.save(stock);
}
}
StockRepository
public interface StockRepository extends JpaRepository<Stock, Long> {
}
Spring Boot 실행시켜서 DB 동기화시키기
테스트를 위해 DB에 데이터 넣어주기
부하 테스트 스크립트 살펴보기
scripts/script_2-1.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
// 가상 유저(VUs) 100명으로 설정
vus: 100,
// 테스트를 10초 동안 진행
duration: '10s',
};
export default function () {
// 재고 차감 API
const url = 'http://localhost:8080/stocks/1/decrease';
const params = {
headers: {
'Content-Type': 'application/json',
},
};
// POST 요청 전송
const res = http.post(url, null, params);
// 1초 간격으로 요청 발송
sleep(1);
// 응답 상태 코드 확인 (200 OK가 왔는 지 체크)
check(res, {
'status is 200': (r) => r.status === 200,
});
}
부하 테스트 진행하기
$ k6 run scripts/script_2-1.js
1000번의 요청을 처리했음을 알 수 있고, 91 TPS 정도의 성능이 나오는 걸 알 수 있다.
DB 확인하기
1000번의 요청을 처리했기 때문에 10,000개의 재고에서 1,000개의 재고가 차감돼서 남아있는 재고가 9,000개여야 정상이다. 확인해보자.
DB를 확인해봤더니 재고가 138개 밖에 차감되지 않았다. 왜 이런 일이 발생한걸까? 다음 강의에서 이 문제의 원인에 대해 살펴보자.