N+1 문제..직면하다!
- 가게 엔티티와 카테고리 엔티티 작업 중 가게는 여러 카테고리를 가질 수 있게 설계를 했고 구현하는 과정에서 문제를 직면했다.
오늘 날짜인 2/19 기준으로 해결하지는 못했지만, 우선 N+1 문제에 대한 개념을 정리해보고 접근하려고 한다.
N+1 이란?
- JPA 에서 N+1 문제란 쿼리 최적화의 중요한 이슈로, RDBMS 에서 데이터를 가져올 때 예상치 못한 불필요한 추가 쿼리가 발생하는 문제이다.
- 이 문제는 JPA 사용 시, 관계를 설정한 엔티티를 조회할 때 자주 발생한다.
설명
- N+1 문제는 크게 2가지 상황에서 발생한다.
1. 1개의 엔티티를 조회하는 쿼리
2. 각 부모 엔티티와 관련된 N개의 자식 엔티티를 조회하는 쿼리
- 부모 엔티티 하나에 자식 엔티티를 조회하는 추가적인 쿼리 N개가 발생
따라서, 1+N 개의 쿼리가 실행되며 이로 인해 성능 저하 및 불필요한 쿼리 실행이 발생하게 된다.
예시
@Entity
public class Store {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "store")
private List<Category> categories;
}
@Entity
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "store_id")
private Store store;
}
- 위와 같은 2개의 엔티티가 있을 때, Store 와 Category 는 1:N 관계를 가진다.
즉, Store 는 여러 Category 를 가질 수 있다.
public List<Store> getStores() {
List<Store> stores = storeRepository.findAll();
for (Store store : stores) {
List<Category> categories = store.getCategories();
}
return stores;
}
- 위 코드에서 findAll() 메서드로 모든 Store 를 조회한다.
그 후 각 Store 객체마다 store.getCategories() 가 호출되는데, 이 때 Store 에 대한 추가 쿼리가 실행된다.
- 첫 번째 쿼리: SELECT * FROM Store; (모든 Store 조회)
- 두 번째 쿼리: 각 Store 에 대한 SELECT * FROM Category WHERE store_id = ?;
(각 Store에 대한 Cateogory 조회)
따라서 1+N 개의 쿼리가 실행되며, 100개의 Store가 조회되면 1 + 100 개의 쿼리가 실행된다.
문제점
성능 저하
- 불필요하게 많은 쿼리가 실행되며, 데이터베이스에 부담을 주고 성능이 크게 저하될 수 있다.
불필요한 DB 트래픽
- 실제로는 하나의 쿼리로 끝낼 수 있는 작업이 여러 번의 쿼리로 분리되어 DB 부하를 증가시킨다.
해결 방법
- 해결 방법은 여러 개가 있고, 우리가 익히 알고 있는 fetch join 을 사용하는 방법이 있지만!
자세한건 해결한 방법에 대해 추후 작성 예정이고, 어떤 종류가 있는지만 알아보려한다.
1. Fetch Join
2. LAZY -> EAGER
3. @EntityGraph 사용
4. Batch Size 설정
고민
- N + 1 문제는 JPA 에서 자주 발생하는 성능 문제이다.
이 문제를 해결하기 위해서는 위 방법을 적절히 사용하면 되는데...
가장 중요한 점은 데이터 로딩 전략을 필요한 데이터만 효율적으로 가져오는 방식으로 설계하는 것이다.