현재 어드민은 MyBatis + JPA를 함께 사용 중이다. 통계성 쿼리 때문에 MyBatis를 활용하고 있다.
어라라? 앱이 터진다. 지금 트래픽도 거의 안나오는데.. 터질리가 없는데?
팀 전체가 달라 붙어 찾아보니 슬로우 쿼리가 문제였다. 그것도 어드민의 유저 관련 통계성 쿼리!
쿼리를 보니 체감상 거의 모든 테이블을 다 조인한 것 같다. 조인 뿐만 아니라 select 절에 들어가있는 서브쿼리는 루프 처럼 동작하여 성능에 부담을 주고 있었다.
잘못된 쿼리로 인해 10초 이상 걸리는, 거의 재앙에 가까운 성능을 보였다.
나의 전략은 이러했다.
1. 유저와 1:N 관계를 맺고 있는 결제 테이블을 조인에서 제거했다.(심지어 서브쿼리로 조인하고 있었다.) 이 후, 다시 결제 내역을 조회하여 비즈니스 로직에서 유저 객체에 결제 정보를 추가해주었다.
private void aggregatePaymentSummaries(List<InfluencerUserDto> result, List<Long> userIds) {
if (CollectionUtils.isNotEmpty(userIds)) {
List<PaymentSimpleSummaryDto> paymentSimpleSummaries = paymentMapper.selectPaymentSummariesByUserIds(userIds);
Map<Long, PaymentSimpleSummaryDto> summaries = paymentSimpleSummaries.stream()
.collect(Collectors.toMap(PaymentSimpleSummaryDto::getUserId, Function.identity()));
result.stream().filter(user -> summaries.containsKey(user.getUserId()))
.forEach(user -> user.addPaymentSummary(summaries.get(user.getUserId())));
}
}
private void aggregateMileageSummaries(List<InfluencerUserDto> result, List<Long> userIds) {
if (CollectionUtils.isNotEmpty(userIds)) {
List<MileageSimpleSummaryDto> mileageSimpleSummaryDtos = mileageLogMapper.selectMileageSummariesByUserIds(userIds);
Map<Long, MileageSimpleSummaryDto> summaries = mileageSimpleSummaryDtos.stream()
.collect(Collectors.toMap(MileageSimpleSummaryDto::getUserId, Function.identity()));
result.stream()
.filter(user -> summaries.containsKey(it.getUserId()))
.forEach(user -> user.addMileageSummary(summaries.get(user.getUserId())));
}
}
이와는 별도로 쿼리상에 비즈니스 로직이 들어간 것도 모두 소스에서 처리할 수 있게 변경하였다.
예를 들면
해당 작업을 통해 어드민의 통계 쿼리 조회가 빨라졌다. 체감상 클릭하면 딜레이 없이 바로 뜬다. 또한 쿼리가 깔끔해져 가독성이 좋아졌다. 한 방 쿼리의 욕심보다는 쿼리를 적절하게 나눠서 비즈니스에서 처리하는 방법도 있다는 것을 배울 수 있었다.
+ 스프링 캐시를 사용하여 DB의 부하를 줄이고자 했다.