Jpa/querydsl

[Querydsl 2편] querydsl 성능 관련 정리

무대포 개발자 2021. 12. 14. 22:50
728x90
반응형

source 는 Github 에 있습니다.

목차는 Querydsl 목차 에 있습니다.

[Querydsl 2편] querydsl 성능 관련 정리

Querydsl Tip

implement, extends

  • implement, extends 를 사용해서 querydsl 사용하지 않는 것이 좋습니다.
  • JpaQueryFactory 만 빈으로 선언해주면 implement, extends 를 사용하지 않아도 됩니다.

동적 쿼리 BooleanExpression

  • 아래처럼 명시적으로 만드는게 좋습니다.

    return queryFactory
                    .selectFrom(xxx)
                    .where(eqAge(age),
                                    eqTel(tel))
                    .fetch();

    private BooleanExpression eqAge(String age) {
        xxxx
    }

성능 (Select)

querydsl 에서 exist

  • 보통 데이터가 얼마나 존재하는지 체크할 떄, exist 가 count 보다 성능이 잘나옵니다. (exist 가 빠른 이유는 조건에 해당하는 row 1개만 찾으면 종료하기에 빠름)
  • querydsl exist 는 exist 를 사용하지 않고 count(1) 로 동작됩니다. (즉, 데이터를 전부 읽어들입니다.)
  • querydsl 에서 exist 를 사용하고 싶을 시, 아래 예제와 같이 구현하면 됩니다.
@Transactional(readOnly = true)
public Boolean exist(Long id) {
    String resultOne = queryFactory
                    .selectOne()
                    .from(test)
                    .where(test.id.eq(id))
                    .fetchFirst(); // limit 1
    return resultOne != null; // 값이 없으면 null 반환
}

Querydsl 결과에 대해서 Entity 보다는 DTO 사용

  • Entity 를 사용하면 보통 Entity 의 컬럼을 전부 사용하지 않습니다. (즉, 성능 낭비입니다.)
  • Select 컬럼에는 Entity 자제하고, DTO 를 만들어서 필요한 컬럼만 조회하는 것이 성능에 좋습니다.
    • One To One 같은게 있으면 N+1 이 발생, 또는 불필요한 컬럼까지 조회됩니다.
    • distinct 를 사용할 경우 select 절에 Entity 가 있을 경우 전체에 대해 distinct 가 동작하기에 성능 낭비입니다.

Group by 최적화

  • MySQL 에서 인덱스가 없는 컬럼에 대해 group by 를 하게 되면 filesort 가 발생합니다.
    • group by 를 하는데 filesort 가 필요할 수도 있고 안할수도 있습니다. 안하는 경우 성능 낭비입니다.
  • 이를 해결하기 위해서는 MySQL 에서 order by null 문법을 사용하면 Filesort 가 제거가 되는데 Querydsl 에서는 이를 지원하지 않습니다.
  • querydsl 에서 직접 만들어서 처리하면 되며, 페이징일 경우 order by null 사용할 수 없습니다.

정렬

  • 정렬이 필요할 경우 데이터가 적다면 DB 에서 order by 를 하는게 아니라 프로그램단에서 sort 하는게 좋습니다.
  • application 레벨의 자원이 DB 보다는 보통 더 빵빵하기에 이 방법이 좋습니다.

커버링 인덱스

  • select 한 데이터가 전부 인덱스에 존재할 경우 디스크를 접근안하기에 성능이 좋습니다. DB 성능 저하의 대부분은 디스크에 접근할 때 발생합니다.
  • 페이징 조회 성능을 향상시키는 보편적인 방법은 커버링 인덱스를 사용하는 것이며, 예제는 아래와 같습니다.
    • 아래 쿼리 동작방식을 보면 id 를 인덱스에서 (메모리) 조회해오기에 빠르게 조회해옵니다.
    • 가져온 ID 를 가지고 자체 조인해서 가져오는 방식입니다.
  • Querydsl 은 JPQL 을 만드는 오픈소스이고, JPQL 은 서브쿼리를 지원안합니다.
  • 아래 처럼 id 를 먼저 조회해오고 다시 쿼리를 날려 id 로 조회하면 커버링 인덱스 방식으로 데이터를 조회합니다.
select * 
from test t
join (select id
      from test
      order by id
      limit 100, 10) as tmp // 이 부분이 페이징 부분이고.
on tmp.id = t.id
List<Long> ids = queryFactory.
                    xxx
                    xxx
                    .fetch();

return queryFactory
        .select(xxx)
        .where(test.id.in(ids)
;                                

성능 개선 (Update / Insert)

  • dirty checking 을 통해 데이터를 업데이트하는건 단건이면 괜찮습니다. 그러나 dirty checking 을 통해 바꾸는 데이터가 많다면 update 쿼리를 한 번 호출해서 바꾸는게 훨씬 효율적입니다.
  • 실시간 비즈니스 처리 ,단건 처리면 dirty checking 사용하는 것이 좋습니다.
  • 대량 처리면 update 사용하는 것이 좋습니다.

Reference