Spring Boot 1.4.6.RELEASE 버전에서 개발을 진행하고 있었습니다.
album과 song이라는 테이블이 있고 song은 외래키를 가지고 있는 주 테이블이며 album은 대상 테이블입니다.
아래는 클래스 코드입니다.
@Data
@Entity
public class Album {
...
@OneToMany(mappedBy = "album")
private List<Song> songList = new ArrayList<>();
...
}
@Data
@Entity
public class Song {
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "album_id", referencedColumnName = "id")
private Album album;
...
}
이 때 Album을 조회하게 되면 Album의 전체 수 N 만큼 song을 조회하는 N개의 쿼리가 수행이 됩니다.
그래서 수정하여 N + 1 이슈를 개선하기 위해 @BatchSize를 입력하여 수행되는 쿼리의 수를 줄이려고 했습니다.
@BatchSize의 size 옵션은 최대 1000을 안 넘는 것을 권장하고 있습니다.
아래는 수정된 Album 클래스입니다.
@Data
@Entity
public class Album {
...
@BatchSize(size = "1_000")
@OneToMany(mappedBy = "album")
private List<Song> songList = new ArrayList<>();
...
}
이와 같이 사이즈를 1000을 하게 됐을 때 IN 절로 1000개의 album_id가 입력되어 1000개 보다 적은 수의 조회 결과가 발생했을 때 한 번의 쿼리가 수행될 걸 기대했습니다. 하지만 총 개수 66개에서 쿼리는 62개, 4개로 두 번 수행이 됐습니다. 저는 쿼리를 최대한 적게 수행하는 것이 부하를 줄이는 것이라 생각했지만 hibernate의 내부 동작은 이와 다른 것을 확인하여 레퍼런스를 찾아봤습니다.
다음은 레퍼런스의 내용 중 일부를 발췌했습니다.
하이버네이트 4.2 버전 문서이며 LEGACY에 보면 pre-built batch size로 1 - 10, 12, 25 를 말하고 있습니다.
조회하려는 수의 2를 나눈 몫을 배치 사이즈로 가져가는 것을 볼 수 있습니다(12).
그래서 1000을 사이즈로 정하면 다음과 같이 배치 사이즈를 가져갑니다.
1000, 500, 250, 125, 62, 31, 15, 7, 3, 1
이렇게 쿼리 배치 사이즈를 나누는 이유는 쿼리 캐시를 통해 성능 개선을 하기 위함이라고 합니다.
다음은 성능에 관련된 김영한님의 답변글 내용입니다.
보통 RDB들은 select * from x where in (?) 와 같은 preparedstatement는 미리 문법을 파싱해서 최대한 캐싱을 해둡니다.
데이터가 1개, 2개, 3개, 100개가 있으면 모두 각각 다음 처럼 최대 100개의 preparedstatement 쿼리를 만들어야 합니다.select * from x where in (?)
select * from x where in (?, ?)
select * from x where in (?, ?, ?)
select * from x where in (?, ?, ? ...)
이렇게 되면 DB 입장에서 너무 많은 preparedstatement 쿼리를 캐싱해야 하고, 성능도 떨어지게 됩니다.
그래서 하이버네이트는 이 문제를 해결하기 위해 내부에서 나름 최적화를 합니다.
100 = 설정값
50 = 100/2
25 = 50/2
12 = 25/2
그리고 1~10까지는 자주 사용하니 모두 설정
이런식으로 잡아둡니다.
batch_fetch_style을 DYNAMIC으로 한다면 제가 원하는 대로 한 번의 쿼리 수행으로 데이터를 조회하겠지만 쿼리 캐싱과 관련된 성능을 떨어뜨리게 된다고하여 LEGACY를 이용하여 사용하기로 했습니다.
hibernate 6.x (Spring boot 3.x) 부터는 배치 페치 스타일이 DYNAMIC처럼 사이즈를 기반으로 해서 IN 절에 데이터를 추가합니다.
Performs a separate SQL select to load a number of related data items using an IN-restriction as part of the SQL WHERE-clause based on a batch size
REFERENCE URIs
https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch20.html#performance-fetching-batch
'[개발] 프레임워크 > JPA' 카테고리의 다른 글
[JdbcTemplate] batchUpdate 를 통해 bulk insert를 하는 방법 (1) | 2024.12.07 |
---|---|
JPA 2차 캐시와 레디스 캐시에 대하여 생각해보기 (2) | 2024.11.21 |
JPA 1차 캐시와 2차 캐시 (1) | 2024.11.12 |
낙관적 락과 비관적 락 (2) | 2024.11.03 |
QueryDSL에 대해서 알아보자. (4) | 2024.10.20 |