Project

[s-market] ERD 설계 개선하기

sangyunpark99 2025. 3. 25. 10:53

 

ERD 설계를 제대로 해볼까..?

 

 

이번글은 e-commerce 프로젝트를 진행하기 위해 설계한 ERD를 수정하는 글입니다.

수정전 ERD

 

 

수정후 ERD

 

 

공통 수정 사항

1. 외래키를 제거합니다. (식별 관계에서 비식별 관계로 변경합니다.)

 

성능 이슈

  • FK는 무결성을 유지하기 위해 항상 대상 테이블을 조회해서 체크해야 합니다.
  • 대량의 INSERT/DELETE/UPDATE가 발생할 때 성능이 떨어질 수 있습니다.  만약 100만 건을 insert하면 100만 번 회원 테이블을 조회해야 합니다.

배포/삭제 시 제약

  • 부모 테이블 데이터를 삭제하려고 하면 FK로 인해 막히거나, 자식 테이블도 같이 삭제해야 하는 복잡한 작업이 생깁니다.

서비스 구조가 MSA인 경우

  • 서로 다른 DB를 사용하면, 물리적인 FK 연결이 불가능합니다.
  • 논리적으로만 연결해두고, 애플리케이션 단에서 무결성을 보장합니다.

논리 설계에서는 FK 관계를 표현하지만, 실제 물리 설계시에는 FK 제약 조건은 생략합니다.

데이터 무결성은 주로 서비스 로직이나 트랜잭션 처리로 보장합니다.

 

2. null이 허용되는 필드와 허용되지 않는 필드를 분리합니다.

 

 

 

회원 테이블(users)

1. email 컬럼에 unique 제약 조건을 걸어, 회원 가입 시 중복 가입을 방지합니다.

2. 회원 배송 정보에 대한 내용을 작성합니다.

3. 회원 권한(user_role)을 회원 타입(user_type)으로 변경합니다.

4. 전화 번호 필드를 추가합니다.

5.  회원 가입 유형이 2가지 이므로 가입 유형 필드를 추가합니다.

6. 회원 이름 필드를 추가합니다.

 

주문 테이블(orders)

 

1. 주문 ID외에 주문 번호를 주문 날짜 + 랜덤한 숫자 형식으로 추가합니다.

쿠팡에서도 실제로 주문 번호를 긴 숫자로 지정합니다.

 

고객 응대 시, 주문번호가 더 직관적입니다. 고객이 "주문번호가 뭐냐", "언제 주문했냐" 물어볼 때,
주문번호에 날짜 정보가 들어 있으면 바로 파악 가능하고, 데이터를 직접 조회하지 않아도 대략적으로 언제 주문한 건지 알 수 있어서 응대가 쉽습니다. 즉, 고객이 말한 주문번호만 보고도 언제 주문했는지 금방 파악하고, 관련 데이터를 빠르게 찾을 수 있습니다.

 

주문 상품 테이블(order_items)

1. 상품명이 변경 되더라도 주문 당시의 상품명을 유지합니다.

 

주문 상세에 상품명을 그대로 유지시키는 이유는, 고객이 주문했던 시점의 정보를 정확하게 남기기 위해서입니다.

상품명이 바뀌는 경우, 고객이 기억하는 상품과 주문 내역이 달라 보이기 때문에, 혼란이 생기게 됩니다.

 

2. 상품당 할인된 가격, 원래 가격, 최종 결제 가격 필드를 추가합니다.

 

3. 주문 상품 부분 취소를 위한 status 필드를 추가합니다.

 

상품 사진 테이블(product_images)

불필요한 테이블이므로 제거합니다.

 

 

상품 테이블(product_images)

사용자에게 노출여부를 판단하기 위해서 노출 여부 필드를 추가합니다.

 

배송 테이블(user_address)

회원 배송 정보를 따로 추가합니다.

회원 1명당 여러 배송지를 저장이 가능하기도 하고, 회원 테이블이 복잡해지는 것을 방지하기 위해서 회원 테이블과 배송지 테이블을 분리합니다.

 

주문 배송 테이블(orders_shipping)

 

주문 배송 테이블을 생성합니다.

회원이 나중에 주소를 바꿔도 과거 주문 내역은 그대로 유지되도록 하기 위해, 주문이 생성될 때, 그 시점의 배송 정보를 복사해서 저장합니다.

 

 

장바구니 테이블(cart)

상품명이 변경 되더라도 장바구니에 담을 당시의 상품 정보를 유지합니다.

 

 

카테고리 테이블(categories)

여러 계층의 카테고리를 구현하기 위해서 자기 참조를 사용합니다.

 

 

상품 할인 테이블(product_discounts)

상품 할인에 대한 정보를 담을 테이블을 생성합니다.

 


재고 테이블(product_discounts)

상품의 재고를 관리하는 테이블을 따로 분리합니다.

가격, 설명은 잘 안 바뀌지만 재고는 주문/취소/반품 등으로 자주 변경됩니다.

상품은 조회 중심, 재고는 쓰기(변경) 중심 데이터입니다. 또한 재고는 동시성 이슈가 크므로 트랜잭션/락 관리가 별도로 필요합니다.

 

재고는 한곳에서 관리하는 것을 가정하기 때문에, 일대일 구조를 가집니다.

 

 

재고 로그 테이블(product_stock)

상품 재고 변경 이력을 관리하는 테이블을 생성합니다.

상품 재고 변경 이력을 남기는 것은 단순히 데이터를 기록하는 차원을 넘어서, 운영/장애 대응/회계/데이터 분석 등에서 핵심 역할을 하게 됩니다.

 

상품 재고 변경 이력은 후차감 선택

주문 정보를 먼저 저장해두고, 결제가 완료된 후에 재고를 차감하는 방식으로 진행

재고 차감 시점 주문 요청 시점 결제 성공 후
장점 오버셀 방지, 확정성 ↑ UX 부드럽고, 실패 처리 간단
단점 결제 실패 시 복원 필요 재고 초과 결제 위험, 동시성 처리 복잡
용도 이벤트, 한정 수량, 인기 상품 등 일반적인 쇼핑몰, 결제 실패율 높을 때

 

결제 테이블(payments)

결제 수단, PG사 거래 ID, 결제 승인 시각 필드를 추가합니다.

 

결제 수단 : 고객이 어떠한 방식으로 결제했는지 확인할 수 있어야 합니다.

PG사 거래 ID : PG사와 연동하거나 조회할때 꼭 필요합니다. (환불 요청, 거래 내역 확인 등)

결제 요청 시간 : 언제 결제가 요청되는지 기록해서 결제 흐름을 파악하고, 요청 - 승인 시간 측정도 가능합니다.

또한 일정 시간 내 미결제 자동 취소 등에도 사용됩니다.

 

 

결제 로그 테이블(payment_log)

결제와 관련된 로그 테이블을 생성합니다.

 

환불 처리를 할때, 로그를 사용해서  추적합니다.

상태 값 : PENDING, SUCCESS, CANCELED, REFUNDED

 

 

회원 로그 테이블(user_log)

회원 정보와 관련된 로그 테이블을 생성합니다.

 

회원 필드값의 변경 이력을 따로 추적합니다.