개발놀이터

N + 1 문제 해결 join fetch vs @EntityGraph 본문

JPA/JPA

N + 1 문제 해결 join fetch vs @EntityGraph

마늘냄새폴폴 2022. 9. 22. 15:02

본 포스팅에선 N + 1 문제에 대해서 직접적으로 다루지 않습니다. N + 1 문제에 대한 개념적인 내용은 이미 많이 알려져 있기 때문에 다루지 않습니다. 

 

그럼 이번 포스팅에선 무엇을 다룰것이냐

 

바로 join fetch 와 @EntityGraph에 대해서 알아볼 것입니다. 그리고 알아보면서 둘의 차이점과 각각을 사용할 때 주의 사항도 알아보도록 하겠습니다. 우선 join fetch부터 보겠습니다.

 

 

join fetch

대표적으로 알려진 N + 1 문제의 해결 방안입니다. 어떻게 사용하는지와 왜 사용하는지에 대해서는 말씀드렸다시피 다루지 않습니다. 

 

대부분은 join fetch로 문제가 해결되기 때문에 만능이라고 생각하시는 분들이 있습니다. 하지만 join fetch도 만능은 아닙니다. 

 

Native Query

우선 join fetch는 하드코딩 해야한다는 단점이 있습니다. 쓰는 방법을 잘 떠올려 보시면 native query로 만들어서 쿼리에 join fetch를 붙입니다. 

 

native query의 문제점은 각종 휴먼 에러가 발생할 수 있다는 점입니다. 

 

이를 해결하기 위해 Querydsl이라는 기술을 접목시킵니다. 이렇게 되면 자동완성이나 문법을 컴파일 단위로 체크할 수 있고 각종 휴먼 에러를 해결할 수있습니다. 

 

그럼 native query가 해결됐으니 문제 업겠네요?

 

Pagination

또 다른 문제가 있습니다. 바로 ~ToMany 연관관계일 경우 Pagination을 조심해야 한다는 것입니다. 물론 Pagination을 진행할 때 ~ToOne인 연관관계는 조심하지 않아도 됩니다. 문제없이 페이징 쿼리가 날아갑니다. 

 

하지만 문제는 ~ToMany일 때 입니다. ~ToMany 연관관계에서 페이징 쿼리를 넣으려 하면 잘 작동되는 것 같지만 로그에 WARN 로그가 찍힙니다. 내용인 즉슨 페이징 쿼리가 날아가지 않고 인메모리 페이징이 됐다는 내용입니다. 

 

그렇다는 의미는 무엇일까요? 이 경고에는 두가지 홱심이 있습니다. 

  1. 페이징 쿼리가 날아가지 않았다.
  2. 인메모리 페이징이 됐다.

이 둘은 서로 연관된 문제입니다. 둘로 보이지만 사실 하나의 문제라는 의미이죠. 한번 살펴보도록 하겠습니다.

 

우선 페이징 쿼리가 날아가지 않고 메모리에 모든 데이터가 올라갔다는 의미이기 때문에 혹시 데이터가 100만건이라면 모든 데이터가 메모리에 올라갔을 것입니다. 

 

이렇게 되면 자칫 잘못했을 때 OOME (Out Of Memory Error)가 발생할 수 있습니다. 

 

 

~ToMany 연관관계가 두개이상 경우

이런 경우에도 join fetch는 문제가 됩니다. 바로 ~Tomany 연관관계가 두개 이상인 경우에 join fetch를 사용하면 MultipleBagFetchException이 발생합니다. 

 

여기서 Bag란 Hibernate에서 중복 요소를 허용하는 비 순차 컬렉션을 의미합니다. 그냥 단순하게 컬렉션을 의미한다고 생각하시면 됩니다. 

 

이 Exception은 2개 이상의 @OneToMany 컬렉션 (Bag) 에 대한 Eager fetch 시 그 결과 만들어지는 카테시안 곱에서 어느 행이 유효한 중복을 포함하고 어능 행이 그렇지 않은지 판단할 수 없어 Bag 컬렉션으로 변환될 수 없기 때문에 예외가 발생하는 것입니다. 

 

이에 대한 해결 방안으로는 자료구조를 List를 사용하지 않고 Set을 사용하는 방법과 default_batch_fetch_size를 조정하는 것입니다. 

 

먼저 Set을 사용하는 경우를 생각해보죠

 

자료구조 Set 사용

단순히 HashSet을 사용한다는 것은 아닙니다. 순서가 있어야 하기 때문에 LinkedHashSet을 사용하는 것입니다. 하지만 이렇게 되면 페이징 쿼리가 나가지 않고 풀스캔 쿼리 발생 후 메모리에서페이징을 처리합니다. 이렇게 되면 해결되는 것 처럼 보이지만 데이터가 일정량 이상 많으면 전체 데이터를 조회해오는 것은 너무나 부담되는 일이라서 해당 방법을 쓰지 않습니다. 

 

default_batch_fetch_size 조정

두번째로 application파일에서 default_batch_fetch_size를 조정하는 것입니다. 이렇게 되는 경우 쿼리가 in 쿼리로 날아가게 되며 가져오는 데이터가 많으면 많을 수록 효과를 보는 방법입니다. 때문에 이 방법이 가장 많이 쓰이는 방법입니다. 웬만하면 이 방법을 추천드리고 싶네요.

 

in 절이나 사용 방법에 대해서는 다루지 않습니다. 

 

 

@EntityGraph

@EntityGraph는 Spring Data JPA 에서 제공하는 어노테이션입니다. attributePath를 지정해서 데이터를 eager로 가져오는 방법입니다. 

 

장점은 하드코딩 하지 않아도 된다는 점이 있습니다. 

 

하지만 단점으로는 관계가 복잡해지는 경우 헬게이트가 열릴 가능성이 있다는 문제가 있죠

 

하지만 N + 1 문제를 해결하는데에는 문제 없습니다. 간단한 관계인 경우 @EntityGraph를 사용하는 것도 좋은 방법일 수 있습니다. 

 

다만 주의 사항이 있습니다. 

 

join fetch는 inner join이고 @EntityGraph는 outer join이 발생해서 카테시안 곱이 일어나기 때문에 의도치않은 값이 생성될 수 있습니다. 

 

따라서 이를 해결하기 위해서 자료구조 중 Set을 사용하거나 distinct로 중복을 제거하는 방법이 있습니다. 

 

 

여기까지 join fetch와 @EntityGraph에 대해서 알아봤습니다. 자세한 내용이 들어가진 않았지만 전체적인 흐름을 파악하는 데에는 도움이 되셨으면 좋겠습니다. 긴 글 읽어주셔서 감사합니다.

 

 

Reference

https://jojoldu.tistory.com/457

 

MultipleBagFetchException 발생시 해결 방법

JPA의 N+1 문제에 대한 해결책으로 Fetch Join을 사용하다보면 자주 만나는 문제가 있습니다. 바로 MultipleBagFetchException 입니다. 이 문제는 2개 이상의 OneToMany 자식 테이블에 Fetch Join을 선언했을때 발..

jojoldu.tistory.com

https://programmer93.tistory.com/83

 

JPA N+1 문제 해결 방법 및 실무 적용 팁 - 삽질중인 개발자

- JPA N+1 문제 및 해결 방법 - JPA를 사용하다 보면 의도하지 않았지만 여러 번의 select 문이 순식간에 여러 개가 나가는 현상을 본 적이 있을 것이다. 이러한 현상을 N+1문제라고 부른다. 해당 포스

programmer93.tistory.com

https://jojoldu.tistory.com/165

 

JPA N+1 문제 및 해결방안

안녕하세요? 이번 시간엔 JPA의 N+1 문제에 대해 이야기 해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와 세미

jojoldu.tistory.com

https://happyer16.tistory.com/entry/N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0

 

N+1 문제 해결하기 ( fetch join, @EntityGraph )

댓글과 대댓글을 조회하는 API를 개발하였는데, 검색이 비이상적으로 오래 걸려 Query문을 다시 봤다. 1. 기존 Query @Query(value = "SELECT c FROM Comment c WHERE c.goodsId = ?1 AND c.id = c.parentIdx " +..

happyer16.tistory.com

https://velog.io/@jinyoungchoi95/JPA-%EB%AA%A8%EB%93%A0-N1-%EB%B0%9C%EC%83%9D-%EC%BC%80%EC%9D%B4%EC%8A%A4%EA%B3%BC-%ED%95%B4%EA%B2%B0%EC%B1%85

 

JPA 모든 N+1 발생 케이스과 해결책

N+1이 발생하는 모든 케이스 (즉시로딩, 지연로딩)에서의 해결책과 그 해결책에서의 문제를 해결하는 방법에 대해 이야기 하려합니다 😀

velog.io

 

'JPA > JPA' 카테고리의 다른 글

JPA가 트랜잭션을 관리하는 방법  (0) 2023.06.18
@Transactional (Propagation과 Isolation의 관점에서)  (2) 2023.03.02
@CreatedDate, @LastModifiedDate  (0) 2022.08.23
AuditorAware  (0) 2021.09.23
Spring Data JPA  (0) 2021.09.23