아래의 내용은 앞전 글에서 작성하였기에 링크만 따로 첨부합니다.
1. DB락과 분산락의 차이 [링크]
2. Lettuce가 아니라 Redisson Client를 선택한 이유 [링크]
3. Redis 분산락의 장단점 (MySQL 네임드락과 비교) [링크]
현재 주문API는
장바구니에서 여러 상품 구매
,상품 상세페이지에서 바로 구매
를 전제로 1~n개 종류의 상품 주문을 받을 수 있도록 설계되어있습니다.
- 이에 따라 Lock도 상품별로 획득하도록 Lock name을
productId: {productId}
로 설정해주었습니다.
productIds
를 stream으로 돌리며 List<RLock>
을 생성대기시간 10초, 락 유효시간 1초
로 설정line 35~36을 보면
try catch
이후의finally
문에서 분산락을 반납하고 있습니다
->트랜잭션 커밋
이후에 분산락을 반납(해제)해야만 하는 이유가 있기 때문인데요... 그 이유는 무엇일까요?
사진출처 - 마켓컬리 기술블로그[링크]
트랜잭션 커밋
이전에 락을 반납함으로써, 다른 트랜잭션이 커밋 이전 시점의 재고 조회
를 해오게 되고,데이터정합성
이 깨지게 되는 문제가 발생합니다. 데드락
도 발생할 수 있습니다. 이렇듯 여러 문제들이 발생할 수 있으므로, 트랜잭션 범위 밖에서 분산락을 반납해주는 것이 필요합니다.
DB의 커넥션
을 가져와야 합니다. 트랜잭션이 진행되는 동안 해당 커넥션을 트랜잭션이 소유하게 되는데요.커넥션을 물고 있다
라고 표현하기도 합니다. 분산락을 획득하지도 않은
트랜잭션들이 커넥션을 물고 분산락 획득을 대기
한다? -> 커넥션이 마르거나, 다른 API 호출들이 커넥션을 획득하지 못하는 불상사가 생길 수 있습니다. 분산락을 먼저 획득하고, 트랜잭션이 시작될 수 있도록
해주는게 아닌가~ 라고 생각합니다. 여러 주문이 동시에 들어오는 동시성 문제
를 productId를 키값으로 하는 분산락
을 구현함으로써 해결하였다.
또한 1~n개의 상품 종류가 담긴 주문의 동시성문제를 해결했다는 점에서, 장바구니 구매
와 상품 상세페이지 바로구매
를 하나의 API로 처리할 수 있도록 했다는 의의가 있다.
단, 여러개의 상품종류를 주문할 경우 상품 번호 순으로 오름차순 정렬
해서 분산락을 획득하도록 해야만, 데드락
을 예방할 수 있다.
IF 상품1, 2, 3을 한번에 주문하는 경우
productId:1
,productId:2
,productId:3
의 분산락을 모두 획득해야합니다.- 3개의 분산락을 모두 획득해야만
비즈니스 로직
을 시작할 수 있는데, 만약 상품1의 분산락을 먼저 획득하고 상품2,3을 기다리게 된다면 어떻게 될까요?
-> 현재 기준분산락 유효시간은 1초이기 때문에
예외가 발생하며 끝나게 됩니다.- 상품 1의 분산락을 점유하고 있었기에 해당 상품을 구매하고자 했던 다른 요청들은 꼼짝없이 기다릴 수 밖에 없습니다..
분산락 유효시간을 길게 해준다
라는 대응은,
상품 1,2,3
을 한번에 주문하는 케이스처럼 분산락 획득을 모두 획득하는 케이스들이주구장창 분산락을 물고서 '다른 상품 분산락'을 기다리는 문제
를 발생시키게 될 우려가 있습니다.
현재 프로젝트도
상품번호 순으로 정렬
만 되어있다면, 소소한 트래픽 정도는 감당할 수 있습니다.
그러나선착순 이벤트
와 같이 단기간에 많은 요청이 몰리는 상황에서는분산락 획득 요청 타임아웃
이 속출하면서 백엔드 개발자의 휴대전화에 불이 나게 될 것입니다.(시스템 장애 콜)
- '트래픽 스파이크'에 어떻게 대응할 수 있을까요?
행사상품은 단품으로만 구매할 수 있도록 한다.
- 일단 행사상품 상세페이지에서
장바구니 담기
를 없애고,- 백엔드에서도 행사상품들이
장바구니 결제
로 다른 상품들과 결제되는지 체크하고 막는다.
여러 상품의 분산락을 획득하기 위해 낭비되는 시간(리소스)
을 줄일 수 있음과 동시에 1~n개의 상품종류를 한번에 주문
할 수 있다.관련 Ref
1. 배민 - 선물하기 시스템의 상품 재고는 어떻게 관리되어질까? [링크]
2. 여기어때 - Redis&Kafka를 활용한 선착순 쿠폰 이벤트 개발기 (feat. 네고왕) [링크]
위 링크들에서는 Redis Transaction
으로 재고 조회와 차감을 묶고, Set 자료구조를 사용하여 Redis로 재고를 관리하고 있다.
평시에 적용할만한 방법이라기 보다는, 선착순 이벤트
와 같이 예측가능하면서도 단시간에 폭발적인 트래픽 이슈에 대한 대응방안으로서 좋은 것 같다고 생각한다.
(재고 히스토리를 RDB에 기록하긴 하지만, 재고에 대한 관리자체는 Redis에서 이루어지고 RDB에 남은 재고수치를 기록하거나 하진 않는다.)
재고 조회와 차감
에 대해서만 Redis Transaction 기반으로
동기적으로 작동하기에 (모든 비즈니스로직을 동기적으로 처리하는) -> 분산락 방식
보다 더 빠르다. 재고사용량
을 Redis에서 관리하고 있기에, 레디스가 갑자기 다운된다면참고 Ref
풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson [링크]
Basically expired events are generated when the Redis server deletes the key and not when the time to live theoretically reaches the value of zero.
- 기본적으로 expired이벤트는 Redis 서버가 키를 삭제할 때 생성되며 TTL(Time to Live)이 이론적으로 0에 도달할 때가 아닙니다.
[Redis 공식문서 - Redis keyspace notifications]
없다면 RDB 조회해서 Redis 세팅하고 재고차감
, 있다면 그냥 Redis 재고차감
하는 식으로 재고를 관리하고키 만료 이벤트
를 발행하여 재고를 RDB에 반영
위 시스템을 구상했으나, expired event의 발행시점이 너무 늦어져서, 재고 데이터 정합성
이 틀어질 확률이 매우 높기에 해당 아이디어를 기각하게 되었다.
유익한 글이었습니다.