자바 기반 웹 프로그램은 기본적으로 멀티스레딩을 기반으로 하기 때문에, 동시성 관련 문제를 잘 해결해야 한다고 들었다.
이번에 내가 하는 쇼핑몰 프로젝트에서도 그 이슈가 딱 터졌다.
예를 들어 몇 만 명이 한정된 재고의 상품을 주문하려고 할 때?
수많은 멀티스레드는 데이터의 재고량을 조회하며 재고가 떨어졌으면 예외를 발생시키면 된다.
하지만 동시에 접근하면? 이거 난감하다. 이 문제에 대한 고찰을 작성하겠다.
DB 동시성 문제
동시에 DB를 조회할 때가 문제이다.
내가 사용하는 DBMS는 Mysql
Mysql은 기본적으로 트랜잭션의 격리 수준으로 Level 2 Repeatable Read를 사용하고 있다. 언두 영역을 통한 다양한 버전 별 MVCC를 통해 버전에 맞는 값을 조회할 수 있다.
하지만 모든 기술은 문제가 있기 마련이다.
그것은 바로 Phantom Read이다.
=> 한 트랜잭션 내에서 같은 쿼리를 두 번 수행할 경우, 첫 번째 쿼리에서 없던 유령 레코드가 두 번째 쿼리에서 나타나는 현상
참조 : https://itpenote.tistory.com/616
내 프로젝트에서의 DB 동시성 문제
조건
=> 수 만명의 사람이 한정된 재고의 상품을 구매하려고 한다. 이때 동시에 주문 버튼을 눌렀을 때 어떻게 해야 할까? (팬텀 리드 발생)
절차
재고는 믿을 수 없는 값이 된다.
해결법
데이터 베이스의 락을 이용하자
=> 재고를 확인하는 순간 리소스를 점유하는 것이다. Mysql은 기본적으로 레코드 락이므로, 해당 레코드의 트랜잭션 동안 락을 점유하는 것이다.
쿼리를 통해 락 점유
=> For Update를 통해 트랜잭션 수행동안 락을 획득해 멀티 스레드 환경에서도 리소스를 점유한다.
@Transactional
public String getkakaoPayUrl(OrderDTO orderDTO, HttpServletRequest request) throws RunOutOfStockException {
/* 재고 확인 */
int productStock = productMapper.selectStockOfProduct(orderDTO.getItemName());
if (orderDTO.getQuantity() > productStock) {
throw new RunOutOfStockException("해당 상품이 품절되었습니다.");
}
서비스 레이어에서 트랜잭션을 건다.
/* 재고 차감 */
updateStock(productStock - orderDTO.getQuantity(), orderDTO.getItemName(), "product_name");
같은 메서드 내의 또 다른 update 쿼리 => 예외가 발생하면 전부 롤백이 되어야 한다. 트랜잭션의 원자성!
장바구니를 결제해보자
기존 상품 재고량은 120개 씩이다.
성공할 경우
=> 두 번 결제했으므로 제대로 수량 차감이 되는 것을 볼 수 있다.
하지만 락을 건다는 것은 성능의 문제가 생긴다는 것이기 때문에, 각별히 신경 써서 주의하며 사용해야 될 것 같다.
성능을 고려한 리팩토링!! (추가)
'📕 Spring Framework > Spring Project' 카테고리의 다른 글
「OutBox Pattern」 활용 (0) | 2022.06.10 |
---|---|
2022.06.07 「프로젝트 중간 점검」 (0) | 2022.06.07 |
2022.06.01 「결제 API - Ver.2」 (2) | 2022.06.01 |
2022.05.29 「결제 API」 (0) | 2022.05.29 |
2022.05.28 「쿠키 수정」 (0) | 2022.05.28 |