N+1 문제

leedong617·2024년 9월 4일
post-thumbnail

N+1 문제란?

N+1 문제는 데이터베이스와 연관된 애플리케이션에서 발생하는 성능 문제로, 주로 ORM(Object-Relational Mapping) 프레임워크(예: Hibernate, JPA)에서 자주 나타남. 이 문제는 하나의 쿼리로 여러 데이터를 가져올 때, 연관된 데이터를 추가로 가져오는 과정에서 불필요하게 많은 쿼리가 발생하는 상황을 말함.

즉, 하나의 엔터티를 가져오는 데 1개의 쿼리가 실행되고, 그 엔터티에 연관된 N개의 데이터에 대해 각각 또 다른 쿼리가 실행되어 총 N+1개의 쿼리가 발생하는 문제임.

예시

게시물과 댓글이 있는 상황에서 게시물 목록을 가져오고, 각 게시물에 달린 댓글을 가져온다고 가정함.

게시물을 가져오는 쿼리

SELECT * FROM posts; (1개의 쿼리)

각 게시물마다 해당하는 댓글을 가져오는 쿼리

게시물 N개가 있으면 댓글을 가져오는 쿼리 N번 실행됨.
총 쿼리 수는 1(게시물 쿼리) + N(댓글 쿼리) = N+1 쿼리가 발생하게 됨.

N+1 문제가 발생하는 이유

N+1 문제는 주로 지연 로딩(Lazy Loading)으로 인해 발생함. ORM에서는 엔터티와 연관된 다른 엔터티를 즉시 로딩(Eager Loading) 하거나 지연 로딩 할 수 있음.

즉시 로딩(Eager Loading)

연관된 엔터티를 함께 로딩함. 즉, 처음에 한 번의 쿼리로 모든 데이터를 가져옴.

지연 로딩(Lazy Loading)

연관된 엔터티를 필요할 때만 별도의 쿼리로 로딩함.

문제가 발생하는 이유는, Lazy Loading을 사용할 때 주 쿼리로 특정 엔터티를 가져온 후, 연관된 데이터를 필요할 때마다 추가로 쿼리를 실행하기 때문에, N개의 데이터에 대해 각각 추가 쿼리가 발생하게 됨.

구체적인 발생 과정

첫 번째 쿼리로 posts 테이블에서 게시물 목록을 가져옴. (SELECT FROM posts;)
게시물 엔터티가 로딩된 후, 각 게시물에 연관된 댓글을 가져올 때마다 각각 추가 쿼리가 발생함. (SELECT
FROM comments WHERE post_id = ?;)
즉, 게시물 N개가 있을 때, 댓글을 가져오기 위한 N개의 추가 쿼리가 발생하여 N+1 쿼리 문제가 발생함.

N+1 문제 해결 방법

N+1 문제는 성능 저하를 일으키므로 이를 해결하기 위해 다양한 방법이 있음. 주로 즉시 로딩이나 JOIN 쿼리를 사용하여 한 번에 데이터를 가져오는 방식으로 해결함.

1. 즉시 로딩(Eager Loading) 사용

지연 로딩 대신 즉시 로딩(Eager Loading) 설정을 사용하여 관련 데이터를 한 번에 로딩함. 즉, 첫 번째 쿼리에서 연관된 엔터티까지 모두 가져옴.

@Entity
public class Post {
    @OneToMany(fetch = FetchType.EAGER) // 즉시 로딩
    private List<Comment> comments;
}
  • 즉시 로딩은 연관된 데이터를 미리 가져오기 때문에 N+1 문제를 방지할 수 있음.
  • 하지만, 즉시 로딩을 사용할 때 불필요한 데이터를 미리 가져오는 문제가 발생할 수 있으므로 신중히 사용해야 함.

2. 페치 조인(Fetch Join) 사용

페치 조인(Fetch Join)은 한 번의 쿼리로 연관된 엔터티를 모두 가져오는 방법임. 이를 통해 여러 쿼리 대신 하나의 쿼리로 필요한 데이터를 한 번에 조회할 수 있음.

SELECT p FROM Post p JOIN FETCH p.comments;
  • JOIN FETCH를 사용하면 게시물과 댓글을 함께 가져오기 때문에 N+1 쿼리 문제를 해결할 수 있음.
  • 페치 조인은 데이터베이스에서 실행되는 쿼리 수를 줄여 성능을 최적화할 수 있음.

3. 배치 사이즈(Batch Size) 설정

배치 사이즈를 설정하여 N+1 쿼리 대신 일정량의 데이터를 한 번에 로딩할 수 있음. Hibernate와 같은 ORM에서 제공하는 @BatchSize 어노테이션을 활용해 이를 설정할 수 있음.

@Entity
@BatchSize(size = 10)
public class Post {
    @OneToMany(fetch = FetchType.LAZY)
    private List<Comment> comments;
}
  • 배치 사이즈를 설정하면 한 번에 여러 개의 연관된 엔터티를 로딩하여 쿼리 수를 줄일 수 있음.
  • 배치 로딩은 N+1 문제를 완전히 해결하지는 않지만, 성능을 크게 개선할 수 있음.

4. 엔티티 그래프(Entity Graph) 사용

엔티티 그래프는 JPQL이나 JPA Criteria API에서 특정 엔터티의 연관 관계를 지정하여 원하는 시점에 데이터를 페치할 수 있는 기능임. 이는 N+1 문제를 해결하기 위해 필요한 연관 엔터티를 명시적으로 정의할 수 있게 해줌.

@EntityGraph(attributePaths = {"comments"})
@Query("SELECT p FROM Post p")
List<Post> findAllWithComments();
  • @EntityGraph를 사용하면 필요한 연관 관계만을 효율적으로 로딩할 수 있음.
  • 페치 전략을 명확하게 설정하여 쿼리 성능을 최적화할 수 있음.

결론

N+1 문제는 ORM을 사용하는 애플리케이션에서 자주 발생하는 성능 문제로, 주로 지연 로딩으로 인해 발생함. 이 문제를 해결하려면 즉시 로딩, 페치 조인, 배치 사이즈 설정, 엔티티 그래프와 같은 기술을 사용하여 불필요한 쿼리 호출을 줄이고 성능을 최적화할 수 있음.

profile
웹개발자 취업 준비생

0개의 댓글