Appearance
https://techblog.woowahan.com/2709/
총 4가지 시스템
- 서비스는 상품의 속성을 정의하고, 관리하는
상품시스템
- 상품 엔티티 : 상품명, 상품 이미지 등 보여지는 요소
- 판매상품 엔티티 : 상품이 어떤식 (판매기간)으로 판매될지를 결정
- 가격정책 엔티티 : 어떻게 판매될지 결정된 판매상품을 어떠한 가격에 얼만큼 팔지 (원금액, 할인금액, 인당재고, 총재고)
- 정의된 상품을 어느 카테고리에 매핑시켜 노출시킬지를 결정하는
전시시스템
- 상품을 상품권화 시키기 위해 고객님의 구매가 이루어질 수 있도록 하는
구매시스템
- 상품권을 음식주문시 사용할 수 있도록 하는
상품권 시스템
요구사항
- 상품의 권종별로 전체 재고수량과 인당 재고수량이 관리되어야 한다.
- 상품의 권종은 전체 재고량을 초과하여 판매되면 안된다.
- 판매가 한번 시작된 상품의 경우에는 재고량 수정이 가능하나 최초 설정된 재고량 이상을 설정할 수 없어야 한다.
- 상품권은 한 개씩 구매한다.
설계
- 전체 재고량의 경우 RDB에 저장하여 관리하고, 트랜잭션이 일어나는 재고사용량의 관리는 연산속도가 빠른 in-memory DB를 사용한다.
- 연산처리는 단일 스레드에서 처리하는 Redis를 이용하여 동시성 이슈를 해결한다.
- 레디스의 데이터 유실이 일어날 수 있으므로, 재고 사용량 데이터를 RDB에 싱크한다.
- 구매번호는 유니크한 값이고, Redis의 Set 자료구조는 중복을 허용하지 않기때문에 구매번호를 Set에 저장할 경우 SCARD 오퍼레이션을 통해 손쉽게 사용량을 가져올 수 있다.
- 재고량 증가 혹은 감소시점에 (삽입만 발생하는) 재고량 히스토리 정보를 누적한다.
구현
{상품번호}:{권종}:stock:{타입}
# 전체 재고 관리
S0630000RU:5000:stock:total
# 개인별 재고 관리
S0630000RU:5000:stock:{회원번호}
- S0630000RU: 상품번호 (오늘도 수고했어 상품)
- 5000: 권종 (5000원권)
- stock: 재고 구분자
- total: 전체 재고 / 201209320003: 회원번호
ADD 구매번호
재고 사용량 증가
구매 과정에 필요한 타 시스템의 API와 동기 방식으로 진행
- 상품 시스템 플로우
트랜잭션 시작
- 구매가 가능한 상태인지 (왜 트랜잭션 열고 유효성 검증하지?)
- 총 재고수량 & 인당 제한수량 확인
- 가능한 경우 Redis에 구매번호 ADD
- 총 재고수량 & 인당 재고 사용량 증가를 트랜잭션으로 묶음
- 가능한 경우 RDB에 재고 히스토리 테이블 INSERT
- 구매가 가능한 상태인지 (왜 트랜잭션 열고 유효성 검증하지?)
트랜잭션 커밋
⭐️ 이 플로우는 동기화된 메소드로 실행됨 (배민이지만 상품권 구매는 빈번하지 않다고 판단한듯)
- AI한테 위 플로우에서 동시성 문제가 발생할 수 있는 포인트를 물어봄
- 재고를 확인하는 시점과 실제로 차감하는 시점 사이에 시간 차이가 존재하기 때문에, 두 요청이 거의 동시에 들어와서 같은 재고 상태를 확인한 후, 각각 재고를 차감하는 상황
- 조회와 업데이트가 분리된 두 개의 독립적인 Redis 명령어로 이루어짐
- Synchronized가 JVM 레벨에서만 동작하여 Redis 레벨의 원자성은 보장하지 못함
- Redis 업데이트는 성공했지만 RDB 트랜잭션이 실패하여 롤백되는 경우
- Redis의 MULTI/EXEC와 Spring의 @Transactional은 완전히 독립적인 트랜잭션 시스템이므로, RDB 예외 발생 시 Redis 트랜잭션은 롤백되지 않음
- EXEC를 실행하는 시점에 큐잉되어있던 명령어들 모두 실행
- 분산 환경에서 Synchronized가 의미가 없음
- 재고를 확인하는 시점과 실제로 차감하는 시점 사이에 시간 차이가 존재하기 때문에, 두 요청이 거의 동시에 들어와서 같은 재고 상태를 확인한 후, 각각 재고를 차감하는 상황
재고를 차감한 이후에 구매가 실패한 경우
- 재고사용량을 차감시키라는 이벤트를
재고사용량 감소 큐
에 발행 트랜잭션 시작
- 레디스에서 전체 재고, 인당 재고 사용량을 구매번호로 제거
- 재고 히스토리 테이블에 재고량 감소
- 메세지 ack 처리
트랜잭션 커밋
생각할 점
- 재고 관리가 빡빡하지 않은 것 같음
- 상품권을 한 개씩 구매함
- 레디스 재고 사용량 변경에 대한 동시성 문제를 메소드 동기화로 해결했음
- 외부 서비스 연동을 동기로 처리함
- 만약
- 상품을 한 번에 여러 개 구매 가능하고
- 재고 변경에 대한 동시성 문제를 분산 환경에 대응해야 하고
- 외부 서비스 연동을 비동기로 처리해야 할만큼 요청이 많이 몰린다면?