본문 바로가기
개인 공부/TIL

TIL : N+1 문제, Fetch 전략 (18)

by 희조당 2022. 12. 7.
728x90

☄️ N+1 문제 

N+1 문제란? JPA를 사용할 때 흔히 발생하는 문제 중 하나이다.

1번의 쿼리로 N개의 데이터를 가져왔는데, 그 데이터들을 처리하기 위해서 N번의 쿼리가 더 발생하는 문제이다.

🧐 언제 발생하는 문제일까?

몇몇 블로그에서 Fetch 전략에 따라 발생하는 문제라고 작성되어있다.

EAGER와 LAZY는 실제 데이터를 가져오는 시점의 차이이지 N+1 문제에 궁극적인 원인이 아니다.

 

JpaRepository에 정의한 메서드를 실행하면 JPA는 메서드 이름을 분석해서 JPQL 쿼리를 생성해 실행한다.

JPQL 객체지향 쿼리 언어로 테이블을 신경 쓰지 않고 엔티티와 필드만으로 쿼리를 생성한다.

따라서, N+1 문제는 JPQL이 연관관계를 무시하고 해당 엔티티만을 기준으로 쿼리를 조회하기 때문에 발생한다.

🛠️ 문제 해결하기

1️⃣ Join Fetch

쿼리문에 "join fetch"를 붙여 쿼리를 최적화해서 N+1 문제를 해결한다.

JpaRepository에서 자동으로 붙일 수 없으니 JPQL로 작성해야 한다.

Join Fetch는 Inner Join을 사용한다.

2️⃣ @EntityGraph

어노테이션의 attributePaths 속성에 가져올 필드명을 지정해서 N+1 문제를 해결한다.

Join Fetch와 동일하게 JPQL로 작성해야 하고 원본 쿼리를 건드리지 않고 N+1 문제를 해결할 수 있다.

@EntityGraph는 Outer Join을 사용하고 FetchType.EAGER을 사용한다.

😮 이렇게 쉽다고?

Join Fetch와 @EntityGraph를 사용할 때 주의점도 존재한다!

 

Join Fetch는 연관관계를 매핑할 때 설정한 FetchType이 의미가 없어진다라는 단점이 존재한다.

또한, 하나의 쿼리문을 사용해 페이징 단위로 데이터를 가져올 수 없다. (페이징 쿼리 사용불가)

 

두 방법 모두 카테시안 곱(Cartesian Product)이 발생하여 데이터가 중복 발생할 수 있다.

두 가지 방법으로 이 문제를 해결할 수 있다.

  • Set 자료구조 사용하기 (데이터의 순서까지 고려한다면 LinkedHashSet)
  • JPQL로 쿼리를 작성하니 distinct 사용하기 (Set보다 List가 맞다고 판단한 경우)

QueryBuilder를 사용하는 방식으로도 해결할 수 있으니 상황에 맞춰서 사용하면 되겠다!


🎇 Fetch 전략

JPA는 ORM이라는 객체와 테이블을 매핑시켜주는 기술로,  테이블의 연관관계는 객체의 참조로 이루어진다.

객체가 커질수록 참조는 많아지고, 많아진 만큼 데이터를 한 번에 불러오는 것은 데이터베이스 입장에서는 부담스럽다.

따라서 JPA는 Fetch 전략을 도입해서 데이터를 가져오는 시점을 변경해서 이런 부담을 줄이려고 한다!

 

1️⃣ 즉시 로딩, Eager

  • 연관된 엔티티를 즉시 조회한다. 
  • 성능 최적화를 위해서 join을 사용한다.
  • @ManyToOne과 @OneToOne의 default이다.

2️⃣ 지연 로딩, Lazy

  • 연관된 엔티티를 프록시 객체로 바인딩한다.
  • 프록시 객체가 실제로 사용될 때 쿼리문을 수행해 데이터를 가져온다.
  • @ManyToOne과 @OneToOne의 default이다.

FetchType.EAGER는 예상치 못한 쿼리를 생성할 수도 있어서 일반적으로 FetchType.LAZY를 권장한다.

하지만 둘의 특성을 잘 파악하고 상황에 맞는 전략을 사용하자! 😋😋


-Reference :

https://jojoldu.tistory.com/165

https://incheol-jung.gitbook.io/docs/q-and-a/spring/n+1 

 

😋 지극히 개인적인 블로그지만 훈수와 조언은 제 성장에 도움이 됩니다 😋 

댓글