본문 바로가기
Back-end

Query DSL 성능 개선

by 신재권 2024. 1. 20.

동적쿼리는 BooleanExpression

  • null 반환 시 자동으로 조건절에서 제거
  • 모든 조건이 null이 발생하는 경우는 에러 발생
  • 가독성 향상
    • 명확하게 무슨 역할을 하는지 알 수 있음

 

exist 메서드 금지

SQL 에서 exist와 count()를 비교하면 exist의 성능이 훨씬 좋음

  • 스캔 대상이 앞에 있을 수록 더 심한 성능 차이가 발생

query dsl에서는 exist는 count() > 0 으로 실행됨

@Override
public boolean exists(Predicate predicate) {
	return createQuery(predicate).fetchOunt() > 0;
}

query dsl은 결국 JPQL 빌더 역할

JPQL은 from 없이는 쿼리 생성이 불가

 

즉, 하위에 select 쿼리를 만드는 것이 불가

exists가 빠른 이유는 조건에 해당하는 row 1개만 찾으면 바로 쿼리를 종료하기 때문이다.

 

해결방법

  • limit 1 로 조회 제한
    • 조회 결과가 없으면 null 이라서 체크를 해야 한다.
return queryFactory
	.selectOne()
	.from(book)
	.where(book.id.eq(bookdId))
	.fetchFirst() != null

 

Cross Join 회피

묵시적 Join으로 Cross Join 발생

Hibernate 이슈라서 Spring Data Jpa도 동일하게 발생

명시적 Join을 사용하면 Inner Join 발생

Cross Join을 발생하지 않도록 할 수 있다.

 

Entity 보다는 DTO 우선

  • Entity 조회 시
    • Hibernate 캐시
    • 불필요한 컬럼 조회
    • One TO One N+1 쿼리
    • 단순 조회 기능에서는 성능 이슈 요소가 많음

Entity 조회 사용 : 실시간으로 Entity 변경이 필요한 경우

Dto 조회 사용 : 고강도 성능 개선 or 대량의 데이터 조회가 필요한 경우

조회 컬럼 최소화 하기

  • 이미 알고 있는 값을 조회할 필요는 없다.
  • asNumber 표현식으로 대체 가능
    • select 절에서 제외 된다.

 

Select 컬럼에 Entity 자제

select 컬럼에 entity를 조회하면 조회된 결과로 신규entity를 생성

  • 전달한 entity의 모든 컬럼이 조회된다.(사용하지 않는 컬럼이여도)
  • 전달한 entity의 OneToOne 관계인 엔티티도 조회된다.
    • oneToOne은 Lazy Loading이 불가
      • N+1 무조건 발생
    • 만약 oneToOne으로 조회된 엔티티에도 oneToOne이 있다면?
      • N+1 또 발생

 

연관관계를 저장할 때는 실제로는 id만 필요하다.

select 절에서 id만 가져올 수 있다.

 

Group By 최적화

MySQL에서 Group By를 실행하면 Filesort가 필수로 발생(Index가 아닌 경우)

  • order by null을 사용하면 Filesort가 제거된다.
  • 하지만 QueryDSL에서는 order by null 문법을 지원하지 않음
  • order By null을 직접 구현해야 한다.
public class OrderByNull extends OrderSpecifier {
	public static final OrderByNull DEFAULT = new OrderByNull();

	private OrderByNull() {
		super(Order.ASC, NullExpression.DEFAULT, Default);
	}
}
  • 정렬이 필요할 때 조회 결과가 100건 이하이면 애플리케이션 정렬
    • DB 보다는 WAS의 자원이 훨씬 저렴
  • 페이징일 경우 order by null을 사용하지 못함

 

커버링 인덱스

커버링 인덱스란 쿼리를 충족시키는데 필요한 모든 컬럼을 갖고 있는 인덱스이다.

select/where/order by/group by 등에서 사용되는 모든 컬럼이 인덱스에 포함된 상태

NoOffSet 방식과 더불어 페이징 조회 성능을 향상시키는 가장 보편적인 방법

 

JPQL은 from절의 서브 쿼리를 지원하지 않음

 

Cluster Key(PK)를 커버링 인덱스로 빠르게 조회

조회된 Key로 SELECT 컬럼들을 후속 조회

 

기존 커버링 인덱스와 거의 비슷한 성능을 보여줌

 

Update/Insert

객체지향을 핑계로 성능을 버리진 않는지, 무분별한 DirtyChecking을 확인해야 한다.

 

하이버네이트 캐시는 일괄 업데이트시 캐시 갱신이 안됨

  • 이럴 경우 업데이트 대상들에 대한 Cache Eviction이 필요

 

DirtyChecking

  • 실시간 비즈니스 처리, 실시간 단건 처리시

 

Querydsl.update

  • 대량의 데이터를 일괄로 Update 처리시

 

진짜 Entity 가 필요한게 아니라면 QueryDsl과 DTO를 통해 딱 필요한 항목들만 조회하고 업데이트 한다.

 

Bulk Insert

rewriteBachedStatements 으로 Insert 합치기 옵션을 넣어도, JPA는 auto_increament 일 때 Insert 합치기가 적용되지 않음

JdbcTemplate로 Bulk Insert는 처리되나, 컴파일 체크, 코드-테이블 간의 불일치 체크 등 Type Safe 개발이 어려움

 

정리

  1. 상황에 따라 ORM/전통적 Query 방식을 골라 사용할 것
  2. JPA/Querydsl로 발생하는 쿼리 한번 더 확인 하기
  3. 필독서 : JPA/ Real My SQL

출처

[우아콘2020] 수십억건에서 QUERYDSL 사용하기

 

'Back-end' 카테고리의 다른 글

Redis  (1) 2024.01.29
Spring HttpMessageConverter  (0) 2024.01.28
Spring AutoConfiguration  (0) 2024.01.20
Spring Validation in Kotlin  (0) 2024.01.14
Spring @Async + ThreadPoolTaskExecutor  (1) 2024.01.14