Spring/Java

[Spring] @BatchSize, default_batch_fetch_size

java곰 2024. 7. 17. 22:33

먼저 @BatchSize, default_batch_fetch_size 모두 N+1 문제를 해결하기 위한 방법들입니다. 그래서 N+1이 무엇인지부터 알아봅시다.

N+1 문제란?

N+1 문제는 ORM(Object-Relational Mapping)을 사용할 때 흔히 발생하는 성능 이슈입니다. 이는 하나의 쿼리로 N개의 결과를 가져온 후, 각 결과에 대해 추가적인 쿼리를 수행하여 총 N+1번의 쿼리가 실행되는 현상을 말합니다.

Fetch Join으로 N+1 문제 해결하기

Fetch Join은 일반적으로 N+1 문제를 해결하는 효과적인 방법입니다. 하지만 OneToMany 관계에서는 주의가 필요합니다.

@Query("SELECT p FROM Post p JOIN FETCH p.comments") 
List<Post> findAllWithComments();

OneToMany 관계에서 Fetch Join의 문제점

  1. 데이터 중복: OneToMany 관계에서 Fetch Join을 사용하면 결과 데이터가 중복될 수 있습니다. 예를 들어, 하나의 게시글에 여러 댓글이 있다면, 게시글 정보가 댓글 수만큼 중복됩니다.
  2. 메모리 사용량 증가: 중복된 데이터로 인해 메모리 사용량이 크게 증가할 수 있습니다.
  3. 페이징 문제: JPA는 메모리에서 페이징을 수행하므로, 대량의 데이터에서 성능 저하가 발생할 수 있습니다.

OneToMany 관계에서 N+1 문제 해결 방안

EntityGraph 사용

@EntityGraph(attributePaths = {"comments"}) 
@Query("SELECT p FROM Post p") 
List<Post> findAllWithComments();
EntityGraph를 사용하면 Fetch Join과 유사하지만 좀 더 유연한 방식으로 연관 엔티티를 조회할 수 있습니다.
@NamedEntityGraph를 사용하여 더 세밀하게 제어할 수 있습니다.

서브쿼리 사용

 

    @Query("SELECT p FROM Post p " +
           "LEFT JOIN FETCH (SELECT c FROM Comment c WHERE c.post = p ORDER BY c.createdAt DESC) AS comments " +
           "WHERE p.id = :postId")
    Optional<Post> findPostWithComments(@Param("postId") Long postId);

이 방식은 두 번의 쿼리로 필요한 모든 데이터를 가져온 후, 메모리에서 매핑합니다.

@BatchSize 사용

@Entity 
public class Post { 
	@OneToMany(mappedBy = "post") 
	@BatchSize(size = 100) 
	private List<Comment> comments; 
}

@BatchSize를 사용하면 지정된 크기만큼 IN 절로 쿼리를 실행합니다. 이는 쿼리 횟수를 줄이는 데 효과적입니다.

OneToMany 관계마다 작성하여 batch size를 각각 다르게 설정할 수 있지만

spring:
  jpa:
  	properties:
      hibernate:
        default_batch_fetch_size: 1000

설정파일에서 전역으로 batch size를 설정하여 일괄적으로 적용할 수 있습니다.

약 1000개의  데이터로 테스트한 결과입니다.

100번의 요청을 보낸 결과 약 60퍼센트의 시간이 단축된 것을 확인할 수 있습니다.