Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- springdatajpa
- Android
- 백준
- java
- AOP
- 김영한
- 스프링
- jpa
- kotlin
- Proxy
- QueryDSL
- Exception
- Greedy
- http
- 자바
- 그리디
- SpringBoot
- JPQL
- transaction
- 스프링 핵심 기능
- 알고리즘
- Thymeleaf
- spring
- 인프런
- 스프링 핵심 원리
- db
- JDBC
- Spring Boot
- Servlet
- pointcut
Archives
- Today
- Total
개발자되기 프로젝트
컬렉션 조회 최적화 :페이징 & batch_fetch 본문
1. 페이징 한계
- 컬렉션을 페치 조인하면 페이징이 불가능함
- 컬렉션을 페이 조인하면 일대다 조인이 발생하여 데이트가 예측할 수 없이 증가한다.
- 일대다에서 "일"을 기준으로 페이징을 하는 것이 목적인데
- DATA는 "다"를 기준으로 DB에서 row가 생성된다..
- 즉 Order 기준으로 페이징을 하고싶지만.. "다"인 OrderItem을 조인하면 "다"가 기준임..
- 이 경우에 페이징을 시도하면 데이터를 싹 메모리에 올려서 페이징을 시도한다.
- ㅎㅎㅎㅎㅎ심각하다.
2. hibernate.default_batch_fetch_size, @BatchSize
- 그러면, 페이징 + 컬렉션 엔티티 함께 조회하고 싶으면 어떻게 해야함..
- xToOne 관계를 모두 fetch join함. ToOne관계를 row수를 증가시키지 않아 페이징쿼리에 영향 안줌.
- ToOne은 싹다 fetch join으로 걸어서 한방쿼리로 만들어.
- 컬렉션은 지연로딩으로 가져와. 즉 컬렉션을 fetch join하지말고
- hibernate.default_batch_fetch_size(항상켜둬 ㅋㅋ), @BatchSize를 적용하자.
- application.yml에 옵션 추가
jpa:
hibernate:
ddl-auto: create-drop
Properties:
hibernate:
default_batch_fetch_size: 100
show_sql: true
format_sql: true
@GetMapping("/api/v3.1/orders")
public Result ordersV3_page(@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "limit", defaultValue = "100") int limit){
List<Order> orders = orderRepository.findAllWitMemberDelivery(offset, limit);
List<OrderDto> result = orders.stream().map(o -> new OrderDto(o)).collect(Collectors.toList());
return new Result(result);
}
- 쿼리를 보자!
- fetch join과 관련된 쿼리가 나가고
Hibernate:
select
order0_.order_id as order_id1_6_0_,
member1_.member_id as member_i1_4_1_,
delivery2_.delivery_id as delivery1_2_2_,
order0_.delivery_id as delivery4_6_0_,
order0_.member_id as member_i5_6_0_,
order0_.order_date as order_da2_6_0_,
order0_.status as status3_6_0_,
member1_.city as city2_4_1_,
member1_.street as street3_4_1_,
member1_.zipcode as zipcode4_4_1_,
member1_.name as name5_4_1_,
delivery2_.city as city2_2_2_,
delivery2_.street as street3_2_2_,
delivery2_.zipcode as zipcode4_2_2_,
delivery2_.status as status5_2_2_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id
inner join
delivery delivery2_
on order0_.delivery_id=delivery2_.delivery_id limit ?
- order_item을 가져오는 쿼리가 나가는데..?
- 응? orderItems_order_id in ( ?, ?) 뭐지?
- 파라미터를 보자.
- orderId(id = 4, id = 11)에 해당하는 orderItems를 모두 가지고 온것.
- 즉, userA, userB의 orderItem를 모두 가지고온다.
Hibernate:
select
orderitems0_.order_id as order_id5_5_1_,
orderitems0_.order_item_id as order_it1_5_1_,
orderitems0_.order_item_id as order_it1_5_0_,
orderitems0_.count as count2_5_0_,
orderitems0_.item_id as item_id4_5_0_,
orderitems0_.order_id as order_id5_5_0_,
orderitems0_.order_price as order_pr3_5_0_
from
order_item orderitems0_
where
orderitems0_.order_id in (
?, ?
)
select
orderitems0_.order_id as order_id5_5_1_, orderitems0_.order_item_id
as order_it1_5_1_, orderitems0_.order_item_id as order_it1_5_0_,
orderitems0_.count as count2_5_0_, orderitems0_.item_id as item_id4_5_0_,
orderitems0_.order_id as order_id5_5_0_, orderitems0_.order_price as order_pr3_5_0_
from order_item orderitems0_
where orderitems0_.order_id in (4, 11);
- 즉 정리하면 batch_size를 지정하게되면
- 컬렉션(여기서는 orders)를 조회할 때 해당 컬렉션(order)관련된 orderItems를 모두 가져오도록 in쿼리를 통해 한번에 가져옴
- 그 때 in 로 가져올 개수를 지정해서 미리 땡겨서 가져옴.
- 그럼 item은???
- item도 orders와 관련된 모든 item을 한번에 가져옴
Hibernate:
select
item0_.item_id as item_id2_3_0_,
item0_.name as name3_3_0_,
item0_.price as price4_3_0_,
item0_.stock_quantity as stock_qu5_3_0_,
item0_.artist as artist6_3_0_,
item0_.etc as etc7_3_0_,
item0_.author as author8_3_0_,
item0_.isbn as isbn9_3_0_,
item0_.actor as actor10_3_0_,
item0_.director as directo11_3_0_,
item0_.dtype as dtype1_3_0_
from
item item0_
where
item0_.item_id in (
?, ?, ?, ?
)
select
item0_.item_id as item_id2_3_0_, item0_.name as name3_3_0_, item0_.price as price4_3_0_,
item0_.stock_quantity as stock_qu5_3_0_, item0_.artist as artist6_3_0_,
item0_.etc as etc7_3_0_, item0_.author as author8_3_0_, item0_.isbn as isbn9_3_0_,
item0_.actor as actor10_3_0_, item0_.director as directo11_3_0_,
item0_.dtype as dtype1_3_0_
from item item0_
where item0_.item_id in (2, 3, 9, 10);
3. 그러면 전부 fetch join해서 가지고 오는 것 보다 장점은?
public List<Order> findAllWithItem(){
List<Order> resultList = em.createQuery("select distinct o from Order o " +
"join fetch o.member m " +
"join fetch o.delivery d " +
"join fetch o.orderItems oi " +
"join fetch oi.item i", Order.class)
.getResultList();
return resultList;
}
컬렉션을 싹 다 fetch join으로 직접 가지고 오면
쿼리는 한번에 끝나지만! 중복된 데이터도 많고 데이터 양 자체가 늘어난다.
반면 batch size를 적용하면
쿼리는 한 번은 아니지만 중복된 데이터도 줄고 양 자체가 줄어든다. 오히려 좋아.
3. batch_fetch를 적용하면 ToOne에 해당하는 fetch join을 빼도 최적화가 된다.
- 이래도 된다고?
public List<Order> findAllWitMemberDelivery(int offset, int limit) {
return em.createQuery(
"select o from Order o " , Order.class)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
}
오 in쿼리로 한번에 불러오긴 하는데... 쿼리가 많이 줄진 않네. 네트워크 많이탈듯.
ToOne은 그냥 fetch join으로 하자. ㅋㅋㅋ
4. 정리
- 장점
- 쿼리 호출 수가 1 + N -> 1 + 1 로 최적화됨
- 조인보다 DB데이터 전송량이 최적화 된다.
(Order와 OrderItem을 조인하면 Order가 OrderItem만큼 중복해서 조회된다.
이 방법은 각각 조쇠하기 때문에 전송해야할 중복 데이터가 없다.) - 페이 조인방식과 비교해서 쿼리 호출 수가 약간 증가하지만, DB데이터 전송량이 감소한다.
- 컬렉션 페치 조인은 페이징이 불가능 하지만, 이 방법은 페이징이 가능하다.
- 결론
- ToOne 관계는 페지 조인해도 페이징에 영향을 주지 않는다. 따라서 ToOne관계는 페치 조인으로 쿼리 수르 ㄹ줄여 해결하고, 나머지(컬렉션)는 hibernate.default_batch_fetch_size로 최적화 하자.
- 그럼 사이즈는 몇으로 해야함?
- DB에 따라 다르긴 한데 max1000임
- 그럼 적당한 사이즈? 100~ 1000 사이
- 참고로 애플리케이션은 100이든, 1000이든 결국 전체 데이터를 로딩해야 하므로 메모리 사용량은 같다.
- 결국 db든 애플리케이션이든 순간 부하를 어디까지 견딜 수 있는지로 결정해야 함.
5. GitHub : 210824 collection, batch_fetch
'인프런 > [인프런] Springboot와 JPA활용 2' 카테고리의 다른 글
컬렉션 조회 : 최적화 (0) | 2021.08.25 |
---|---|
컬렉션 조회: DTO 직접 조회 (0) | 2021.08.24 |
컬렉션 조회 최적화 : fetch join 적용 (0) | 2021.08.24 |
컬렉션 조회 최적화 (0) | 2021.08.24 |
지연 로딩과 조회 성능 최적화(JPA에서 DTO 바로 조회) (0) | 2021.08.23 |
Comments