프로젝트 회고

Optimizer 中 N+1 문제 해결 시 검색 속도 향상

iksadnorth 2023. 11. 29. 21:09

👣 개요

N+1 문제 해결 시 검색 속도 향상.
의도적으로 Dto에서 요구하는 엔티티를 Lazy Fetch로 불러와서
N+1 쿼리를 내보내도록 유도함.
그리고 다양한 방법으로 N+1 문제를 해결하면서 성능 향상을 확인.

 

👣 실험 계획

위 이미지에서 Member 테이블의 ID 칼럼의 1인 데이터와 연관된 
Wish 데이터의 갯수는 113개 이다.

즉, 만약 Lazy Fetch로 ID가 1인 Member의 Wish 데이터를 불러오면
1[Member Query] + 113[Wish Query] 개의 쿼리가 불러와질 예정이다.

위와 같이 하나의 Member 정보를 불러올 때, 
해당 Member가 Wish 리스트에 추가한 Item들을 함께 불러올 수 있는
DTO인 MemberResponse를 만들고 MemberService 구현체에서는
다양한 전략으로 해당 DTO값을 출력할 것이다.

실험에 사용되는 테스트 코드는 위와 같이
순차적으로 N번 메서드를 호출하고 해당 실행 시간을 확인하는 형태로 진행된다.

 

👣 실험 수행

⚗️ 대조군 - Lazy FetchType으로 DTO 생성 시점에서 동적으로 쿼리 출력.

위와 같이 Service Layer에서 동적으로
Member의 Wish 엔티티마다 DTO를 만들도록
코드를 구성한다면, 총 (1 + 113)개의 쿼리를 호출하게 만들 수 있다.

1000번 정도 메서드를 호출했을 때의 결과다.

43708 ms, 즉 43.708초가 걸림을 알 수 있다.

 

⚗️ 실험군 1 - Eager FetchType으로 DTO 생성 시점에서 한꺼번에 쿼리 호출.

대조군과 코드는 완벽히 동일하나
Wish 엔티티에서 Item 엔티티와의 관계 설정 시, 
Lazy FetchType을 Eager FetchType으로 변경하고 성능을 측정한 결과다.

위와 같이 코드를 구성한다면, 총 (1 + 1)개의 쿼리를 호출하게 만들 수 있다.

1000번 정도 메서드를 호출했을 때의 결과다.

3582 ms, 즉 3.582초가 걸림을 알 수 있다.

 

⚗️ 실험군 2 - Fetch Join으로 쿼리 1개로 모든 엔티티 취득.

위와 같은 설정으로 아예 쿼리에 의해 엔티티가 전달될 때, 
Fetch Join을 사용하면 필요한 연관 관계의 엔티티까지 함께 가져올 수 있다.

위 사진은 해당 쿼리에 의해 Fetch Join되어 모든 엔티티를 한번에 가져오는 모습이다.

1000번 정도 메서드를 호출했을 때의 결과다.

3635 ms, 즉 3.635초가 걸림을 알 수 있다.

 

⚗️ 실험군 3 - Batch Size를 조절해 한번에 가져오기.

대조군과 코드는 완벽히 동일하나

Batch Size를 113보다 큰 1000으로 설정했을 때의 성능 결과이다.

1000번 정도 메서드를 호출했을 때의 결과다.

5920 ms, 즉 5.920초가 걸림을 알 수 있다.

 

⚗️ 실험군 4 - EntityGraph를 사용해 그래프 탐색

위와 같이, @EntityGraph 어노테이션으로 같이 가져올 엔티티 그래프를 지정하면
원하는 연관 관계의 모든 그래프들을 한꺼번에 가져올 수 있다.

1000번 정도 메서드를 호출했을 때의 결과다.

3547 ms, 즉 3.547초가 걸림을 알 수 있다.

 

👣 실험 결과

결과적으로 N+1 문제는 확실히 해결되어야 하는 것으로 인식할 수 있었고
Batch Size 조절하는 방법 이외에는 성능 향상 효과는 대동소이함을 알 수 있었다.

  특징 수행 시간 향상 효과
대조군 N + 1 문제 발생 43708 ms -
실험군 1 Eager FetchType 3582 ms 91.80% 감소
실험군 2 Fetch Join 3635 ms 91.68% 감소
실험군 3 Batch Size 5920 ms 86.45% 감소
실험군 4 EntityGraph 3547 ms 91.88% 감소