서론
새로운 프로젝트에 참여하게 되었다. 이번 프로젝트에서는 MSA에 대한 기본적인 이해를 바탕으로 DDD 기반의 대용량 처리 프로세스를 경험해보기로 하였는데, 그 중에서도 짧은 시간 동안 폭발적인 트래픽이 몰리는 특수한 도메인이기도 하고, 데이터 정합성 면에서의 문제 해결 경험을 다룰 수 있는 타임딜(Time Deal) 서비스를 주제로 선정하게 되었다.
타임딜 서비스의 핵심적인 문제는 다음과 같다.
- 트래픽 과부하: DB 커넥션 풀이 과부화되어 전체 서비스가 마비될 위험
- 동시성 이슈: 재고는 1개인데, 동시에 100명이 결제에 성공하는 '재고 초과 판매' 이슈
본론
현재 프로젝트에서는 타임딜 상품만 판매하는 것으로 하게 되었는데, 타임딜 동안 몰리는 대규모 동시 접속과 대기열을 안정적으로 구성하는데에 집중하기 위해 일반 상품은 고려하지 않는 것으로 하기로 하였다. (오픈마켓에서의 타임딜 이벤트를 생각하면 좋을 듯 하다.)
예를 들어, A 업체는 11번가, 쿠팡, G마켓 등의 오픈마켓에 각각 한정된 수량의 제품만을 제공하는 방식이고, 전체적인 재고 관리는 통합 관리 서비스를 따로 이용하고 있다는 것을 전제로 하였다.
그래서 이번 프로젝트의 핵심 문제 정의는 다음과 같다.
- 100개의 한정수량을 50%의 할인가격에 30분 동안 타임딜 이벤트를 진행한다
- 타임딜 이벤트 동안 10,000명의 동시 접속자가 발생한다.
- 이와 같은 상황에서 어떻게 서버의 과부화와 장애를 방지할 수 있을까?
아래는 타임딜 예시이다. 해당 사이트의 타임딜 이벤트 시간은 24시간 정도로 넉넉하나 만약 타임딜 시간이 오후 1시부터 1시 30분까지라면..? 서비스 규모가 클수록 꽤나 어마어마한 트래픽이 몰릴 것으로 예상된다.

서비스 경계 나누기
앞서 쓴 것과 같이 유연한 확장을 위해 MSA를 채택하기로 하였고, 서비스 간 결합도를 낮추기 위해 Kafka를 메시지 브로커로 도입하기로 하였다. 이후에 고민할 것으로 서비스의 경계를 어떻게 나누냐에 대해 팀원들과 이야기를 나누었는데, 서비스 컨텍스트를 나눠보는 것이 어떤지 제안하게 되었다.
그 이후에 다른 팀원분께서 DDD에서의 중요 개념인 Bounded Context를 나눌 때, 이벤트 스토밍을 통해 나누는 방법론을 소개하시면서 이번 프로젝트에서 도입해보면 좋을 것 같다고 하여 해당 방법론을 채택하기로 하였다.
이벤트 스토밍에 대해서는 다음 링크에서 자세히 알아볼 수 있다.
https://www.msaschool.io/operation/design/design-three/
msaschool - msaschool
*MSA School의 모든 콘텐츠에 대한 권리는 MSA School에 있으며, 무단 복제 및 배포를 금합니다. 영리 목적의 사용은 허용되지 않으며, 개인적 용도로 복제할 경우 반드시 출처를 표기해야 합니다. © uE
www.msaschool.io
이벤트 스토밍
타임딜 서비스는 일반적인 쇼핑몰의 주문 프로세스와 다른 양상이다. 여러 사용자가 몰리는 것을 대비해서 대기열이라는 것이 존재해야 하고, 재고에 대한 동시성 처리가 꽤나 중요하다고 한다.
처음에는 단순하게 TimeDeal, Product, Order, Stock 등으로 서비스를 나누면 된다고 생각했으나, 기획을 구체화할수록 모호한 지점이 생겨났다. 우리가 의문을 가지고 논의했던 것들은 다음과 같다.
- 대기열은 주문 서비스의 일부인가, 별도 서비스인가?
- 재고는 상품 서비스가 관리해야 하나, 타임딜 서비스가 관리해야 하나?
이러한 모호함을 해소하기 위해, 도메인별 이벤트를 중심으로 시스템을 시각화하는 이벤트 스토밍을 수행하였다.
도메인별 주요 이벤트 도출(Event)
가장 처음으로 도메인을 나누게 되었는데 먼저 Order, Queue, User, Product, TimeDeal로 나누게 되었다. 그리고 해당 도메인에서 일어나는 이벤트들을 포스트잇으로 붙여가며 흐름을 정리하였다.
시스템에서 발생하게 될 이벤트를 빨간색 포스트잇으로 나열하였다. 예를 들어, 주문 도메인에서의 이벤트는 아래와 같이 정의될 수 있다.
주문 영역: 주문 요청됨 -> 포인트 사용 요청됨 -> 주문 생성됨
커맨드와 정책(Command, Policy)
이벤트가 발생하기 위한 트리거(Command)와, 이벤트 발생 후 시스템이 수행해야 할 규칙(Policy)을 연결하였다. 내 담당인 대기열의 흐름을 정리하면서 트래픽 제어와 재고 관리의 복잡성을 알게 되었다. (그 전엔 그냥 주문 서비스 안터지게 대기열에서 주문 요청할 수 있도록 활성화시켜주는 인원만 생각했었다..)
예를 들어, 재고가 품절되었는데도 대기열에 있는 대기 인원들이 계속적으로 주문 서비스에 요청하게 된다면
- 주문 요청 시 유효한 대기열 토큰인지(ACTIVE) 검증하는 "주문 서비스"에서의 부하가 매우 클 것이다
- 그러므로 재고가 0이 되면 즉시 대기열 진입을 차단해야 한다
원래는 주문에서 재고를 확인하다가 품절되면 차단하도록 하기로 하였는데, 위의 1번과 같은 이유로 대기열 선에서도 재고 품절을 파악하면 대기를 끝내는걸로 하기로 하였다.

Bounded Context 도출
이벤트 스토밍을 통해 각 이벤트들의 연관성을 파악하여 응집도가 높은 기능끼리 묶어 다음과 같이 Bounded Context를 도출하였다.

핵심 의사결정
이번에 설계를 진행하면서 가장 많이 논의했던 부분은 상품 컨텍스트와 타임딜 컨텍스트의 물리적 분리였다. (이거 하나로도 꽤나 많은 시간 논의를 했었던걸로 기억..)
문제 상황은 다음과 같았다.
타임딜 상품만 서비스할 것인가? 일반 상품도 같이 서비스할 것인가?
일반적으로는 상품 테이블에 stock(재고) 컬럼이 존재하고, 기존에도 Product Option 테이블에 재고 컬럼을 위치시켰었다. (사이즈(옵션)별 재고를 관리하기 위해)
그러나 타임딜의 특성에 좀 더 집중하기 위해 일반 상품은 제외하고, 타임딜에서만 판매하는 한정수량의 재고만을 관리하도록 결정하였다.
(일단 내가 생각했었던 건 "자사의 여러 브랜드들을 모아둔 사이트에서 이뤄지는 타임딜 이벤트라면 일반 상품도 판매해야 한다." 였었는데 "오픈마켓에서 이뤄지는 타임딜 서비스"라면 타임딜에서 판매할 한정수량만 등록하게 되니 전체적인 재고는 상관없게 된다.) => 사실 이거 가지고도 각자 팀원끼리 생각하는 게 달랐었다..
그래서 다음과 같은 원칙을 세우게 되었다.
타임딜을 위해 소싱된 재고는 타임딜 서비스가 관리한다
Product 서비스 : 상품의 이름, 이미지, 옵션 등 변하지 않는 정보만 관리한다. (재고 관리 X)
TimeDeal Service: 타임딜 이벤트가 생성될 때, 판매할 수량을 입력받아 TimeDealStock이라는 독자적인 테이블에서 관리한다.
이로써 Product Service는 트래픽 폭주 상황에서도 단순 조회 역할만 수행하므로 부하가 적고, TimeDeal Service는 재고 차감이라는 핵심 로직에만 집중할 수 있게 되었다.
결론
DDD와 이벤트 스토밍을 통해 우리는 단순히 기능을 나열하는 것을 넘어, 각 서비스가 '무엇을', '왜' 관리해야 하는지 명확하게 이해하게 되었고, 기획/설계 부분에서 많은 것들을 확실히 구체화할 수 있었다.
기획/인프라 설계 회의하고 문서 정리하다보니 정신없어서 글이 매끄럽지 않다.. 다음부터는 가독성에 좀 더 신경을 써야겠다..
'Project' 카테고리의 다른 글
| [RUSH_CREW] 3. 대기열 Redis 데이터 구조 설계 (0) | 2025.12.03 |
|---|---|
| [RUSH_CREW] 2. DDD에서의 VO(Value Object) (0) | 2025.12.02 |
| [DELIVERY_SIGNAL] Circuit Breaker 구현(Resilience4j) (0) | 2025.11.21 |
| [DELIVERY_SIGNAL] 배송 담당자 순번 할당 설계/구현 (0) | 2025.11.07 |
| [DELIVERY_SIGNAL] DDD 계층 구조 적용 (0) | 2025.11.06 |