[TIL] N + 1 문제 해결

냠냠빈·2024년 12월 23일

📌 N + 1 문제란 무엇인가?

개발하다 보면 "N + 1 문제"라는 용어를 자주 접하게 됩니다. 이 문제는 주로 JPA와 같은 ORM(Object Relational Mapping) 기술을 사용할 때 발생합니다. 간단히 말해, 하나의 데이터를 조회하는데 필요한 쿼리와 함께 관련된 데이터를 조회하기 위해 추가적으로 N개의 쿼리가 실행되는 상황을 의미합니다.

🧐 왜 N + 1 문제가 발생할까?

  1. 연관된 엔티티를 가져올 때
  • 예를 들어, 게시글 데이터를 가져오고 각 게시글의 작성자 데이터를 조회한다고 가정해봅시다.
  • 게시글 10개를 가져오는 데 1개의 쿼리가 필요합니다.
  • 이후 각 게시글의 작성자를 가져오는 데 10개의 추가 쿼리가 실행됩니다.
  • 결과적으로 1(게시글) + 10(작성자) = 11개의 쿼리가 발생합니다.
  1. FetchType.LAZY의 기본 동작
  • 연관된 엔티티는 기본적으로 지연 로딩(LAZY)으로 설정되며, 접근 시점에 데이터를 가져옵니다.
  • 따라서, 각 게시글의 작성자를 개별 쿼리로 조회하게 됩니다.

🔍 프로그램에 미치는 영향

  • 성능 저하: 많은 쿼리가 실행되면서 응답 속도가 느려지고, 데이터베이스에 부하를 줍니다.
  • 유지보수 어려움: 쿼리 수를 추적하고 최적화하기 어려워집니다.

💡 N + 1 문제를 해결하는 방법

이 문제를 해결하기 위해 몇 가지 방법이 있습니다.

1. FetchType.EAGER 사용
FetchType.EAGER를 설정하면 연관된 데이터를 한 번의 쿼리로 가져올 수 있습니다. 하지만, 모든 경우에 EAGER를 사용하는 것은 권장되지 않습니다.

  • 장점: 관련 데이터를 한꺼번에 가져와 N + 1 문제가 발생하지 않음.
  • 단점: 불필요한 데이터를 가져오거나 연관 데이터가 많을 경우 성능 문제가 발생.

2. JPQL 또는 Fetch Join 사용
JPQL의 join fetch 구문을 사용하면 필요한 데이터만 한 번의 쿼리로 가져올 수 있습니다.

@Query("SELECT p FROM Post p JOIN FETCH p.author")
List<Post> findAllWithAuthor();
  • 예시: 게시글(Post)과 작성자(Author)를 한 번의 쿼리로 가져옵니다.
  • 장점: 쿼리 수를 줄이고 필요한 데이터만 효율적으로 조회.

3. Batch Size 설정
JPA에서 default_batch_fetch_size를 설정하면 N + 1 문제를 효과적으로 해결할 수 있습니다. 이 설정은 지연 로딩 시 한 번에 가져오는 엔티티 수를 제한합니다.

hibernate.default_batch_fetch_size=10
  • 장점: 여러 개의 지연 로딩 쿼리를 묶어 한 번에 처리.
  • 예시: 게시글과 작성자를 한 번에 가져오는 쿼리로 최적화.

4. DTO를 활용한 데이터 조회
필요한 데이터만 선택적으로 가져오기 위해 DTO(Data Transfer Object)를 사용합니다.

@Query("SELECT new com.example.dto.PostWithAuthorDTO(p.title, a.name) FROM Post p JOIN p.author a")
List<PostWithAuthorDTO> findPostWithAuthor();
  • 장점: 필요한 필드만 조회하여 불필요한 데이터 전송 방지.

📘 N + 1 문제 해결 예시

문제 상황

@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    private Author author;
}

@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
}
  • 게시글(Post)과 작성자(Author) 관계가 @ManyToOne으로 설정되어 있습니다.
  • 기본적으로 FetchType.LAZY로 설정되었으므로 N + 1 문제가 발생합니다.

해결 코드 (Batch Size)

@Configuration
public class JpaConfig {

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
        adapter.getJpaPropertyMap().put("hibernate.default_batch_fetch_size", 10);
        return adapter;
    }
}

참고문헌

N+1 문제란? 그리고 해결방법 (feat. fetch join)

profile
다 먹어버릴거야!

0개의 댓글