본문 바로가기

TIL

n+1 문제

n+1 문제

요약

1:N, N:N 연관관계가 설정된 엔티티 조회할 경우 해당 엔티티와 관련된 컬렉션을 사용할때 n번의 추가 쿼리가 발생하는 문제, 이 때문에 데이터베이스에 대량의 쿼리가 실행되어 성능 문제가 발생할 수 있음

 

상세설명

N+1 문제는 데이터베이스 쿼리 성능과 관련된 문제 중 하나로, 주로 ORM(Object-Relational Mapping)을 사용하는 경우에 발생합니다. N+1 문제는 다음과 같은 상황에서 발생합니다.

  1. 하나의 쿼리로 엔티티를 가져올 때 (1번 쿼리)
  2. 이후 해당 엔티티와 관련된 컬렉션(OneToMany 또는 ManyToMany 등)을 사용할 때 (N번 추가 쿼리)

이때 총 실행되는 쿼리의 수는 N+1이 되므로 "N+1 문제"라고 부릅니다.

간단한 예를 통해 설명하겠습니다. 예를 들어, 블로그 포스트(Post) 엔티티와 그에 속한 댓글(Comment) 엔티티가 있다고 가정해봅시다.

  1. 첫 번째 쿼리로 블로그 포스트를 가져옵니다.
  2. 그 후에 각 블로그 포스트에 대해 관련된 댓글을 가져오는 쿼리가 실행됩니다.
1. List<Post> posts = entityManager.createQuery("SELECT p FROM Post p", Post.class).getResultList();

2. for (Post post : posts) {
    List<Comment> comments = post.getComments(); // 각각의 블로그 포스트에 대해 N개의 추가 쿼리
}

이런 식으로 각각의 블로그 포스트마다 추가적인 쿼리가 발생하게 되는데, 이 때문에 데이터베이스에 대량의 쿼리가 실행되어 성능 문제가 발생할 수 있습니다.

 

해결방법

N+1 문제를 해결하기 위한 방법 중 하나는 Eager 로딩 대신 Lazy 로딩을 사용하는 것입니다.

Lazy 로딩은 실제로 데이터가 필요한 순간까지 데이터를 불러오지 않고, 그 시점에서 쿼리를 실행합니다.

이를 통해 N+1 문제를 피할 수 있습니다.

이것도 근본적인 해결은 아니기에

다른 방법으로는 Fetch Join 등의 방법을 사용하여 연관된 엔티티들을 한 번에 로딩할 수 있습니다.

 

Lazy Loading 사용:

  • 대부분의 ORM 프레임워크는 지연 로딩(Lazy Loading)을 기본적으로 사용합니다. 이는 연관된 엔티티나 컬렉션을 실제로 필요한 시점까지 데이터베이스에서 불러오지 않고, 필요한 순간에 쿼리를 실행하는 방식입니다.
  • 예를 들어, JPA에서는 @OneToMany 또는 @ManyToMany 관계에 대한 기본 설정이 Lazy Loading이며, 이를 통해 N+1 문제를 방지할 수 있습니다.
@Entity
public class Post {
    // ...
    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
    @BatchSize(size = 10) // 일정 개수의 묶음으로 로딩
    private List<Comment> comments;
    // ...
}
@Entity
public class Post {
    // ...
    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
    private List<Comment> comments;
    // ...
}

 

Fetch Join 사용:

  • Fetch Join은 한 번의 쿼리로 연관된 엔티티들을 함께 로딩하는 방법입니다. 이를 사용하면 N+1 문제를 효과적으로 해결할 수 있습니다.
  • JPQL에서 FETCH JOIN을 사용하는 예제:
List<Post> posts = entityManager.createQuery(
    "SELECT p FROM Post p LEFT JOIN FETCH p.comments", Post.class)
    .getResultList();

 

Batch Size 설정:

  • 일부 ORM 프레임워크에서는 Batch Size를 설정하여 한 번의 쿼리로 여러 엔티티를 로딩할 수 있습니다. 이는 일종의 페이징 기법으로, 연관된 엔티티를 일정 개수만큼 묶어서 한 번에 로딩하는 방법입니다.
//application.yml

spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 1000

 

 

추가 참고 링크

https://programmer93.tistory.com/83

 

 

 

'TIL' 카테고리의 다른 글

관계형 데이터베이스 (RDBMS)와 비관계형 데이터베이스  (0) 2023.12.14
Tags In HTML  (0) 2023.07.05
Spring MVC Testing JUnit Hamcrest  (0) 2023.05.24
Spring MVC Testing 단위 테스트  (0) 2023.05.23
자료구조 Graph traversal  (0) 2023.05.17