우테캠 파일럿 프로젝트를 진행하며 동작하는 서비스를 만들었습니다.
이 파일럿 서비스가 성장했다는 가정하에 발생하는 문제를 파악해보겠습니다.
가상 시나리오 설정
누적 주문 100만 건과 팀원들과 벌고싶은 매출 월 매출 100억을 기준으로 계산해보았습니다.
210일 째 운영중인 든든킷... (가정)
누적 주문량 100만건
현재 유저 가입 수는 266000명
DAU 38000
예상 Peek time (5시~8시)
조회 TPS 100: 주문 TPS 2.5
실제로 저희가 만든 프로덕트의 문제를 위 시나리오를 기반으로 파악해보겠습니다.
모의 유저 파악
유저는 든든킷에 들어오면 어떤 행동을 할까?
실제 인터넷 기사의 지표와 팀원들의 모의 유저 데이터를 활용햐여 요청 데이터를 계산해보았습니다.
유저가 특정 요청을 할 확률
메인 조회 | 46.62% |
상품 상세 조회 | 46.62% |
카트 담기만 하고 주문 안하기 | 2.792% |
주문 전체 내역 조회 | 2.331% |
단건 상품 주문 | 0.233% |
물품 3개 담고 주문하기 | 1.165% |
어떤 상품을 조회하고 구매할까?
파레토 법칙에 영감을 받아
20%의 상품에 80%의 요청이 몰리도록 설정해보았습니다.
위를 기반으로 성능 개선하기
- 메인 페이지 캐싱하여 개선하기
- 주문내역 전체 조회 인덱싱을 활용한 개선
- 주문-결제 트랜잭션 분리로 개선하기
등을 진행했습니다.
실제로 모의 유저를 세팅했을 때 얼마나 버틸 수 있을까?
위에서 설명한 모의 유저 기능 확률과 파레토 법칙을 적용하여 groovy 스크립트를 작성해보았습니다
// 시나리오 스크립트 groovy
private void scenarioMove() {
int dice = random.nextInt(429)
grinder.logger.info("dice : " + dice)
switch (dice) {
case 1..200:
// 1. 메인 조회
메인_조회()
break
case 201..400:
// 2. 상품 상세 조회
상품_상세_조회(getWeightedRandomProductId())
break
case 401..412:
// 3. 단건 주문
카트_담기(getWeightedRandomProductId())
break
case 413..422:
// 4. 카트 담기
주문_전체_내역_조회()
break
case 423:
// 5. 카트 주문
단건_주문(getWeightedRandomProductId())
break
case 424..429:
// 6. 주문 전체 내역 조회
카트_주문(getWeightedRandomProductId(), getWeightedRandomProductId(), getWeightedRandomProductId())
break
}
}
스크립트 작성 기반으로 실제 환경과 비슷하게 테스트를 할 수 있기에 nGrinder을 선택했습니다.
Ec2 t4 small instance - vUser 24명 기준
Grafana를 확인하며 Load avarage / cpu core count < 1 기준으로 측정했습니다.
피크타임 기준 평균 240 TPS를 안정적으로 방어하는 것으로 확인할 수 있었습니다.