티스토리 뷰

Spring

JPA N+1문제

soo__ 2022. 4. 23. 23:39

N+1이란?

조회 시 1개의 쿼리만을 생각하고 설계했지만, 추가로 N개의 쿼리가 발생하는 문제를 일컫는다.

  • 즉시로딩(EAGER)의 경우: User 전체를 조회할 때, 각 User의 필드를 채우기 위해 즉시 조회하여 N+1 발생
  • 지연로딩(LAZY)의 경우: User 조회 후 필드에 접근할 때, N+1 발생

 

fetch join으로 해결하기

즉시로딩에서는 커스텀할 수 있는 부분이 존재하지 않기 때문에, 지연로딩 과정에서 바로 사용을 할 객체에 대해서는 조정이 필요한데, 이때 해결책으로 fetch join을 사용한다.

 

 

fetch join의 한계점

fetch join은 ~ToMany 관계(컬렉션)가 1개 이하일 때만 가능하며, 이 때 페이징이 가능하지만(distinct 키워드를 사용).. 권장하지 않는다.

  • 일반적으로 ~ToMany 관계에 있는 엔티티끼리 join을 하게 되면, 결과 row 수가 뻥튀기 된다(중복 데이터 발생)
  • 따라서, 페이징의 기준으로 가져갈 컬럼이 불명확해지고
    (컬렉션이 2개 이상이 되면 n*m개가 되면서 데이터가 부정합하게 조회될 수 있음)
  • 하이버네이트는 경고 로그를 남기면서, 메모리 내에서 페이징을 한다..!
  • 이때, OOM(Out Of Memory)예외가 발생할 수 있어 위험하다

이렇게 정리할 수 있다.

(한계점 1) 컬렉션(~ToMany) fetch join 시 페이징은 위험하다
(한계점 2) 둘 이상의 컬렉션(~ToMany)은 fetch join이 불가능하다

 

 

1) 컬렉션(~ToMany) fetch join 시 페이징은 위험하다

fetch join 대신 지연로딩을 유지하고, hibernate.default_batch_fetch_size 옵션(global 하게 작용)을 사용하거나, 대상 필드 또는 클래스에 @BatchSize을 적용하여 개별 최적화한다.

   * size 크기는 100~1000 사이를 권장 (DB에 따라 IN절 파라미터를 1000으로 제한하기도 하므로)

  • 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size만큼 where절 IN 쿼리로 조회한다
  • 따라서 쿼리 호출 수가 N번 만큼 무수히 날아가지는 않음 (설정한 size에 비례하여 날아감)
    fetch join 방식과 비교해서 쿼리 호출 수가 약간 증가하지만, DB 데이터 전송량이 감소
  • 데이터가 명확하므로 쿼리를 통한 페이징이 가능하며,
  • 중복 데이터 또한 없다!

※ 주의해야할 점!

@BatchSize에 fetch join을 걸면 안된다.

fetch join이 우선시되어 적용되기 때문에 batch size가 무시된다!

 

 

2) 둘 이상의 컬렉션(~ToMany)은 fetch join이 불가능하다

두 컬렉션의 자료형을 Set으로 변경하여 중복 자체를 허용하지 않게끔 하면, MultipleBagFetchException(둘 이상의 컬렉션 fetch join을 막는 exception)없이 정상적으로 모든 데이터를 가져온다.

   * 이 때, 데이터의 순서를 보장하려면 LinkedHashSet 사용

하지만 여전히 페이징은 위험하다(메모리에서 진행됨)
페이징을 하고 싶다면 1)처럼 fetch join 대신 지연로딩을 유지하고 @BatchSize 옵션을 통해 최적화해야한다. 

 

 

 

 

공지사항
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31