Skip to content

선물하기 시스템의 상품 재고는 어떻게 관리되어질까?

https://techblog.woowahan.com/2709/

총 4가지 시스템

  • 서비스는 상품의 속성을 정의하고, 관리하는 상품시스템
    • 상품 엔티티 : 상품명, 상품 이미지 등 보여지는 요소
    • 판매상품 엔티티 : 상품이 어떤식 (판매기간)으로 판매될지를 결정
    • 가격정책 엔티티 : 어떻게 판매될지 결정된 판매상품을 어떠한 가격에 얼만큼 팔지 (원금액, 할인금액, 인당재고, 총재고)
  • 정의된 상품을 어느 카테고리에 매핑시켜 노출시킬지를 결정하는 전시시스템
  • 상품을 상품권화 시키기 위해 고객님의 구매가 이루어질 수 있도록 하는 구매시스템
  • 상품권을 음식주문시 사용할 수 있도록 하는 상품권 시스템

요구사항

  1. 상품의 권종별로 전체 재고수량과 인당 재고수량이 관리되어야 한다.
  2. 상품의 권종은 전체 재고량을 초과하여 판매되면 안된다.
  3. 판매가 한번 시작된 상품의 경우에는 재고량 수정이 가능하나 최초 설정된 재고량 이상을 설정할 수 없어야 한다.
  4. 상품권은 한 개씩 구매한다.

설계

  1. 전체 재고량의 경우 RDB에 저장하여 관리하고, 트랜잭션이 일어나는 재고사용량의 관리는 연산속도가 빠른 in-memory DB를 사용한다.
  2. 연산처리는 단일 스레드에서 처리하는 Redis를 이용하여 동시성 이슈를 해결한다.
  3. 레디스의 데이터 유실이 일어날 수 있으므로, 재고 사용량 데이터를 RDB에 싱크한다.
  4. 구매번호는 유니크한 값이고, Redis의 Set 자료구조는 중복을 허용하지 않기때문에 구매번호를 Set에 저장할 경우 SCARD 오퍼레이션을 통해 손쉽게 사용량을 가져올 수 있다.
  5. 재고량 증가 혹은 감소시점에 (삽입만 발생하는) 재고량 히스토리 정보를 누적한다.

구현

{상품번호}:{권종}:stock:{타입}

# 전체 재고 관리
S0630000RU:5000:stock:total

# 개인별 재고 관리
S0630000RU:5000:stock:{회원번호}
  • S0630000RU: 상품번호 (오늘도 수고했어 상품)
  • 5000: 권종 (5000원권)
  • stock: 재고 구분자
  • total: 전체 재고 / 201209320003: 회원번호
  • ADD 구매번호

재고 사용량 증가

구매 과정에 필요한 타 시스템의 API와 동기 방식으로 진행

  • 상품 시스템 플로우
    • 트랜잭션 시작
      • 구매가 가능한 상태인지 (왜 트랜잭션 열고 유효성 검증하지?)
        • 총 재고수량 & 인당 제한수량 확인
      • 가능한 경우 Redis에 구매번호 ADD
        • 총 재고수량 & 인당 재고 사용량 증가를 트랜잭션으로 묶음
      • 가능한 경우 RDB에 재고 히스토리 테이블 INSERT
    • 트랜잭션 커밋

⭐️ 이 플로우는 동기화된 메소드로 실행됨 (배민이지만 상품권 구매는 빈번하지 않다고 판단한듯)

  • AI한테 위 플로우에서 동시성 문제가 발생할 수 있는 포인트를 물어봄
    1. 재고를 확인하는 시점과 실제로 차감하는 시점 사이에 시간 차이가 존재하기 때문에, 두 요청이 거의 동시에 들어와서 같은 재고 상태를 확인한 후, 각각 재고를 차감하는 상황
      1. 조회와 업데이트가 분리된 두 개의 독립적인 Redis 명령어로 이루어짐
      2. Synchronized가 JVM 레벨에서만 동작하여 Redis 레벨의 원자성은 보장하지 못함
    2. Redis 업데이트는 성공했지만 RDB 트랜잭션이 실패하여 롤백되는 경우
      1. Redis의 MULTI/EXEC와 Spring의 @Transactional은 완전히 독립적인 트랜잭션 시스템이므로, RDB 예외 발생 시 Redis 트랜잭션은 롤백되지 않음
      2. EXEC를 실행하는 시점에 큐잉되어있던 명령어들 모두 실행
    3. 분산 환경에서 Synchronized가 의미가 없음

재고를 차감한 이후에 구매가 실패한 경우

  • 재고사용량을 차감시키라는 이벤트를 재고사용량 감소 큐에 발행
  • 트랜잭션 시작
    • 레디스에서 전체 재고, 인당 재고 사용량을 구매번호로 제거
    • 재고 히스토리 테이블에 재고량 감소
    • 메세지 ack 처리
  • 트랜잭션 커밋

생각할 점

  1. 재고 관리가 빡빡하지 않은 것 같음
    1. 상품권을 한 개씩 구매함
    2. 레디스 재고 사용량 변경에 대한 동시성 문제를 메소드 동기화로 해결했음
    3. 외부 서비스 연동을 동기로 처리함
  2. 만약
    1. 상품을 한 번에 여러 개 구매 가능하고
    2. 재고 변경에 대한 동시성 문제를 분산 환경에 대응해야 하고
    3. 외부 서비스 연동을 비동기로 처리해야 할만큼 요청이 많이 몰린다면?