이번엔 N+1 문제가 발생할 것을 알고도 한 번 어느정도 차이가 있는 지 보기 위해 그냥 실행시켜본 N+1 문제를 보려고 한다.
문제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
@Getter
@NoArgsConstructor
public class MbtiWebtoon extends BaseEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "mbti_id")
private Mbti mbti;
@ManyToOne
@JoinColumn(name = "webtoon_id")
private Webtoon webtoon;
@Builder
public MbtiWebtoon(Mbti mbti, Webtoon webtoon) {
this.mbti = mbti;
this.webtoon = webtoon;
}
}
- MBTI별 선호 웹툰의 목록을 담는 엔티티이다.
- 홈에 각 MBTI별로 선호웹툰들이 약 20개 정도 존재하고 해당 웹툰들이 한 번에 조회된다.
1
2
3
4
public interface MbtiWebtoonRepository extends JpaRepository<MbtiWebtoon, Long> {
List<MbtiWebtoon> findByMbti(Mbti mbti);
}
- 처음에 이렇게 작성하고 DTO로 변환하는 로직을 작성하던 중 N+1 문제가 일어날 것임을 알았다.
- 하지만 실제로 쿼리가 어떻게 발생하는지, 그리고 그에 따른 성능 차이는 얼마나 될 지 궁금해서 그냥 실행해보게 되었다.
- 여기서 나타나는 N+1문제는 MbtiWebtoon 엔티티의 Webtoon이 지연로딩 설정되어 있기 때문에 해당 Webtoon에 대한 정보를 얻으려 할 때 각각의 Webtoon에 대한 조회 쿼리가 발생하는 문제이다.
즉 MBTI 가지 수 16가지 * 약 20의 최소 320번 이상의 쿼리가 발생하는 것이다.
해결
페치 조인을 사용해 웹툰 정보를 쿼리 한 번으로 가져오도록 하였다.
1
2
3
4
5
public interface MbtiWebtoonRepository extends JpaRepository<MbtiWebtoon, Long> {
@Query("select mw from MbtiWebtoon mw join fetch mw.webtoon where mw.mbti = :mbti")
List<MbtiWebtoon> findByMbti(@Param("mbti") Mbti mbti);
}
결과
- N+1 문제 해결 전
- N+1 문제 해결 후
- 약 37% 정도의 성능 개선을 볼 수 있었다. (한 가지 MBTI에 대한 요청)
- 만약 조회하는 Webtoon의 수가 더 컸다면 차이가 더 컸을 것이라고 생각한다.
쿼리도 무수히 많은 select 쿼리에서 단 하나의 쿼리만 발생하는 것을 볼 수 있다.