- 기존 exists 관련 메서드는 내부적으로 count 쿼리를 통해 동작합니다.
- exists 쿼리의 경우, 조건에 해당하는 row 발견 시 쿼리가 종료됩니다.
- 그러나 count 쿼리의 경우, 조건에 해당하는 row가 발견되어도 테이블의 끝까지 모두 스캔하므로 exists 쿼리에 비해 성능이 저하됩니다.
- 이를 해결하기 위해 QueryDsl의 selectOne과 fetchFirst를 통해 성능 개선을 진행하였습니다.
- fetchFirst를 통해 where절 조건에 해당하는 row가 발견되면 쿼리를 종료함으로써 exists 쿼리와 동등한 동작을 수행하도록 변경했습니다.
- 이 때, 조건에 해당하는 row가 발견되지 않으면 fetchFirst의 결과가 null이므로, null 여부를 통해 exists의 결과를 반환합니다.
- 블로그 포스팅: exists 메서드 성능 개선 - count 대신 limit 1 사용
- 사용되지 않는 import문을 제거했습니다.
- 또한, 조회용 메서드에 대해 일괄적으로
@Transactional(readOnly = true)
어노테이션을 추가했습니다. - Entity의 수정이 없는 조회용 메서드의 경우, readOnly를 설정했을 때의 장점은 다음과 같습니다.
- JPA에서 엔티티는 영속성 컨텍스트에 초기 상태에 대한 스냅샷 인스턴스를 보관함으로써 변경 감지(Dirty Checking)를 수행합니다.
- readOnly=true 설정 시 스프링 프레임워크가 하이버네이트의 세션 플러시 모드를 MANUAL로 설정함으로써, 강제로 flush를 호출하지 않는 한 플러시가 일어나지 않습니다.
- 이로 인해 트랜잭션 커밋 시 영속성 컨텍스트가 자동으로 플러시 되지 않으므로 조회용으로 가져온 Entity의 예상치 못한 수정을 방지할 수 있습니다.
- 또한, JPA가 변경 감지를 위한 스냅샷을 보관하지 않으므로 메모리가 절약됩니다.
- Replication을 적용중이라면, readOnly가 설정된 메서드의 경우 slave에서 데이터를 가져옴으로써 부하를 분산할 수 있습니다.
- 직관적으로 해당 메서드는 조회용 메서드임을 알 수 있어 가독성 측면에서도 이점을 갖습니다.
- 블로그 포스팅: @Transactional(readOnly = true)를 왜 붙여야 하나요
- 1:N 연관관계에 대한 N+1 문제를 해결하기 위해 JPA batch fetch size를 설정해 둔 상태입니다.
- batch fetch size를 설정해두었기 때문에, 연관관계에 대해 in 절을 통한 조회가 가능합니다.
- 다음과 같이 batch fetching을 통한 해결에도 불구하고, 의미없는 left join이 많이 존재하여 모두 제거했습니다.
- 기존 : 게시글(Document, Toktok, Report)에 대한 Comment, Like 등의 부가 정보 조회 시 BooleanExpression을 통해 동적쿼리를 생성했습니다.
- 모든 부가정보 관련 QueryDsl Custom Repository에 해당 게시글을 판단하는 동적쿼리가 중복되어 작성되어 있었습니다.
- 개선 : Lambda와 try-catch를 활용해 null-safe한 BooleanBuilder를 구현하여 게시글을 판단하는 공통 BooleanBuilder 유틸성 메서드를 구현했습니다.
- 이를 통해 중복된 코드를 제거하고, 코드의 재사용성을 높일 수 있습니다. (line 95줄 제거)
- 기존 : 댓글 목록 조회 쿼리에 대해 limit, offset을 사용해 일반적인 방식으로 페이징 기능을 구현했습니다.
- 하지만, 이러한 기존 페이징 구현 방식은 데이터가 많아질수록 과거의 값을 조회할 때의 성능이 저하됩니다.
- 매번 페이징 쿼리가 수행 될 때 최신 값부터 순차적으로 scan하므로 조회 할 값이 과거의 값일수록 성능이 저하됩니다.
- 즉, 조회 하고자 하는 값의 첫번째 값 이전의 모든 값들은 사용되지 않음에도 불구하고 순차적으로 scan되는 불필요한 과정이 발생합니다.
- 이러한 페이징 성능 문제를 해결하는 방식은 구현해야하는 페이징 방식에 따라 나누어집니다.
- 페이지 번호 형식으로 페이징 구현 시 : 커버링 인덱스를 활용한 성능 개선 (클러스터 인덱스 활용 PK만 조회 -> PK를 in절로 묶어 데이터 조회)
- 무한 스크롤 형식으로 페이징 구현 시 : No-Offset을 활용한 성능 개선 (마지막 데이터를 기준으로 이후 값만 조회)
- 변경 : 본 프로젝트에서는 무한 스크롤 형식으로 페이징을 구현해야 하므로 No-Offset을 활용해 페이징 성능을 개선했습니다.
- offset을 사용하지 않고, 이전에 조회한 마지막 데이터의 PK를 WHERE절에서 less than으로 사용하여 이전 데이터의 scan을 건너뛸 수 있습니다.
- 또한, WHERE절에 사용되는 PK는 클러스터 인덱스가 적용되므로 조회 성능이 훨씬 더 향상됩니다.
- 블로그 포스팅: No-Offset 적용을 통한 무한 스크롤 방식의 페이징 쿼리 성능 개선