[항해 플러스 5기 백엔드 4주차] Usecase 개발

 

이번 4주차는 3주차에 만든 문서를 기반으로 콘서트 예약 서비스에 주어진 요구사항 개발을 시작한다

 

구현할 API는 크게 다섯가지가 있다.

  • 유저 토큰 발급 API
  • 예약 가능 날짜 / 좌석 API
  • 좌석 예약 요청 API
  • 잔액 충전 / 조회 API
  • 결제 API

대기열을 하는 이유

대기열을 하는 이유는 무엇일까?

유량 제어를 하기 위함이다. 유량 제어를 하는 이유는 흐름을 제어하는 것을 의미한다.

즉, 트래픽이 몰릴 경우를 대비하여 이를 제어하고 디비의 부하를 막는것이 목적이다.

 

부하의 종류는 읽기 부하와 쓰기 부하가 있고, 부하의 대상은 CPU, 메모리, 디스크가 있다.

부하에 대해서도 생각을 해야 한다. 가령 메이플에서 몬스터를 때릴때마다 디비에 데이터를 삽입하면 디비의 쓰기 부하가 많을 것이다.

그렇다면 메모리에 저장하고, 10분마다 디비에 저장하면 디비의 쓰기 부하가 적을 것이다. 그런데 서버가 갑자기 죽어서 메모리에 있는게 날라가면 10분 동안 있던 데이터가 날라간다. 간혹 게임 장애 발생으로 인해 유저 데이터가 날라가서 보상해주는 이유가 이런 것일 수도 있다.

 

대기열은 디비의 스파이크성 쓰기 부하를 막기 위함이다.

 

대기열 관리 방식

대기열 관리 방식은 크게 두가지가 있다

은행창구식

항상 N명만 수행을 유지하고, 1명이 빠지면 1명이 들어간다. 만료된 사람이 있어야지 입장할 수 있다.

 

놀이공원식

주기적으로 N명씩 입장시킨다. 만료된 사람 유무 관계 없이 주기적으로 입장시킨다.

 

대기열 통과 후 최대 5분 동안만 사용할 수 있다고 정하고, 스케줄러 만료 검사 주기를 10분마다 한다고 해보자.

그럼 최악의 케이스에서 5분이 넘는 8분 9분마다 체류하고 아무것도 하지 않는 사용자가 있다면 대기열을 차지하고 있는 꼴이 된다.

스케줄러 주기를 줄이면 그만큼 만료 처리를 빠르게 할 수 있지만 디비에 부담이 될 수 있다.

이를 보완하기 위해 API 앞단에서 만료인지 체크하는 로직을 추가해도 괜찮다.

 

만료 방식은 위의 처럼 주기적으로 체크할 수도 있고 아니면 결제 완료 후에 만료 시키는 방식으로 처리할 수 있다. 은행 창구식 방식이면 이 방법이 어울릴 수 있다. 만약 놀이공원식 방식이라면 결제 완료 후 만료 처리를 해도 바로 입장하지 않기 때문에 주기적으로 시간 지나면 만료 처리를 하는것이 어울린다. 

 

캐시

이번 주차는 캐시를 사용하지 않고 RDBMS만을 사용한다. 실무였으면 다양한 기술을 사용하지만 이번 주차에서는 DB 역랑을 다루는 것과 캐시나 큐를 사용하지 못했을 때를 대비하여 'DB로도' 구현을 할줄 알아야 된다고 하셨다.

디비로만 대기열을 구현하면 트래픽이 많을 경우 부하가 걸릴 수 밖에 없다.

결국 문제는 디비를 많이 왔다 갔다 하는 것이기 때문에 캐시를 고려해야 한다.

 

그럼 캐시는 어떻게 활용할 수 있을까? 어떤 값을 캐시에 보관하고 얼만큼 보관할 건지 정해야 한다

API 마다 대기열 토큰을 받아 토큰의 상태값을 검증한다고 하자. 로직 전에 디비를 계속 찔러야 하는데 토큰의 상태값을 토큰의 만료 시간만큼 보관하면 된다. 

key: {대기열 토큰}, value:{status값}

디비를 찌르기 전에 캐시를 조회하고 캐시에 값이 있으면 캐시를 사용하고, 없으면 디비를 찌르는 식으로 처리해서 디비 부하를 줄일 수 있다. 만약 캐시의 TTL을 5분으로 저장했는데 사용자가 3분만에 토큰으로 할 수 있는 작업을 모두 끝마쳤으면 이때 캐시를 삭제시키면 된다.

 

스프링의 @Cacheable는 캐시 스탬피드 현상이 많이 발생한다고 한다.

캐시 스탬피드 현상이란 캐시 미스가 난 찰나의 순간, 요청이 몰려 디비에 읽기 요청이 집중되고, DB에서 읽은 데이터를 레디스에 쓰기 작업을 할려고 할때 DB에 과부하가 걸리는 현상을 말한다.인스턴스가 여러개라면 레디스에 중복 쓰기도 발생할 수 있다.

※ 캐시에서 데이터를 먼저 찾고 없으면 DB에서 찾기 -> DB에서 찾은 데이터를 캐시에 삽입하는 과정을 Look-Aside 방식이라고 한다.

 

API 개발

API 개발 자체는 어렵지 않았다. 하지만 요구사항을 어떻게 가져갈 것이고 그에 따른 고려할 케이스는 어떤 것들이 있을까를 고민하는데 애를 많이 먹었다. 가령 아래와 같다.

 

  • 좌석 임시 배정 만료를 체크하는 스케줄러가 도는데 해당 스케줄러가 죽으면?
  • 로직에서 현재 시간을 이용한다고 했을때 프론트에서 구한 값을 받아서 처리할껀지, 애플리케이션의 계층에서 구할껀지, 디비 단에서 구할건지.
  • 좌석 데이터는 모두 넣어둘껀지?
  • 대기열 토큰 테이블에 순서값 필드를 추가해야하나? 그런데 데이터가 많을수록 순서값 변경시 update 대상이 많아서 부하가 가지 않을까?
  • 유저의 대기 순번은 어떻게 구할 수 있을까?
  • 대기열 만료 시점을 결제요청을 한다음 만료시킬지, 결제가 모두 완료된 후 진행할지
  • 데이터 중복검사를 디비의 유니크 속성을 이용해서 구현할까?

 

 

생성한 문서는 ERD, 시퀀스 다이어그램, api 문서가 있다. 개발을 시작하자마자 불현듯 놓친 부분이 떠올라서 문서를 수정하면서 진행했다.

개발 하면서 문서를 수정하거나 엣지 케이스를 추가하는건 자연스러운 과정이다. 그래도 초반에 분석을 잘했으면 하는 아쉬움이 개인적으론 있다. 그래도 하다보면 늘겠지!!

 

코치님께 얻은 팁으로는 항상 극한으로 생각하는 것이다.

내가 고민한 부분은 "좌석 데이터를 미리 넣어야 할까?" 였다. 요구사항에서는 좌석이 50개만 존재했기에 넣어도 무방하겠지 라고 생각했다. 하지만 콘서트를 대규모 스타디움에서 하면 6만석의 좌석이 존재하는데 모든 데이터를 디비에 넣어줘야 할까? 만약 좌석이 1만개만 팔리면 5만개는 필요없는 데이터가 된다. 그럼 좌석 예약 요청이 올때마다 좌석 데이터를 생성한다고 한다 치자. 그럼 좌석마다 고유 번호가 있고 가격이 다를 수 있는데 데이터는 프론트에서 하드코딩으로 관리해야 할까? 아니면 백엔드서 인메모리로 관리해야 할까?

 

개발은 어떤걸 해결하면 다른 부분에서 문제가 생기기 때문에 트레이드 오프를 잘 해야 한다.

 

===

 

현재 항해 플러스 백엔드 코스 6기 모집중입니다.
항해플러스 관련해서 문의 주실 내용 있으시면 이메일 혹은 댓글로 편하게 문의주세요!!
지원시 추천 코드 입력 한번 부탁드립니다!!

 

추천코드
HHPGS0858

https://hanghae99.spartacodingclub.kr/plus/be