성능테스트

ngrinder entity 조회방식 vs dto 조회방식 성능테스트 결과

MIN우 2023. 10. 17. 12:43
728x90

오늘은 다중조인이 들어가지않은 단순 paging 처리가 되어있는코드에 성능테스트를 해보겠습니다.

ngrinder 의 구성부터 보여드리겠습니다!

 

1개의 초당 50번의 요청을 하였고 약 1분간 진행하였습니다.

 @Override
    public Page<Board> getBoardList(BoardSearchCondition condition, Pageable pageable) {

        String[] word = (condition.getActivityField()!=null) ? condition.getActivityField().split("/") : null;
        BooleanExpression activityFieldExpression = (word != null) ? activityFieldContains(word) : null;


        JPAQuery<Board> contentQuery = selectFrom(board)
                .where(categoryEq(condition.getCategory()),recruitmentPeriodPredicate(),activityFieldExpression)
                .orderBy(getOrderSpecifier(pageable.getSort()).stream().toArray(OrderSpecifier[]::new))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize());


        JPAQuery<Long> countQuery = select(board.count())
                .from(board)
                .where(categoryEq(condition.getCategory()),recruitmentPeriodPredicate(),activityFieldExpression);

        return PageableExecutionUtils.getPage(contentQuery.fetch(),pageable,()->countQuery.fetchCount());
    }
 /**
     * 진행 , 마감 중 어떤 값이 들어오든 동적으로 검색해주는
     */

    private BooleanExpression boardStateEq(List<BoardStateEnum> boardStates) {
        if (boardStates == null || boardStates.isEmpty()) {
            return null;
        }
        return boardStates.stream()
                .map(state -> {
                    if (state == BoardStateEnum.ONGOING) {
                        return recruitmentPeriodPredicate();
                    }
                    if (state == BoardStateEnum.CLOSED) {
                        return closedRecruitmentPredicate();
                    }
                    return null;
                })
                .filter(Objects::nonNull)
                .reduce(BooleanExpression::or)
                .orElse(null);
    }

    /**
     *  진행 중 활동
     */

    private BooleanExpression recruitmentPeriodPredicate() {
        StringExpression recruitmentPeriodEndDate = Expressions.stringTemplate("STR_TO_DATE(SUBSTRING_INDEX({0}, '~', -1), '%Y.%m.%d')", board.recruitmentPeriod);
        return recruitmentPeriodEndDate.goe(String.valueOf(LocalDate.now()));
    }

    /**
     * 마감 된 활동
     */
    private BooleanExpression closedRecruitmentPredicate() {
        StringExpression recruitmentPeriodEndDate = Expressions.stringTemplate("STR_TO_DATE(SUBSTRING_INDEX({0}, '~', -1), '%Y.%m.%d')", board.recruitmentPeriod);
        return recruitmentPeriodEndDate.lt(String.valueOf(LocalDate.now()));
    }

    /**
     * 동적 orderby
     */

    private List<OrderSpecifier> getOrderSpecifier(Sort sort){
        List<OrderSpecifier> orders=new ArrayList<>();
        
        sort.stream().forEach(order->{
            Order direction = order.isAscending() ? Order.ASC : Order.DESC;
            String property = order.getProperty();
            PathBuilder orderByExpression = new PathBuilder(Board.class, "board");
            orders.add(new OrderSpecifier(direction,orderByExpression.get(property)));

        });
        return orders;
    }

    /**
     * 카테고리 동적 검색
     */
    private BooleanExpression categoryEq(String category) {
        return hasText(category) ? board.category.eq(category) : null;
    }


    /**
     * 활동영역 동적처리
     */
    private BooleanExpression activityFieldContains(String[] activityFields) {
        BooleanExpression result = null;
        for (String field : activityFields) {
            if (hasText(field)) {
                BooleanExpression fieldExpression = board.activityField.like("%" + field + "%");
                // or 을 처리하여 split 한 word 가 포함되어 있는지 확인해서 넣어줌
                // 예 문학/시나리오  (board.activityField.like("%문학%").or(board.activityField.like("%시나리오%")))
                result = (result == null) ? fieldExpression : result.or(fieldExpression);
            }
        }
        return result;
    }


    /**
     * boardId 동적검색
     */
    private BooleanExpression boardIdEq(Long boardId) {
        return hasText(String.valueOf(boardId)) ? board.id.eq(boardId) : null;
    }

    /**
     * 검색어 단어를 통한 동적검색
     */
    private BooleanExpression searchWordExpression(String searchWord) {

        return Optional.ofNullable(searchWord) //seachWord가 null이 아닌경우에 Optional로 감싸기
                .filter(word->!word.isEmpty()) // searchWord가 비어 있지 않은경우에만 map 함수
                .map(word-> Stream.of(board.activityName.containsIgnoreCase(word),
                                      board.activityDetail.containsIgnoreCase(word),
                                      board.activityField.containsIgnoreCase(word))
                        .reduce(BooleanExpression::or) // 위 조건들을 OR 연산으로 묶음
                        .orElse(null))
                .orElse(null);//  // 만약 조건이 없으면 null 반환

    }

이처럼 동적쿼리 및 페이징쿼리가 있는 board 를 entity조회방식으로 불러왔을 때 성능테스트 실시하였습니다.

 

t2.micro 기준으로해서 TPS 약 27.4가 나왔습니다 아무리 프리티어래도 너무나도 낮은수치같았습니다. 그래서 dto조회방식으로 변경해보기로 했습니다. dto 에 querydsl의 queryproject어노테이션을 붙이고 dto조회방식으로 조회했습니다.

@Override
    public Page<BoardResponseDto.BoardSimpleListResponseDto> getBoardList(BoardSearchCondition condition, Pageable pageable) {

        String[] word = (condition.getActivityField()!=null) ? condition.getActivityField().split("/") : null;
        BooleanExpression activityFieldExpression = (word != null) ? activityFieldContains(word) : null;


        JPAQuery<BoardResponseDto.BoardSimpleListResponseDto> contentQuery = new JPAQueryFactory(getEntityManager())
                .select(new QBoardResponseDto_BoardSimpleListResponseDto(
                        board.id,
                        board.activityName,
                        board.activityImg,
                        board.category,
                        board.view,
                        board.likeCount,
                        board.recruitmentPeriod,
                        Expressions.numberTemplate(
                                Integer.class,
                                "DATEDIFF(STR_TO_DATE(SUBSTRING_INDEX({0}, ' ~ ', -1), '%y.%m.%d'), CURRENT_DATE)",
                                board.recruitmentPeriod
                        ),
                        board.comments.size()
                ))
                .from(board)
                .where(categoryEq(condition.getCategory()), recruitmentPeriodPredicate(), activityFieldExpression)
                .orderBy(getOrderSpecifier(pageable.getSort()).stream().toArray(OrderSpecifier[]::new))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize());


        JPAQuery<Long> countQuery = select(board.count())
                .from(board)
                .where(categoryEq(condition.getCategory()),recruitmentPeriodPredicate(),activityFieldExpression);

        return PageableExecutionUtils.getPage(contentQuery.fetch(),pageable,()->countQuery.fetchCount());
    }

약 3배정도의 성능차이를 보이네요 기존 27 -> 78.2 tps 로 확실하게 차이가 분명했습니다.

 

궁금하고 앞으로 테스트해봐야 할 것

t2.micro -> t2.small 로 늘렸을 때 cpu를 2배로 업그레이드하게되면 tps도 정확하게 2배가 늘어나게 될까 ?

 

where 문에 있는 컬럼을 index조회방식으로 조회하게 되면 성능은 얼마나 좋아질 것 인가 ? where 문에 있다고 무조건 다 index를 타게 될까 ? 

 

728x90