ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ 이모저모 ] ERD 설계(1) - 회원 도메인
    Projects 2023. 12. 13. 02:31

     

    최근 새롭게 프로젝트를 시작했다.

    지그재그 서비스를 클론코딩한 프로젝트이고, 개인 프로젝트로 백엔드 API 쪽을 구현해보기로 했다. 

    이번 포스팅에서는 ERD 설계 관련해서 고민하고, 피드백을 받아 수정한 것들에 대해 정리하려 한다. 


     

    먼저 구현할 기능들을 User Story 형태로 정리해보면 다음과 같다. 

    • 회원은 카카오, 네이버 로그인을 통해 서비스에 회원 가입 및 로그인할 수 있다.
    • 회원은 언제든 서비스 탈퇴가 가능하다.
    • 회원은 4가지의 등급별로 매달 등급 쿠폰을 발급받을 수 있다.
    • 회원은 만료된 쿠폰, 소지하고 있는 쿠폰, 발급 가능한 쿠폰 목록을 확인할 수 있다.
    • 회원은 구매 확정한 주문에 포함한 상품들에 대해 확정 후 30일 이내로 리뷰를 작성할 수 있다. 
    • 회원은 작성한 리뷰를 수정할 수 있다.
    • 회원은 작성 가능한 리뷰, 작성한 리뷰, 작성 만료된 리뷰 목록을 확인할 수 있다.
    • 회원은 구매 확정한 주문에 대한 적립금을 받을 수 있으며, 적립금의 사용 기한은 1년이다.
    • 회원은 적립금에 대한 적립, 사용, 만료 내역을 조회할 수 있다. 
    • 회원은 상품을 찜하거나 삭제할 수 있다. (위시리스트)
    • 회원은 장바구니에 상품을 저장할 수 있다. 
    • 회원은 장바구니에서 선택한 상품들에 대해 주문을 진행할 수 있다.
    • 회원은 주문 시 장바구니와 쿠폰, 프로모션 등을 적용할 수 있다. 
    • 회원은 하나의 주문을 일괄로 취소할 수 있다. (부분취소 불가)
    • 회원, 비회원은 판매량에 따른 쇼핑몰 랭킹을 매일 확인할 수 있다.
    • 회원, 비회원은 프로모션 중인 쇼핑몰 목록을 확인할 수 있다.
    • 회원, 비회원은 하나의 쇼핑몰의 모든 상품 목록을 확인할 수 있다.
    • 회원, 비회원은 쇼핑몰 또는 상품 이름, 태그로 검색이 가능하다. 

     

    크게 도메인은 회원, 스토어, 주문 3가지 정도로 구분할 수 있다. 

     

    회원 도메인 : 회원, 리뷰, 위시, 즐겨찾기 관련 테이블

    스토어 도메인 : 프로모션, 쇼핑몰, 상품 관련 테이블

    주문 도메인 : 장바구니, 주문 관련 테이블 

     

     

    설계한 전체 ERD는 아래와 같다. 

    개발하면서 조금씩 바뀔수도 있겠지만, 일단은 아래처럼 확정하였다. 

     

     

     

    회원 도메인에서 고민했던 것들은 2가지 정도였다. 

     

    1. 적립금 관련해서 이력들을 다 보여줘야 하는데, 적립금 총액을 회원 테이블에 가지고 있어야 하는가? 

    2. 찜과 장바구니, 2가지를 합칠 수 있을까? 

     

     

     

     

     

     

    ✅ 회원 테이블에 적립금 총액 Column을 만들자. 

     

    • 처음에는 적립금 총액(reserve_sum) 칼럼을 회원 테이블에 만들어두지 않았다.
    • 정규화를 생각해서 분리해두고, 그때 그때 계산해야지라는 ... 생각을 했었다. 
    • 하지만, 아래와 같은 질문을 생각해보자. 
    한 회원이 매일매일 만 건 씩 주문하면 어떻게 될까요?
    유효기간이 1년이면, 몇 건이 저장될까요?
    그렇다면 데이터를 계산할 때 연산이 얼마가 들까요?  

     

    • 위 상황을 가정하면 360만건이다. 아주 극단적인 예시지만, 유저 여럿이 있다고 생각해보면 충분히 가능한 데이터 양이다.
    • 그럼 이 상황에서 특정 회원의 사용 가능한 적립금 총액을 계산하려면 어떻게 할까?

    GROUP BY 연산

    • 우선 데이터베이스 자체에 Native Query 날려서 status 별로 분리한 후 계산하여 가져오는 방식
    • 데이터 자체가 몇 백만건이므로 굉장히 느릴 것 
    • 데이터베이스 자체에 부하를 주는 것이므로 ! 다른 기능들도 영향을 받을 수 있다.

    Application 단에서 연산하기

    • userIdx로 가져온 360만건의 데이터를 그냥 어플리케이션 단으로 올려준다. 
    • 데이터베이스 자체에서 연산하진 않으므로 데이터베이스 쪽은 괜찮을지도 모른다.
    • 하지만 이 방식 또한 JVM 메모리를 생각해보면 불가능하다는 것을 알 수 있다. 
      • JVM의 Heap 사이즈는 전체 메모리의 1/64가 기본이고, 메모리의 1/4까지 늘어날 수 있다. 
        • Initial heap size of 1/64 of physical memory
        • Maximum heap size of 1/4 of physical memory (oracle docs)
      • 주로 운영환경에서는, JVM의 메모리 최소 최대 크기를 동일하게 맞춰둔다.
        • 유동적으로 조절하게 되면, 나중에 JVM이 추가 메모리 요청했을 때 Native 영역에서 못 준다고 하면? 
          • 바로 Out Of Memory 에러 내면서 프로그램이 죽는다. 
        • 이 사이즈만큼 메모리 확보해두고, 이 안에서 최대한 버티면서 GC로 메모리를 확보한다.
    • 위 2가지를 생각해보면, 제한된 Heap 내에서 GC로 최대한 버티면서 사용하는 것이 메모리 전략이다.
      • 그런데, 데이터 360만건을 메모리에서 처리한다? 
      • 이 데이터 자체가 커서 메모리를 많이 잡아먹으므로, GC가 빈번하게 일어난다. 
        • (360만건의 데이터들은 우리가 계산해야 할, reachable 객체이므로 GC 대상이 아니다. 따라서 다른 곳에서 메모리를 확보해야 한다.)
      • 최악의 경우, GC를 계속해도 메모리 확보가 되지 못해서 Out Of Memory 낼 위험도 있다. 

     

    🤔 그렇다면, 적립금 계산이 바뀔 때에도, 회원 정보가 바뀔 때에도
    같은 회원 테이블에 접근해야 하지 않나요? 

     

    이 경우에는 어플리케이션 단에서 좀 완화할 수 있는 방법이 있을 것 같다.

    • 트랜잭션 전파 - REQUIRES_NEW 를 사용 :  적립금 / 회원 정보 이 2가지 관심사를 분리할 수 있다.
    • 메서드 단에서 로직 분리 처리 등등...! 

     

      찜과 장바구니는 분리하자. 

    처음에 찜과 장바구니는 모두 쇼핑몰 + 상품 식별자로 굉장히 단순하게 생각하고 찜이냐 장바구니냐를 구분했었다.

    하지만 도메인 측면에서도 찜은 사용자와 더 가깝고, 장바구니는 주문 도메인과 더 가깝다. 

    또한 장바구니는 상품 옵션과 수량이라는 정보까지 가지고 있어야 하는데에 반해, 찜은 그냥 해당 상품 자체에 누른다. (지그재그 기준) 

     

    그렇기 때문에 2가지 테이블은 분리해야 한다. 

    어찌보면 당연한건데 처음에 둘이 비슷해서 그냥 합쳐두는게 편하지 않나? 라고 단순하게 생각했었다...;; 

     

     

Designed by Tistory.