1. 문제 이해와 설계범위 확정
질문
- 시스템 규모
- 대금 지불 시점 : 예약시
- 예약 인입 : 웹,앱으로만
- 다른 고려사항 : 10% 초과예약이 가능해야한다.
- 시간이 제한되어있으므로 검색은 제외합니다. (면접시 범위를 스스로 줄일수도있다!)
- 객실 가격은 유동적.
비기능 요구사항
- 높은수준의 동시성
- 적절한 지연시간 : 요청 처리에 응답시간이 적당히 늦어도 허용됨.
개략적 규모 추정
- 5,000개의 호텔, 100만개의 객실
- 평균 객실 70%가 사용중이고, 평균 투숙기간 3일
- 일 예약건수 $100만 * 0.7 / 3 = 약 24만$
- 초당 예약건수 = $24/ 10^5 = 3$ TPS
QPS
약 10% 사용자가 다음 단계로 진행한다고 가정.
- 객실 예약 페이지 3 QPS(TPS)
- 예약 상세 페이지 30 QPS
- 객실 상세페이지 300 QPS
2. 개략적 설계안 제시 및 동의 구하기
API 설계
- 신규 예약접수 API : POST
/v1/reservations
{ startDate : 'yyyy-mm-dd', endDate : 'yyyy-mm-dd', hotelId : 123, roomId : 123A, reservationId : 123 // 이중예약 방지, 멱등성이 보장되는 키 }
데이터 모델
서비스 특성
- 이벤트가 있는경우 트래픽 급증가능
- 평시 시스템 규모가 크지않음.
- R > CUD 10배? 크게 차이가 있는편이 아님.
RDB를 선택.
- 읽기빈도가 쓰기 연산에 비해 높은 작업 흐름을 잘 지원한다.
- NoSQL은 대체로 쓰기연산에 최적화되어있다.
- ACID(원자성/일관성/격리성/영속성)을 보장한다.
- 중복처리방지에 꼭 필요한 속성.
- 데이터 모델링과 호텔/객실 등 사이 관계를 나타내기 쉽다.
- 일반적인 예약에서 객실의 특정 호수를 예약하는것이 아닌, 객실 유형을 선택하므로 데이터 모델링시 고려되어야한다.
개략적 설계안
// 이미지 참고 p.238
3. 상세 설계
개선된 데이터 모델
- 호텔서비스
- hotel
- roomType
- room (roomTypeId, hotelId)
- 요금 서비스
- roomTypeRate : (hotelId, roomTypeId, date, rate) 날짜별 요금상태
- 투숙객 서비스 (회원)
- guest
- 예약 서비스
- roomTypeInventory :(hotelId, roomTypeId, date), total_inventory, total_reserved : 날짜별 수용가능한 객실수, 예약된 객실수
- 데이터가 너무 많아지면 cold, hot으로 분리 혹은 샤딩으로 해결가능.
- reservation
- roomTypeInventory :(hotelId, roomTypeId, date), total_inventory, total_reserved : 날짜별 수용가능한 객실수, 예약된 객실수
용량 추정
$5000개의 호텔 * 20개의 객실유형(가정) * inventory는 2년간 저장 * 365d = 7,300만개$ * DB 복제 서버
동시성 문제
- 예약 API 요청파라미터에 미리 발급받은 멱등key를 추가하여 중복 처리를 방지한다.
lock
- ACID의 Isolation 에 따라 각 트랜잭션은 상대 트랜잭션의 변경사항을 모른다. 동시 예약이 가능해질 수 있다. -> lock으로 해결
- 비관적 lock
- SELECT … FOR UPDATE -> mysql 에서 레코드 락 걸림
- 장점 :
- 구현이 쉽고, 모든 갱신 연산을 직렬화하여 충돌을 막는다.
- 데이터 경합이 심할때 유용
- 단점 :
- 여러 레코드에 락을 걸면 데드락이 발생가능.
- 트랜잭션의 수명이 길거나 많은 엔티티에 관련된 경우 db성능문제.
- 낙관적 lock
- 버전번호, 타임스탬프로 현재 번호보다 큰 버전이 오지않으면 트랜잭션 중단.
- 장점 :
- db자원에 락을 걸지 않으므로, db성능 문제 없음.
- 데이터에 대한 경쟁이 치열하지 않을때 적합.
- 단점
- 경쟁이 치열할때는 재시도가 여러번 걸리면서 응답시간이 느려질 수 있음.
- 데이터베이스 제약 조건
CONISTRAINT `check_room_count` CHECK((`total_inventory - total_reserved` >= 0))
- 데이터베이스 제약 조건
- 경쟁이 치열할때는 재시도가 여러번 걸리면서 응답시간이 느려질 수 있음.
- 장점
- 구현이 쉽다
- 단점
- 경쟁이 심하면 실패하는 요청 수가 많이 늘어나 사용자경험이 좋지않을 수 있다.
- 지원되지않는 db로 교체시 이슈.
시스템 규모 확장
- DB 샤딩
- 캐시
- 현재, 미래의 데이터만 중요하고 과거데이터는 중요하지않은 특성이 있다.
- TTL, LRU로 캐시최적화 가능.
캐시와 DB의 데이터 일관성
- DB CUD 시점에 캐시를 비동기로 업데이트.
- CDC
- 비동기 업데이트 사이의 데이터 불일치는 사용자 경험 영향을 고려해야함.
마이크로서비스 아키텍처에서의 데이터 일관성 문제에 대한 해결방안
- 마이크로아키텍처이므로, 각 서비스는 독립된 DB를 가져야한다.
- 2단계 커밋 (2PC)
- 모든 노드(서비스)의 원자적 트랜잭션 실행을 보증.
- Saga
- 각 노드의 트랜잭션을 하나로 묶어 실패하면 롤백 트랜잭션을 순차실행.
책에서 나오는것처럼 동시성 문제와, 마이크로 서비스라면 일관성문제가 가장 큰 이슈일듯.
- 이벤트성 트래픽
- 선착순 이벤트가 있는경우, 순서가 중요하므로 예약 앞에 큐를 도입?
- 아고다 같은곳은 예약 확정이 메일로 오는편. 호스트의 수동 확인도 있지만 비동기로 예약되어도 될듯.
- 동시성 문제
- 예약 선점하는 시스템이 하나 더 있어야할듯.
- TTL을 활용한 redis를 이용하여 멱등적 id 발급, 선점?
- 사례를 찾아보자
- 예약 선점하는 시스템이 하나 더 있어야할듯.