컬렉션 패치 조인

2023. 6. 12. 17:14BackEnd(Java)/Spring Data JPA

✅ 아래 내용들에 대해서 알아보자

- 컬렉션 패치 조인
- 컬렉션 패치조인 문제점
- 컬렉션 패치조인 한계
- 컬렉션 패치조인 최적화

 

 

컬렉션 패치 조인

 아래 코드를 보면 Order 엔티티와 OrderItem 엔티티가 N:1인 관계 OnetoMany로 구성되어 있고, Order와 orderItems를 join fetch로 컬렉션 패치 조인 하고 있는 것을 확인할 수 있다. 

 

public List<Order> findAllWithItem() {
        return em.createQuery("select distinct o from Order o"
                + " join fetch o.member m"
                + " join fetch o.delivery d"
                + " join fetch o.orderItems oi", Order.class)
            .getResultList();
    }

 

 

실제 실행 쿼리 값을 보면 inner join으로 쿼리가 수행되는 것을 확인할 수 있다.

 select
        distinct order0_.order_id as order_id1_6_0_,
        member1_.member_id as member_i1_4_1_,
        delivery2_.delivery_id as delivery1_2_2_,
        orderitems3_.order_item_id as order_it1_5_3_,
        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_,
        orderitems3_.count as count2_5_3_,
        orderitems3_.item_id as item_id4_5_3_,
        orderitems3_.order_id as order_id5_5_3_,
        orderitems3_.order_price as order_pr3_5_3_,
        orderitems3_.order_id as order_id5_5_0__,
        orderitems3_.order_item_id as order_it1_5_0__ 
    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 
    inner join
        order_item orderitems3_ 
            on order0_.order_id=orderitems3_.order_id

 

 

컬렉션 패치 문제점

onetoMany 컬렉션 패치 조인 시 문제점이 있는데.. 바로 페이징 처리가 안된다는 문제점이 있다!!

public List<Order> findAllWithItem() {
        return em.createQuery("select distinct o from Order o"
                + " join fetch o.member m"
                + " join fetch o.delivery d"
                + " join fetch o.orderItems oi", Order.class)
            .setFirstResult(1)
            .setMaxResults(100)
            .getResultList();
    }

 

위의 실제 쿼리를 실제 확인해 보면 limit, offset 쿼리가 적용 안 되는 것을 볼 수 있다.

 select
        distinct order0_.order_id as order_id1_6_0_,
        member1_.member_id as member_i1_4_1_,
        delivery2_.delivery_id as delivery1_2_2_,
        orderitems3_.order_item_id as order_it1_5_3_,
        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_,
        orderitems3_.count as count2_5_3_,
        orderitems3_.item_id as item_id4_5_3_,
        orderitems3_.order_id as order_id5_5_3_,
        orderitems3_.order_price as order_pr3_5_3_,
        orderitems3_.order_id as order_id5_5_0__,
        orderitems3_.order_item_id as order_it1_5_0__ 
    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 
    inner join
        order_item orderitems3_ 
            on order0_.order_id=orderitems3_.order_id

 

하이버네이트는 경고 로그를 남기면서 모든 데이터를 DB에서 읽어오고, 메모리에서 페이징을 해버리게 되는 이슈가 생김

하이버네이트 경고 로그

 

 

컬렉션 패치조인 한계

  1. 컬렉션 페치 조인은 1개만 사용이 가능하다 -> 컬렉션 둘 이상에 패치조인 사용하면 안 됨, 데이터 부정합 현상이 나타나게 된다.
  2. 페이징 처리를 할 수 없다.
  3. 컬렉션 패치 조인시 DB 조인으로 인해 데이터가 뻥튀기된다(N 테이블의 개수에 맞게 됨)
  4. DB 데이터를 메모리에서 적재해 페이징 하는 성능 이슈가 발생하게 됨

 

 

컬렉션 패치조인 최적화

  컬렉션 패치조인하면 페이징이 불가능하다는 이슈를 알게 되었다.

그러면 어떻게하면 페이징 + 컬렉션 엔티티 조회를 최적화를 할 수 있을까?? 같이 알아보도록 하자. ✌✌

 

1. 먼저 ToOne(OneToOne, ManyToOne) 관계를 모두 패치조인한다. ToOne관계는 row 수를 증가시키지 않으므로 페이징 쿼리에 영향을 주지 않는다.(여기서는 Member, Delivery 엔티티)

 

To One 관계 패치조인 및 실행 결과

 

 

2. 컬렉션 toMany(OneToMany, ManyToMany)는 지연 로딩으로 조회한다.

orderItems 지연로딩 및 실행 결과

 

 

3. 위의 2번 과정시 1+N 문제가 발생하게 된다. 따라서 지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size, @BatchSize를 적용한다.

  • hibernate.default_batch_fetch_size : 글로벌 설정
  • @BatchSize: 개별 엔티티 최적화
  • 이 옵션들을 사용하면 컬렉션이나, Proxy 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회한다.
  • default_batch_fetch_size는 100~1000 사이를 권장한다. DB에 따라 다르지만 In 절 파라미터를 1000으로 제한하기도 한다. 또한 1000 이상 잡으면 DB에서 데이터를 가져와 애플리케이션에 불러오므로 순간 DB 부하가 증가할 수 도 있다

 

옵션 설정

 

정리

  • 컬렉션 엔티티 쿼리 호출수가 '1 + N -> 1 + 1'로 최적화 된다.
  • 조인보다 DB 데이터 전송량이 최적화된다.
  • 패치 조인 방식과 비교해서 쿼리 호출수가 약간 증가하지만, DB 데이터 전송량이 감소한다
  • 컬렉션 페치 조인은 페이징이 불가능하지만 이 방법은 페이징이 가능하다.
  • ToOne 관계는 패치 조인해도 페이징에 영향을 주지 않는다. 따라서 ToOne 관계는 패치조인으로 LazyLoading, 1+N 문제를 해결하고, 나머지는 hibernate.default_batch_fetch_size 옵션으로 최적화하자
반응형