포스트

온라인 해킹 대회 사이트 만들기 2 > 퀴즈 랭킹 시스템 개발

퀴즈 랭킹 시스템을 만들어야하는데, 고민에 빠졌다.

구현하고자 하는 시스템의 요구사항은 다음과 같다.

  1. 퀴즈의 점수는 퀴즈 정답자가 늘어날때 마다 바뀜
    1. 이에따라 퀴즈 점수에 대한 칼럼이 따로 없다.
      1. -> 점수를 그때 그때 동적으로 계산해야함
  2. 랭킹을 기준으로 reward가 제공되는 대회 플랫폼이기 때문에 최대한 real-time이어야 함.
  3. 퀴즈들의 데이터는 mysql을 통해 관리하고있음.
  4. 개발 당시까지 대회 참가 인원이 불확실하여 정확한 스케일 인아웃이 불가피한 상황

랭킹을 조회할때마다 sql쿼리로 점수 계산 및 순위 산정하는건 자체 DDOS API만드는 느낌이구… 그렇다고 캐시를 하기엔 요구사항들 때문에 에매했다….

(내가 아는 지식선에서) 생각해낸 최선의 선택은… 스케줄러 였다.
스케줄러를 사용하면 사용자가 랭킹 조회시에 쿼리 계산하는 로직을 매번 실행안해도 되기 때문이다. 그냥 특정 초마다 스케줄러가 계산해둔 랭킹을 그대로 읽기만 하면 되는것이다.
복잡한 쿼리가 아닌 단순 조회는 크게 과부화 될 가능성이 없다고 생각했다.

더 상세히 설명해보자면
랭킹이라는 것은 조회 요청을 한 시점이 같다면 똑같은 결과값을 보여줘야 한다.
스케쥴링 적용전에는 10번의 조회 요청이 한 시점에 들어온다면 쓸데없이 10n번의 명령어 실행되지만,
스케쥴링 적용후에는 10번의 조회 요청이 한 시점에 들어온다면 단순히 10번의 명령어만 실행하면 된다.
쿼리가 복잡할 수록 지연시간은 배로 늘어날 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class RankSchedule {  
    private final RankRepository rankRepository;  
    public static List<RankDto.AccountSolveProbList> accSolveList = new ArrayList<>();  
    private List<RankDto.ProbWithDynamicScore> prbSolveList = new ArrayList<>();  
    public static RankDto.EveryHourScore everyHourScoreRank = new RankDto.EveryHourScore();  
  
    public static final String RANK_HISTORY_FILENAME = "rankHistory.json";  
  
    @Scheduled(fixedDelay = 5000, initialDelay = 2000)  
    public synchronized void dynamicScorePollingTask() {  
        /*  
            설정한 인터벌 마다,  
            동적으로 문제들의 점수를 계산해 주는 스케쥴러  
         */        
        long bef = System.currentTimeMillis();  
        prbSolveList = rankRepository.findPrbSolve();  
  
        //점수 계산  
        prbSolveList.forEach(RankDto.ProbWithDynamicScore::setCalculatedScore);  
        prbSolveList.sort(Comparator.comparingLong(RankDto.ProbWithDynamicScore::getId));  
        accSolveList = rankRepository.findWhoSolveProb();  

....
.... (중략)
....
  
	accSolveList.sort(Comparator.comparing(RankDto.AccountSolveProbList::getScore, Comparator.reverseOrder())  
                .thenComparing(RankDto.AccountSolveProbList::getLastAuthTime, Comparator.naturalOrder()));  
        long mills = System.currentTimeMillis() - bef;  
        if (mills > 2000){  
            log.warn("Slow Query.... takes {} ms", mills);  
		}    
	}
}

스케줄링 적용후 코드

fixedDelay에 적당한 값을 주어서, 서버에 부하가 덜함과 동시에 랭킹이 실시간(realtime)을 반영 할 수 있도록 처리했다.
또한 스케줄링 결과값을 DB등이 아닌 단순히 static 변수에 저장하여 조회시에도 크게 부하되지 않겠끔 구성을 하였다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

Comments powered by Disqus.