MySQL 동시성 제어

아재발자·2024년 6월 3일
0

MySQL

목록 보기
2/6

제가 2018년 1월부터 2021년 9월까지, 총 3년 9개월을 다녔던 회사에서 근무하면서 직접 경험한 내용을 바탕으로 공부하고 알게된 지식을 간단하게 정리하는 글입니다.

최종적으로 낙관적 동시성 제어 방식을 활용하여 재고 수량을 초과하여 주문이 되는 문제를 해결하였습니다.

동시성 제어는 순수 MySQL만을 이용해서 제어하는 방법과 Redis 등 Application의 도움을 받아 제어하는 등 다양한 방법과 상황이 존재합니다.

이 글은 MySQL만을 이용해서 동시성을 제어하는 방법에 대해서 정리합니다.

참고!
아래의 예시 플로우차트는... 2018년도에 대충 PPT로 그린 플로우차트입니다.
즉, 레거시한 이미지 입니다...


동시성 제어의 필요성

개발을 하다보면 동시성을 제어해야만 하는 상황이 생깁니다. 대표적으로 상품을 구매했을 때 재고를 차감하는 로직이 그러합니다.

일반적으로 구매자가 상품을 구매하면 아래와 같은 순서로 처리가 이루어집니다.

  1. 상품을 구매할 수 있는 상태인지 확인합니다.
  2. 구매할 수 있는 상품이라면, 구매자를 주문서 작성 페이지로 이동시킵니다.
  3. 구매자가 주문 정보(구매자 정보, 배송지 정보, 할인 수단 등)를 입력 후 결제를 시도합니다.
  4. 시스템은 결제 페이지로 넘기기 전에 구매자가 입력한 주문서 정보를 DB에 저장하고, 상품의 재고가 유효한지 확인합니다.
  5. 상품의 재고가 유효하면 구매자가 구매한 수량만큼 재고를 차감합니다.
  6. 재고 차감이 성공하면 구매자를 결제 페이지(예: PG 화면 등)로 이동시킵니다.
  7. 결제가 성공하면 주문이 완료됩니다.

위 과정만 본다면 로직에는 별 문제가 없어보입니다. 하지만 같은 시간에 여러 사용자가 같은 상품을 구매하면 어떻게 될까요?

아래는 재고를 차감하는 로직에 대해서 A, B 사용자가 동시에 요청을 했을 때 발생하는 상황을 나타냅니다.

상품의 재고가 5개만 남아있는 상황에서 A 사용자는 5개, B 사용자는 3개를 구매하기를 원합니다.

이 두 사용자가 같은 시간에 구매를 시도할 경우 A 사용자의 트랜잭션이 재고를 반영하기 전에 B 사용자의 트랜잭션에서 상품의 재고를 조회하기 때문에 A와 B 둘 다 재고가 유효하다고 판단을 하게 됩니다.

따라서 상품의 재고는 실제로 5개밖에 존재하지 않지만 Application 로직의 동시성 문제로 두 사용자 모두 정상적으로 구매가 이루어지게 되며, 상품의 재고는 결과적으로 -3개가 되는 상황이 발생하게 됩니다.

이런식으로 동시성을 제어하지 않은 로직은 의도하지 않은 결과를 초래할 수 있습니다.

동시성 제어 방식

비관적 동시성 제어 (Locking Reads 이용)

비관적 동시성 제어 방식은 MySQL의 잠금을 이용해서 동시성 문제를 해결하는 방법입니다.

잠금이 발생하면 다른 트랜잭션은 잠금이 해제될 때 까지 데이터에 접근할 수가 없으므로 처리량(성능)이 떨어진다는 단점이 있지만, 처리 과정 중 실패나 에러가 발생할 경우 데이터를 Rollback하기가 편하다는 장점이 있습니다.

SELECT ... FOR SHARE

읽기는 허용하되 수정은 불가능하게 하는 잠금 방식입니다.

SELECT ... FOR UPDATE

읽기와 수정 둘 다 불가능하게 하는 잠금 방식입니다.

해당 방식은 UPDATE 쿼리를 날렸을 때와 동일한 잠금 방식입니다.


낙관적 동시성 제어

참고!
일반적으로 MySQL은 트랜잭션과 격리 수준, 그리고 잠금 매커니즘을 사용하여 데이터의 순차성과 일관성을 보장하기 때문에, 동시 다발적으로 UPDATE 쿼리가 실행되어도 문제가 발생하지 않습니다

낙관적 동시성 제어는 MySQL의 잠금을 이용하지 않고, UPDATE 쿼리에 조건을 지정하여 쿼리 성공 유무와 적용된 행의 개수로 동시성을 제어하는 방식으로, 비관적 동시성 제어와 다르게 처리량(성능) 저하가 발생하지 않습니다.

대신 처리 과정에서 실패나 에러가 발생할 경우 데이터를 Rollback 하기가 어렵다는 단점이 있습니다.

아래는 낙관적 동시성 제어에 대한 예시입니다.

  1. Update 쿼리 요청을 보냅니다.
    UPDATE TABLE SET stock=stock-3 WHERE stock >= 3; -- 재고가 3개 이상일 때만 차감
  2. Application에서 MySQL 서버가 응답한 데이터(쿼리 성공 여부와 적용된 행)를 확인합니다.
  3. 만약 쿼리가 성공됐고 적용된 행이 존재할 경우 성공에 관련된 처리를 진행합니다.
  4. 만약 쿼리가 실패됐거나, 성공했지만 적용된 행이 존재하지 않을 경우 실패로 간주하고 처리를 합니다.
profile
안녕하세요. 아재 개발자입니다. 공부한 내용을 기록하고 잘못된 부분에 대해서 조언을 받기 위해 velog를 시작했습니다. :)

0개의 댓글