https://www.youtube.com/watch?v=704qQs6KoUk
배달의 민족 주문시스템: 특정 시간대(12시, 18시 30분)에 트래픽이 몰림
MSA 아키텍처로 이루어져 있음.
가게, 메뉴, 주문, 결제, 배달 등 수많은 시스템이 통신하고 있음
대용량 데이터 : 일 평균 300만 건의 데이터를 저장하고 수년간 저장
그에 따른 대규모 트랜잭션이 발생함 : 일 평균 300만 건, 점심 저녁에 트래픽이 더 몰린다.
여러 시스템과 연계로 이벤트 기반으로 통신하는 MSA로 구성되어 있다.
주문 시스템은 이벤트 기반으로 여러 시스템과 통신
기존의 아키텍처의 단점과 그 개선 방법
- 단일 장애 포인트 하나의 시스템 장애 -> 전체 시스템의 장애
- 중앙 집중 db의 장애-> 전체 시스템으로 전파
- 각 시스템별로 각각에 맞는 도메인을 설계하여 저장소를 따로 가져가게 됨, 각 시스템 한 게는 MQ를 통해 느슨한 통신 가져가게 됨
- 특정 시스템의 장애는 메시지 발행의 실패로 끝이 나고, 다른 영향을 받지 않는 시스템에는 영향이 가지 않음
- 영향이 가는 이벤트의 경우 이벤트 재발행시, 재소비를 하여 빠르게 서비스가 안정화됨
- 주요 기능 : MQ를 이용한 이벤트 기반 통신
- 대용량 데이터 rdbms 조인 연산으로 조회성능이 좋지 않음
- 4중 join을 사용하다 보니 오히려 성능이 떨어지게 되었다. 조회 성능을 올리기 위해 단일 테이블로 역 정규화를 진행
- mongodb(NoSQL)에 저장하여, id 기반으로 단순 조회가 가능하게 됨
- 주문 도메인은 생명주기에서만 도메인 변경이 발생
- CQRS 적용 아키텍처
- 커맨드 모델과 조회 모델을 분리, 조회 모델 역 정규화를 통해 조회 성능 개선
- 대규모 트랜잭션: 주문수 증가로 저장소의 쓰기 처리량 한계에 도달
- 주문 db 샤딩
- aws aurora는 샤딩 지원을 하지 않음
- 직접 샤딩을 구현하게 됨
- key based sharding
- shard key를 이용하여 데이터 소스를 결정
- 주문 번호를 hash function을 통해 변환하여 분배되도록 함
- 장점? 구현 간단함, 데이터 골고루 분배 가능
- 장비를 동적으로 추가, 제거할 때 데이터 재배치가 필요함
- Range based sharding
- 값의 범위 기반으로 데이터를 분산시키는 방식
- 일정 가격 이하는 0번 중간은 1번 고가는 2번
- 장점: 구현 간단함
- 단점: 데이터의 균등 분배가 불가능하여 성능저하가 발생할 수 있음
- directory based sharding
- 샤드가 어떤 데이터를 가질지 look up table을 유지하는 방식
- 중간 테이블로 어떤 샤드로 갈지 결정해 줌, 그리고 그것을 기록해 준다.
- 장점: 동적으로 샤드를 추가하는데 유리하다.
- 단점: Lookup table이 단일 장애 포인트가 될 수 있다.
- key based sharding
- 주문 시스템의 특징?
- 배달앱의 특성상, 주문이 정상 동작하지 않으면, 배민 서비스 자체의 신뢰도가 내려가게 됨
- 단일 장애 포인트는 최대한 피하자
- 동적 주문 데이터만 최대 30일만 저장함(배민 규정상)
- 샤드 추가 이후 30일이 지나면 데이터는 다시 균등하게 분배된다.
- 배달앱의 특성상, 주문이 정상 동작하지 않으면, 배민 서비스 자체의 신뢰도가 내려가게 됨
- 주문순번 % 샤드 수로 샤드번호를 결정하여 분배
- AOP와 AbstractRoutingDataSource를 이용하여 구현
- 이 부분은 유튭 23:12분 근처 보기
- 다건 조회 애그리게이트 로직: 어떻게 데이터를 조합하여 내려줄까? 몽고 db를 조회성 모델을 사용
- 주문 API, 주문 이터널 API를 분리해 둔 것이 큰 도움이 되었음 분산할 때
- 주문 db 샤딩
- 규칙 없는 이벤트 발행으로 서비스 복잡도가 높아짐
- 주문서비스에 집중된 관심도, 무분별한 이벤트가 발생하여 추적이 어려워짐
- 주문생성-주문접수-배달완료-주문취소 (도메인 로직)
- 알림 전송, 현금 영수증 발행, 분석 로그 전송 데이터 동기화 외부 이벤트 발행 (서비스 로직)
- 도메인 과점에선 아키텍처가 잘 분리되어 있음
- 하지만 시스템관점에선 아키텍처가 로직을 수행하는 주체를 파악하기 어려움,
- 서비스 주체에 대해서 수행하는 어플리케이션이 달라짐으로써 수행하기 어려워지는 문제점이 발생하게 됨.
- 기능 추가할 때도 서버 하나를 놓치게 되는 경우가 있음 -> 그에 따라 sqs event에서 유실이 발생할 경우, 재처리가 어려움
- 내부/외부 이벤트 정리
- 내부 이벤트에는 zero payload 사용
- 내부 이벤트에는 서비스 로직을 심기 어렵게 하기 위함.
- 데이터가 없다 보니 서비스를 수행하기에는 한계가 있게끔 하고 싶었음
- 이벤트 처리기가 저장소에서 즉시조회함
- 이벤트 처리 주체를 단일화하여 해결함
- 이벤트 발행 실패 유형
- 트랜잭션 내부/외부 이벤트 발행 실패
- 내부의 경우 도메인 로직 전체가 실패
- 외부의 경우, 서비스 로직 비수행( 재발행 방법이 없어 도메인로직과 서비스로직의 일관성 보장 불가능)
- 트랜잭션 아웃박스 패턴 사용
- 이벤트 발행 실패와 서비스 실패를 격리하여 재발행 수단을 보장함
- 이벤트 발행 실패 시 아웃박스 엔티티에 저장된 페이로드 재발행
- 주문서비스에 집중된 관심도, 무분별한 이벤트가 발생하여 추적이 어려워짐