목표

성능 측정 (AS-IS)

테스트 데이터 삽입

기존 QueryDslRepository

@Repository
@RequiredArgsConstructor
public class DiscussionQueryRepositoryImpl implements DiscussionQueryRepository {

	private final JPAQueryFactory jpaQueryFactory;

	public Page<DiscussionQueryResult> findAllByProblemId(Long problemId, String sortBy, Long currentUserId, Pageable pageable) {

		QUser user = discussion.user;

		JPQLQuery<Long> upvoteCount = getVoteCount(VoteType.UP);
		JPQLQuery<Long> downvoteCount = getVoteCount(VoteType.DOWN);

		NumberOperation<Long> bestScore = Expressions.numberOperation(
			Long.class,
			Ops.SUB,
			upvoteCount,
			downvoteCount
		);

		JPQLQuery<Long> replyCount = JPAExpressions
			.select(reply.count())
			.from(reply)
			.where(reply.discussion.eq(discussion));

		Expression<VoteType> userVoteType = getUserVoteTypeExpression(currentUserId);

		List<DiscussionQueryResult> results = jpaQueryFactory
			.select(new QDiscussionQueryResult(
				discussion.id,
				Projections.constructor(SimpleUserInfoResponse.class,
					user.id,
					user.nickname,
					user.tier,
					user.profileImageUrl
				),
				discussion.problem.id,
				discussion.content,
				discussion.createdAt,
				upvoteCount,
				downvoteCount,
				replyCount,
				userVoteType
			))
			.from(discussion)
			.where(discussion.problem.id.eq(problemId))
			.orderBy(getOrderSpecifier(sortBy, bestScore, upvoteCount))
			.offset(pageable.getOffset())
			.limit(pageable.getPageSize())
			.fetch();

		JPAQuery<Long> countQuery = jpaQueryFactory
			.select(discussion.count())
			.from(discussion)
			.where(discussion.problem.id.eq(problemId));

		return PageableExecutionUtils.getPage(results, pageable, countQuery::fetchOne);
	}

	private JPQLQuery<Long> getVoteCount(VoteType voteType) {

		return JPAExpressions
			.select(discussionVote.count())
			.from(discussionVote)
			.where(discussionVote.discussion.eq(discussion)
				.and(discussionVote.voteType.eq(voteType)));
	}

	private OrderSpecifier<?>[] getOrderSpecifier(String sort, NumberExpression<Long> bestScore, Expression<Long> upvoteCount) {
		OrderSpecifier<?> primarySort;

		switch (sort.toLowerCase()) {
			case "best":
				primarySort = new OrderSpecifier<>(Order.DESC, bestScore);
				break;
			case "upvote":
				primarySort = new OrderSpecifier<>(Order.DESC, upvoteCount);
				break;
			default:
				return new OrderSpecifier<?>[]{ new OrderSpecifier<>(Order.DESC, discussion.id) };
		}

		OrderSpecifier<?> secondarySort = new OrderSpecifier<>(Order.DESC, discussion.id);

		return new OrderSpecifier<?>[] { primarySort, secondarySort };
	}

	private Expression<VoteType> getUserVoteTypeExpression(Long currentUserId) {
		if (currentUserId == null) {
			return Expressions.nullExpression(VoteType.class);
		}
		QDiscussionVote discussionVoteForUser = new QDiscussionVote("discussionVoteForUser");
		return JPAExpressions
			.select(discussionVoteForUser.voteType)
			.from(discussionVoteForUser)
			.where(discussionVoteForUser.discussion.eq(discussion)
				.and(discussionVoteForUser.voter.id.eq(currentUserId)));
	}
}

SQL 로그 및 실행 계획

Hibernate: 
    select
        d1_0.id,
        d1_0.user_id,
        u1_0.nickname,
        u1_0.tier,
        u1_0.profile_image_url,
        d1_0.problem_id,
        d1_0.content,
        d1_0.created_at,
        (select
            count(dv1_0.id) 
        from
            discussion_vote dv1_0 
        where
            dv1_0.discussion_id=d1_0.id 
            and dv1_0.vote_type=?),
        (select
            count(dv2_0.id) 
        from
            discussion_vote dv2_0 
        where
            dv2_0.discussion_id=d1_0.id 
            and dv2_0.vote_type=?),
        (select
            count(r1_0.id) 
        from
            reply r1_0 
        where
            r1_0.discussion_id=d1_0.id),
        (select
            dv3_0.vote_type 
        from
            discussion_vote dv3_0 
        where
            dv3_0.discussion_id=d1_0.id 
            and dv3_0.voter_id=?) 
    from
        discussion d1_0 
    join
        users u1_0 
            on u1_0.id=d1_0.user_id 
    where
        d1_0.problem_id=? 
    order by
        ((select
            count(dv4_0.id) 
        from
            discussion_vote dv4_0 
        where
            dv4_0.discussion_id=d1_0.id 
            and dv4_0.vote_type=?)-(select
            count(dv5_0.id) 
        from
            discussion_vote dv5_0 
        where
            dv5_0.discussion_id=d1_0.id 
            and dv5_0.vote_type=?)) desc,
        d1_0.id desc 
    limit
        ?, ?
Hibernate: 
    select
        count(d1_0.id) 
    from
        discussion d1_0 
    where
        d1_0.problem_id=?

image.png

원인 분석 및 코드 최적화

1차 개선 : JOIN + GROUP BY

실행 계획 분석