refactor: 컬렉션 Fatch Join 쿼리 분리를 통한 N+1 해결 #115
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
#️⃣ 이슈
🔎 작업 내용
1️⃣ N+1 문제 발생
기존 코드의 경우, �키워드를 통한 Blueprint를 검색하는 JPQL을 사용하고 있다. 이 경우에 당연하게도 Blueprint에 있는 연관관계에 의해 N+1이 발생하게 된다.
현재 기본 값으로 지연 로딩이 설정되어 있다. Blueprint를 조회하게 되면 연관관계를 가진 orderBlueprints와 cartBlueprints를 가져오게 된다. 이 경우, Blueprint의 orderBlueprints와 cartBlueprints의 ID마다 조회하는 쿼리가 실행되기 때문에 N+1이 발생한다.
N+1 문제를 해결하기 위해
Fetch Join
을 통해 해결하려고 한다.Fetch Join을 사용한 이유
@EntityGraph
의 경우, JPQL 외에 별도로 설정을 해야하기 때문에 번거롭다고 판단.BatchSize
는 정확한 연관관계의 데이터 크기를 알기 어렵기 대문에 사용하기 알맞지 않다고 판단.Fetch Join
을 통해 JPQL 내에서 간단하게 해결이 가능.2️⃣ FetchJoin 시 Pageable 사용 불가능 문제 발생
Fetch Join 시, 부모 자식 관계를 조인하여 데이터를 한 번에 가져온다. 이 경우 부모 엔티티가 페이징 범위에 포함되더라도 조인된 자식 엔티티가 많으면 중복 데이터가 발생한다. 따라서, 페이징은 부모 엔티티 기준으로 페이징을 하지만, Hibernate는 조인된 겨로가 전체에 대한 페이징을 수행하게 된다. 결국 데이터 중복이 발생하여 페이징 결과가 왜곡될 수 있다.
예를 들어, 부모 엔티티가 3개 있고 자식 엔티티가 3개씩 있을 경우, 페이징 결과는 3개의 부모가 아닌 9개의 겨로가를 가진다.
따라서, Fetch Join 사용 시 중복 문제를 해결하게 되면 자연스럽게 Pageable의 왜곡도 해결될 것 이다.
해결 방법
3️⃣ CountQuery 적용 후 문제 발생
Fetch Join의 경우 다음 조건이 있다.
따라서, 현재 OneToMany를 2개 사용하는 Fetch Join으로 인해
MultipleBagFetchException
이 발생하는 것이다.원인
Bag 컬렉션은 List 기반의 out-of-order와 중복을 허용하기에 Fetch Join이 많아질 경우, 동시에 가져올 때 카테시안 곱이 매우 많이 발생하여 중복이 많이 발생한다. 따라서 ToMany에 대한 제약을 두는 것으로 보인다.
해결 방안
- 문제점: 각 연관관계에 대한 적절한 배치 크기를 설정해야 N+1 문제를 회피할 수 있다.
컬렉션 Fetch Join을 쿼리 분리
위 3가지 방법 중 2번을 통해 해결하기로 결정했다. 1번의 경우 명확하게 카테시안 곱을 해결해주지 못해 성능상 문제가 발생할 여지가 있고, 3번의 경우 적절한 @batchsize를 컬렉션마다 적절한 크기로 할당해야 하는데 이에 대한 지식이 많이 부족하기 때문이다.
키워드를 가진 전체 Blueprint를 먼저 조회한다. 그 이후 각 Blueprint의 orderBlueprints를 조회하고, 이렇게 조회된 blueprints로 다시 cartBlueprints를 조회한다. 이를 통해 총 3번의 쿼리로 해당 문제를 해결할 수 있다.
수정 후
총 3개의 쿼리로 성능을 최적화 할 수 있다.
🤔 고민해볼 부분 & 코드 리뷰 희망사항
현재 JPQL을 이용하여 모든 쿼리를 실행하기 때문에 가독성이 매우 떨어진다. 추후
QueryDSL
과 같은 라이브러리를 통해 가독성을 챙기는 방향으로 리렉토링하는 방향을 생각해보려 한다.🧲 참고 자료 및 공유 사항
N+1 문제 원인 및 해결방법
MultipleBagFetchException 발생시 해결 방법