지난 포스팅에서는 JOIN FETCH로 조회API 성능최적화를 해보았다. JOIN FETCH의 문제는 일대다 관계에서 컬렉션을 페이징하지 못함에 있다. 페이징이 필요없다면 상관없지만 페이징이 필요하다면 다른 방법이 필요하다.
4. default_batch_fetch_size로 최적화하기
JOIN FETCH로 일대다 연관관계의 페이징이 안되면 깔끔히 포기하자. 대신 Lazy 페치전략을 향상시켜보자.
JOIN FETCH를 사용했던 이유는 일대다 관계에서 '일'이 '다'에 반복적으로 참조하며 잦은 SELECT문을 발생시키기 때문이다. JPA는 이런 성능저하를 방지하는 기능을 제공한다.
application.yml에 설정을 하나 추가해보겠다.
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
datasource:
url: jdbc:h2:tcp://localhost/~/jpashop
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 100 # 추가!
logging:
level:
org.hibernate.SQL: debug
default_batch_fetch_size는 참조할 때마다 엔티티를 fetch하지 않고 버퍼에 보관하는 설정이다. 일대다 관계는 동일한 엔티티를 반복적으로 참조하므로 잠시 버퍼에 대기해두었다가 설정한 값이상 넘어가면 IN절로 묶어 하나의 SELECT문을 생성한다. SELECT문이 N개가 IN절로 묶여 1개가 된다.
( 자세한 원리는 위 포스팅을 참고바란다. )
그럼 조회API를 개선해보자.
Controller 조회API
@GetMapping("api/v3.1/orders")
public List<OrderDto> ordersV3_page(
@RequestParam(value="offset", defaultValue= "0") int offset,
@RequestParam(value="limit", defaultValue="100") int limit
){
List<Order> orders = orderRepository.findAllWithMemberDeliver(offset,limit); ;//To0NE 관계는 JOIN FETCH로 가져온다.
return orders.stream()
.map(OrderDto::new)
.collect(toList());
}
Order 엔티티는 Member 엔티티와 Delivery 엔티티와 xToOne 연관관계이다. xToOne 연관관계는 JOIN FETCH에 페이징이 가능하므로 JOIN FETCH를 수행한다. 그리고 일대다 관계는 '참조'를 통해 Lazy 페치한다. 페이징된 Order 엔티티가 참조하는 컬렉션만 조회하면 컬렉션 전체가 아닌 부분만 메모리에 로딩된다. 이는 페이징이다.
Order 엔티티가 참조하는 컬렉션의 원소 개수가 N개라면 N개의 SELECT문이 실행되어야 한다. 그러나 우리는 이미 fetch 관련 설정을 해놓았다. 100개까지는 IN절로 묶여 하나의 쿼리로 만들어 진다. 사이즈는 상황에 맞게 설정하면 된다. DB 중에 IN절 파라미터를 1000개로 제한하는 DB가 있으니 default_batch_fetch_size는 10개-1000개 사이로 설정하는 것이 좋다. size를 크게 잡을수록 쿼리가 적게 나갈 가능성이 높으니 성능에는 좋지만 DB에 부하가 적게 걸리는 적당선을 찾아야 한다.
정리하면, 실행되는 쿼리는 총 2개이다.
1) xToOne 연관관계 JOIN FETCH : 1개
2) xToMany 향상된 Lazy Fetch : 1개
xToMany 연관관계가 하나가 아니라 둘이라면 실행되는 쿼리의 수는 3개가 된다. N+1개의 쿼리가 아닌 2-3개의 쿼리로 원하는 데이터를 가져올 수 있어 성능이 크게 향상된다. 이렇듯, 일대다 관계에서 페이징이 필요하면 JOIN FETCH를 버리고 Lazy fetch를 향상시키는 전략으로 가는 것이 좋다.
참고로, default_batch_fetch_size는 전체에 적용되는 설정이다. 만약 특정 연관관계나 엔티티에만 적용하고 싶다면 @BatchSize 어노테이션을 활용하면 된다. @BatchSize는 SELECT문에 IN절을 추가하는 어노테이션이다. 관련해서는 아래 포스팅에 잘 설명되어 있다.
'JPA > JPA Basic' 카테고리의 다른 글
[JPA] OSIV ( Open Session In View ) (0) | 2023.06.30 |
---|---|
[JPA] 조회API 성능최적화하기( XToMany ) (3) - DTO 직접조회 (0) | 2023.06.29 |
[JPA] 조회API 성능최적화하기 ( XToMany ) (1) - DTO, JOIN FETCH (0) | 2023.06.29 |
[JPA] 조회 API 성능 최적화하기 ( XToOne ) (0) | 2023.06.28 |
[JPA] DTO의 필요성 (0) | 2023.06.28 |