관련 Issue & PR
서비스의 조회 기능이 DB 기능에 의존해도 될까?
고민
순위를 포함한 랭킹을 얻으려고 할 때 DB 기능에 의존해도 될까?
비즈니스 로직이 너무 낮은 레벨까지 퍼진건 아닐까?
처음에는 전체 순위표가 필요했고, DB에 너무 의존하지 않고 만들고 싶었다.
그래서 게임 세션 정보는 가져온 도메인 객체에서, 순위 계산은 어플리케이션 레벨에서 구현했다.
하지만 문제가 많았다.
- 성능상 굉장히 비효율적이다
DB를 다 긁어서 메모리에 올려놓고 찾는다?
기록이 조금만 많아지거나 사용자가 조금만 많아져도 OOM의 위험이 있다.
- mysql
rank()
함수를 사용하면 간단한데, 이걸 어플레케이션 레벨에서 직접 구현할 필요가 있을까?
같은 점수들을 동일한 등수로 표현하려면 추가 구현이 필요하다. 이걸 효율적으로 만드려면 꽤 많은 노력이 들어가야할 것이다.
간단한 기능인데 너무 복잡해지고 리소스가 많이 든다.
첫번째 페이지는 어찌저찌 하더라도, 두번째 페이지부터는 첫번째 페이지도 있어야 순위를 알 수 있다.
더 큰 문제는 개인 순위가 필요했을 때였다.
개인 순위는 페이지로 가져오지 않고, 단건 조회다. 따라서 어플리케이션의 순위 계산 로직은 사용할 수 없다.
전체 세션들을 메모리에 올릴 수는 없기 때문이다.
결론
DB 기능을 적절히 조합해 줄 건 주고, 취할 건 취하는 것도 서비스 레이어의 역할이라는 생각이 들었다.
결국 랭킹 조회가 DB 레벨로 내려가고, mysql rank()
를 활용해 순위를 얻었다.
DB없이 단순 조회만으로 어플리케이션을 개발하기는 현실적으로 어렵다.
현재 상황에서는 랭킹을 다른 도메인에서 사용할 일도 없고, 단순 조회일 뿐이다.
이미 조회 단계에서 정확하고 효율적으로 처리할 수 있는 기능을 어플리케이션 레벨에서 다시 구현할 필요는 없다고 판단했다.
따라서 바꾸기 쉽도록 DTO를 통해 의존성 분리만 하고, JdbcTemplate
으로 직접 쿼리하도록 했다.
남은 고민
정확도나 의존성에 관련한 고민은 어느정도 결론을 내렸다.
하지만 성능상의 문제가 남아있다.
바로 게임 개수만큼 쿼리가 날아가는 것. 😅🤮
다음과 같은 코드 때문이다:
결과는 다음과 같다:
한 페이지 넘길 때마다 쿼리가 50개 발생한다?? 이건 사고다.
하지만 방법은 있다. 충분히 한방에 받아올 수 있긴 하다.
List<Long> sessionIds = top50Rankings.stream()
.map(RankingDto::getSessionId)
.collect(Collectors.toList());
Map<Long, AppleGame> sessions = appleGameSessions.findAllById(sessionIds).stream()
.collect(Collectors.toMap(AppleGame::getSessionId, appleGame -> appleGame));
// 세션들을 한방에 받아와 해싱해둔다.
sessions.get(1L);
sessions.get(2L);
sessions.get(3L);
sessions.get(4L);
하지만 서비스 로직이 복잡해진다🥲
어떻게 하면 성능을 잡으면서 로직도 명료하게 유지할 수 있을까?