Skip to main content Link Menu Expand (external link) Document Search Copy Copied

QueryDsl를 이용한 쿼리 수와 실행시간 개선 여정기

Table of contents

  1. 개선하기로 한 이유
  2. 코드적 차이와 Querydsl을 이용한 개선
  3. 결과 분석
    1. 개선 전
    2. 개선 후
    3. 결론


개선하기로 한 이유


일단 결과물을 완성시켜야 한다는 생각이 앞서서 쿼리문을 잔뜩 남발했다.

‘쿼리문을 여러개 날려도 원하는 결과만 반환되면 되니까?!’ 라고 생각했었다. 🤔

먼저 문제점을 살펴보자.

내가 진행했던 SNS 프로젝트에서 알람을 조회하는 기능이 있었다.

image-20230522213330013

위 기능은, 내가 작성한 글에 댓글을 달거나 좋아요를 다른 사용자가 입력했을 때 목록을 가져와서 최신 알림 순으로 보여주는 기능이다.

그렇다면, 이 기능을 위해서 몇개의 쿼리를 날렸었을까?


코드적 차이와 Querydsl을 이용한 개선


    public Page<AlarmListDetailsDto> getDetailAlarms(String requestUserName, Pageable pageable) {
        //user 유효성 검사하고 찾아오기
        User requestUser = userRepository.findByUserName(userName)
                .orElseThrow(() -> new SNSAppException(ErrorCode.USERNAME_NOT_FOUND));

        Page<Alarm> alarms = alarmRepository.findByUser_IdOrderByCreatedAtDesc(requestUser.getId(), pageable);
        List<AlarmListDetailsDto> alarmsDto = new ArrayList<>();


        //post id와 user id가 아닌 post title 과 userName 을 넣기 위해..for Each 사용
        for (Alarm alarm : alarms) {
            Long fromUserId = alarm.getFromUserId();
            Long postId = alarm.getTargetId();

            String fromUserName = userRepository.findById(fromUserId)
                    .orElseThrow(() -> new SNSAppException(ErrorCode.USERNAME_NOT_FOUND)).getUserName();

            String title =  postRepository.findById(postId)
                    .orElseThrow(() -> new SNSAppException(ErrorCode.POST_NOT_FOUND)).getTitle();

            alarmsDto.add(new AlarmListDetailsDto(alarm, fromUserName, title));

        }

        log.info("🔔알림 조회 끝 userName : {}");

        return new PageImpl<>(alarmsDto);
    }

원래 로직은 위와 같았다.

먼저, 알람 조회를 요청한 ①사용자를 DB에서 조회하고, 그 사용자의 기본키에 해당하는 ②알람을 모두 조회한 후

그 알람의 갯수만큼 for문을 돌려서 알람 엔티티가 갖고있는 알람을 발생시킨 사용자의 id와 게시글 id를 이용해서

③알람을 발생시킨 DB에서 사용자를 조회하고 ④알람이 발생한 게시글을 DB에서 조회해서 dto를 구성했다..

단순히 코드로만 봐도 최소 4번의 쿼리를 날리는데,

사실 사용자의 수와 게시글의 수가 늘어나면 쿼리문이 2개씩 더 늘어나는 엄청난 나비효과가 있었다..

image-20230225211848955

알람을 발생시킨 회원이 한명 늘자, 쿼리문이 하나 증가했다.

만약 게시글이 증가하면, 또 쿼리문의 수가 계속 늘어날 것이다..

같은 조건에서 QueryDsl로 개선한 로직을 적용하니

image-20230225212215767

놀랍게도 한개의 쿼리문으로 같은 반환값을 얻어낼 수 있었다.

import com.growith.domain.alarm.dto.AlarmGetListResponse;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;

import java.util.List;

import static com.growith.domain.alarm.QAlarm.*;
import static com.growith.domain.post.QPost.*;
import static com.growith.domain.user.QUser.*;
import static com.querydsl.core.group.GroupBy.groupBy;

@RequiredArgsConstructor
public class AlarmCustomRepositoryImpl implements AlarmCustomRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<AlarmGetListResponse> getAlarms(Long userId) {
        List<AlarmGetListResponse> result = jpaQueryFactory.from(alarm)
                .where(alarm.user.id.eq(userId))
                .join(user).on(user.id.eq(alarm.fromUserId))
                .join(post).on(post.id.eq(alarm.targetId))
                .orderBy(alarm.createdDate.desc())
                .transform(groupBy(alarm.id).list(
                        Projections.constructor(AlarmGetListResponse.class,
                                alarm, user.nickName, post.title)));
        return result;
    }
}

작성한 QueryDsl 로직은 위와 같다.

먼저, 알람 조회를 요청한 사용자의 id(기본키)로 alarm 데이터를 모두 가져온 뒤, 알람 데이터가 갖고 있는 알람을 발생시킨 id와 user 테이블을 조인했고

그 다음 알람 데이터가 갖고 있는 알람이 발생된 게시글 id와 post 테이블을 조인한 뒤

orderby 를 사용해서 최신순으로 정렬하고

transform() 메서드와 Projections.constructor()를 사용해서 바로 DTO로 맵핑하였다.

위와 같이 쿼리문을 구성하니 1개의 쿼리문으로 원하는 결과를 얻을 수 있었다.


결과 분석


개선 전


image-20230225221358973

반환 결과는 위와 같다.

image-20230225221155928

쿼리문의 총 갯수는 5개이고 총 실행 시간은 39+39+39+40+41ms = 198 ms


개선 후


image-20230225221534112

반환 결과는 당연히 동일하다.


image-20230225221609187

쿼리문의 총 갯수는 2개이고 총 실행 시간은 37+38ms = 75ms이다.


결론


알람을 발생시킨 유저 or 알람이 발생된 게시글의 수가 늘어나면

개선 전의 경우 쿼리수는 더 늘어날 것이고 총 쿼리 실행시간도 계속 늘어날 것이다.

실제로 다른 사용자가 좋아요를 한번 더 눌렀을 때, 개선 전의 경우 쿼리문 하나가 추가되었고 39msec 가 추가되었다.


하지만, 개선 후의 상황의 경우

image-20230225222331826

알람 조회 쿼리문의 실행 시간이 38ms 에서 40ms 로 아주 약간의 시간만 추가되었다.


참고 블로그 : https://kobumddaring.tistory.com/59