지연 로딩과 조회 성능 최적화

slee2·2022년 3월 17일
0

주문 + 배송정보 + 회원을 조회하는 API를 만들자.
지연 로딩 때문에 발생하는 성능 문제를 단계적으로 해결해보자.

지금부터 하는 설명은 정말 중요하다고 언급
잘 안들으면 인생 허비스

V1 - 엔티티 직접 노출

음... 이렇게 쓰면 엔티티가 외부에 노출되는데, 이때 Member -> Order -> Member -> ... 무한루프를 돌게 된다.
그러므로 엔티티를 직접 노출할 때는 양방향 연관관계가 걸린 곳은 꼭 한곳을 @JsonIgnore처리해야 한다.

하지만, 그냥 DTO를 쓰는 것이 훨씬 좋다.
@JsonIgnore를 사용해도, LAZY 설정을 했다면, 해당 객체를 프록시로 생성하기 때문에 이것과 관련된 바이트예외가 던져진다. 이를 해결하기 위해
강의에서 Hibernate5Modle을 사용해서 더 수월하게 보내는 방법을 설명했지만, 그냥 DTO로 따로 보내는것이 훨씬 유지보수에 좋아 보인다.

V2 - 엔티티 DTO 변환

이 부분의 문제는
반복문을 돌면서
Order 하나 찾는 쿼리를 동작하면서
order.getMember().getName();
order.getDelivery().getAddress();
이 부분을 돌때마다 쿼리를 돌려야한다.
즉, 하나의 상품을 돌때 쿼리를 1 + 2번 날려야한다.
두개의 상품은 쿼리를 1 + 4번 날려야한다.
10개면 1 + 20번 날려야한다.

이게 N + 1 문제이다.

그러므로 성능에 문제가 발생할 수 있다는 것.

V3 - 페치 조인 최적화

이렇게 할 경우, 쿼리를 처음 한 번만 실행하게 된다.

V2

2022-03-17 09:18:13.346 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL                        : 
    select
        order0_.order_id as order_id1_6_,
        order0_.delivery_id as delivery4_6_,
        order0_.member_id as member_i5_6_,
        order0_.order_date as order_da2_6_,
        order0_.status as status3_6_ 
    from
        orders order0_ 
    inner join
        member member1_ 
            on order0_.member_id=member1_.member_id limit ?
Hibernate: 
    select
        order0_.order_id as order_id1_6_,
        order0_.delivery_id as delivery4_6_,
        order0_.member_id as member_i5_6_,
        order0_.order_date as order_da2_6_,
        order0_.status as status3_6_ 
    from
        orders order0_ 
    inner join
        member member1_ 
            on order0_.member_id=member1_.member_id limit ?
2022-03-17 09:18:13.396 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL                        : 
    select
        member0_.member_id as member_i1_4_0_,
        member0_.city as city2_4_0_,
        member0_.street as street3_4_0_,
        member0_.zipcode as zipcode4_4_0_,
        member0_.name as name5_4_0_ 
    from
        member member0_ 
    where
        member0_.member_id=?
Hibernate: 
    select
        member0_.member_id as member_i1_4_0_,
        member0_.city as city2_4_0_,
        member0_.street as street3_4_0_,
        member0_.zipcode as zipcode4_4_0_,
        member0_.name as name5_4_0_ 
    from
        member member0_ 
    where
        member0_.member_id=?
2022-03-17 09:18:13.403 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL                        : 
    select
        delivery0_.delivery_id as delivery1_2_0_,
        delivery0_.city as city2_2_0_,
        delivery0_.street as street3_2_0_,
        delivery0_.zipcode as zipcode4_2_0_,
        delivery0_.status as status5_2_0_ 
    from
        delivery delivery0_ 
    where
        delivery0_.delivery_id=?
Hibernate: 
    select
        delivery0_.delivery_id as delivery1_2_0_,
        delivery0_.city as city2_2_0_,
        delivery0_.street as street3_2_0_,
        delivery0_.zipcode as zipcode4_2_0_,
        delivery0_.status as status5_2_0_ 
    from
        delivery delivery0_ 
    where
        delivery0_.delivery_id=?
2022-03-17 09:18:13.408 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL                        : 
    select
        member0_.member_id as member_i1_4_0_,
        member0_.city as city2_4_0_,
        member0_.street as street3_4_0_,
        member0_.zipcode as zipcode4_4_0_,
        member0_.name as name5_4_0_ 
    from
        member member0_ 
    where
        member0_.member_id=?
Hibernate: 
    select
        member0_.member_id as member_i1_4_0_,
        member0_.city as city2_4_0_,
        member0_.street as street3_4_0_,
        member0_.zipcode as zipcode4_4_0_,
        member0_.name as name5_4_0_ 
    from
        member member0_ 
    where
        member0_.member_id=?
2022-03-17 09:18:13.414 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL                        : 
    select
        delivery0_.delivery_id as delivery1_2_0_,
        delivery0_.city as city2_2_0_,
        delivery0_.street as street3_2_0_,
        delivery0_.zipcode as zipcode4_2_0_,
        delivery0_.status as status5_2_0_ 
    from
        delivery delivery0_ 
    where
        delivery0_.delivery_id=?
Hibernate: 
    select
        delivery0_.delivery_id as delivery1_2_0_,
        delivery0_.city as city2_2_0_,
        delivery0_.street as street3_2_0_,
        delivery0_.zipcode as zipcode4_2_0_,
        delivery0_.status as status5_2_0_ 
    from
        delivery delivery0_ 
    where
        delivery0_.delivery_id=?

V3

2022-03-17 09:19:05.854 DEBUG 72007 --- [nio-8080-exec-6] org.hibernate.SQL                        : 
    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
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

한 번에 전부 조회하여 넣어주기 때문에 쿼리를 한 번만 사용하는 것이다.
그로 인해 성능도 굉장히 빨라지게 된다.

하지만, 모든 데이터를 찍어야하는 단점이 존재한다.

V4 - JPA에서 DTO 바로 조회

2022-03-17 09:38:13.036 DEBUG 73510 --- [nio-8080-exec-5] org.hibernate.SQL                        : 
    select
        order0_.order_id as col_0_0_,
        member1_.name as col_1_0_,
        order0_.order_date as col_2_0_,
        order0_.status as col_3_0_,
        delivery2_.city as col_4_0_,
        delivery2_.street as col_4_1_,
        delivery2_.zipcode as col_4_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
Hibernate: 
    select
        order0_.order_id as col_0_0_,
        member1_.name as col_1_0_,
        order0_.order_date as col_2_0_,
        order0_.status as col_3_0_,
        delivery2_.city as col_4_0_,
        delivery2_.street as col_4_1_,
        delivery2_.zipcode as col_4_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

딱 필요한 데이터만 정확히 쿼리로 가져와서 Dto에 넣어주는 방식이다.
더 최적화되었지만, V3이 좋은지 V4가 좋은지는 가리기가 애매하다.

V3의 경우에는 fetch join로 인해 결과를 만들었고, 이 데이터들은 재사용성이 가능하다. 단점은 한번에 많은 데이터를 가져온다는 점이다.

V4의 경우에는 딱 원하는 데이터만 가져오기 때문에 성능성에서는 좋다는 장점이 있다. 하지만, 이 Dto 전용이므로, 재사용을 할 수 없고, 코드도 지져분하다는 단점을 가지고 있다.

또, V3의 경우에는 Order엔티티를 가져오기 때문에 계층이 유지되지만, V4에서 리포지토리에 다른 Dto를 사용한다는 것 자체가 논리적으로 계층이 깨진다는 것을 볼 수 있다.

강사의 개인적인 의견으로는 V3V4는 사실상 성능차이가 크지 않기 때문에 V3를 선호하는 것 같다. 요즘에는 네트워크도 좋다는 점과, select가 많이 들어간다고 해서 성능이 많이 저하되지는 않고, 그 밑의 from이나 다른 명령어들이 성능을 먹는 것이라고 말씀하심.

만약 V4를 사용하고 싶다면,
해당 메서드를 OrderSimpleQueryRepository 를 통해 리포지토리를 하나 만드는 것이 좋다고 하심.

권장 순서
1. 우선 엔티티를 DTO로 변환하는 방법을 선택한다.
2. 필요하면 패치 조인(V3)으로 성능을 최적화 한다. -> 대부분의 성능 이슈 해결
3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용.(V4)
4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용.

0개의 댓글

관련 채용 정보