N+1 문제는 데이터베이스 질의와 관련된 성능 문제 중 하나로 관계형 데이터베이스에서 ORM 도구를 사용할 때 주로 발생한다.
이 문제의 핵심은 특정 데이터를 검색할 때 필요한 질의의 수가 예상보다 많아져서 성능 저하가 일어나는 것디다.
예를 들자면 부모 테이블에 있는 각 레코드에 대한 연관된 자식 테이블의 레코드들을 가져오려고 할 때 부모 테이블의 레코드 하나를 가져오는 질의와 그 레코드에 연관된 자식 테이블의 레코드들을 가져오는 별도의 질의가 필요하게 된다.
부모 테이블에 N개의 레코드가 있다면 총 N+1개의 질의가 필요하게 되는데 여기서 N+1 문제의 이름이 유래되었다.
@Query("SELECT p FROM Book b JOIN FETCH b.apply WHERE b.id = :id")
Parent findByIdWithApply(@Param("id") Long id);
EntityGraph는 엔터티의 특정 속성을 EAGER 혹은 LAZY로 가져올 것인지를 동적으로 정의할 수 있는 기능이다.
Spring Data JPA에서는 @EntityGraph 어노테이션을 사용하여 쿼리 메서드에 이를 적용할 수 있다.
@EntityGraph(attributePaths = "apply")
List<Book> findAll();
배치 가져오기
한 번의 질의로 여러 개의 레코드를 한꺼번에 가져오는 방법이다.
Hibernate에는 @BatchSize 어노테이션을 제공하여 한 번의 쿼리로 가져올 엔터티의 크기를 설정할 수 있다.
이는 N+1 문제를 완전히 해결하는 것은 아니지만 질의의 횟수를 줄여 성능을 개선하는 데 도움을 준다.
예를 들면 부모 테이블의 ID 목록을 이용하여 해당 ID들과 연관된 자식 테이블의 레코드들을 한 번의 질의로 가져올 수 있다.
@Entity
@BatchSize(size = 10)
public class Parent {
// ...
}
spring.data.web.pageable.default-page-size=10
spring.data.web.pageable.max-page-size=100
데이터베이스 뷰 사용
데이터베이스 뷰를 생성하여 필요한 데이터를 미리 조인하거나 집계하는 등의 연산을 수행한 후, 해당 뷰를 조회하여 필요한 데이터를 가져올 수 있다.