N+1 문제는 데이터베이스를 사용하는 애플리케이션에서 자주 발생하는 성능 문제 중 하나예요. 보통 ORM(Object-Relational Mapping) 도구를 사용할 때 많이 나타나는데요, 쉽게 말해서 1개의 쿼리를 실행했는데 그 결과로 인해 추가적인 N개의 쿼리가 더 실행되는 상황을 의미해요.리가 실행될 거예요. 이게 바로 N+1 문제예요.
N+1 문제는 주로 ORM이 연관 데이터를 가져오는 방식 때문에 발생해요. 보통 개발자가 명시적으로 연관 데이터를 미리 로딩(Fetch Join)하지 않으면, ORM이 각 데이터를 개별적으로 쿼리해서 가져오게 돼요.
List<Post> posts = postRepository.findAll(); // 게시글 목록 가져오기
for (Post post : posts) {
System.out.println(post.getComments()); // 각 게시글의 댓글 가져오기
}
위 코드는 findAll로 게시글을 한 번에 가져온 다음, 각 게시글의 getComments()를 호출하면서 N번의 쿼리를 추가로 실행하게 돼요. 데이터가 많아질수록 쿼리 수가 기하급수적으로 늘어나겠죠?
가장 일반적인 해결 방법은 Fetch Join
을 사용하는 거예요. JPA에서는 연관된 데이터를 한 번의 쿼리로 가져올 수 있도록 Fetch Join
을 지원해요.
@Query("SELECT p FROM Post p JOIN FETCH p.comments")
List<Post> findAllWithComments();
이렇게 하면 게시글과 댓글을 한 번의 쿼리로 가져올 수 있어요. 성능이 훨씬 좋아지겠죠?
JPA에서는 @EntityGraph
를 사용해서 필요한 연관 데이터를 미리 로딩할 수도 있어요.
@EntityGraph(attributePaths = {"comments"})
@Query("SELECT p FROM Post p")
List<Post> findAllWithComments();
Hibernate를 사용한다면 Batch Fetching
을 설정해 N+1 문제를 완화할 수 있어요. Batch Fetching은 연관 데이터를 한 번에 묶어서 가져오는 방법이에요.
hibernate.default_batch_fetch_size=10
이렇게 설정하면 각 배치에 포함된 데이터는 한 번의 쿼리로 가져오게 돼요.