JPA Pagination, 그리고 N + 1 문제
뻥튀기 되는 문제 + distinct 해결법 정리
결국엔 Batch Size로 귀결!
@Override
public Page<Board> findBoardPage(BoardRequestDto pageRequestDto) {
Pageable pageable = pageRequestDto.getPageable();
OrderSpecifier[] orderSpecifiers = createOrderSpecifier(pageRequestDto.getOrderCondition());
List<Board> boardList = queryFactory
.selectFrom(board)
.leftJoin(board.categoryBridges, categoryBridge)
.where(getSearch(pageRequestDto)
// , board.id.in(
// JPAExpressions
// .select(categoryBridge.board.id)
// .from(categoryBridge)
// .where(categoryBridge.category.id.in(pageRequestDto.getCategoryIdList()))
// )
)
.groupBy(board.id)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(orderSpecifiers)
.fetch();
JPAQuery<Long> countQuery = queryFactory
.select(board.count())
.from(board)
.leftJoin(board.categoryBridges, categoryBridge)
.where(getSearch(pageRequestDto)
// , board.id.in(
// JPAExpressions
// .select(categoryBridge.board.id)
// .from(categoryBridge)
// .where(categoryBridge.category.id.in(pageRequestDto.getCategoryIdList()))
// )
)
// .groupBy(board.id)
;
return PageableExecutionUtils.getPage(boardList, pageable, countQuery::fetchOne);
}
@Override
public Page<Board> findBoardPage(BoardRequestDto pageRequestDto) {
Pageable pageable = pageRequestDto.getPageable();
OrderSpecifier[] orderSpecifiers = createOrderSpecifier(pageRequestDto.getOrderCondition());
List<Long> boardIdList = queryFactory
.select(board.id)
.from(board)
.leftJoin(board.tagBridges, tagBridge)
.where(eqTagIdList(pageRequestDto))
.groupBy(board.id)
.fetch();
log.info("boardIdList: {}", boardIdList);
List<Board> boardList = queryFactory
.selectFrom(board)
.where(
board.id.in(boardIdList),
getSearch(pageRequestDto)
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(orderSpecifiers)
.fetch();
JPAQuery<Long> countQuery = queryFactory
.select(board.count())
.from(board)
.where(
board.id.in(boardIdList),
getSearch(pageRequestDto)
)
;
return PageableExecutionUtils.getPage(boardList, pageable, countQuery::fetchOne);
}
-> 안됨!
in 쿼리로 들어올 idList 들이 너무 많아지면 문제 생김!
다 안되는 얘기!
이걸로 해결!
@BatchSize
를 적용한다.@BatchSize: 개별 최적화
=> 이 방식을 씀application.yml
jpa:
open-in-view: true
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 100 # 위치 꼭 이곳!
dialect: org.hibernate.dialect.MySQL8Dialect
Entity
@BatchSize(size = 100)
@OneToMany(mappedBy = "board")
private List<TagBridge> tagBridges = new ArrayList<>();
해결된 사진
주테이블: board
컬렉션: tagBridges
주테이블 board_id IN 쿼리로 담아서 조회한다. size는 100개만큼!
default_batch_fetch_size 의 크기는 적당한 사이즈를 골라야 하는데, 100~1000 사이를 선택하는 것을 권장한다.
이 전략을 SQL IN 절을 사용하는데, 데이터베이스에 따라 IN 절 파라미터를 1000으로 제한하기도 한다.
1000으로 잡으면 한번에 1000개를 DB에서 애플리케이션에 불러오므로 DB에 순간 부하가 증가할 수 있다. 하지만 애플리케이션은 100이든 1000이든 결국 전체 데이터를 로딩해야 하므로 메모리 사용량이 같다.
1000으로 설정하는 것이 성능상 가장 좋지만, 결국 DB든 애플리케이션이든
순간 부하를 어디까지 견딜 수 있는지로 결정하면 된다.