[JPA] N+1 이슈

SW고구마·2021년 11월 11일
0
post-custom-banner

N+1?

JPA를 활용하면서 테이블 간의 연관관계(1:N, N:1, 1:1)가 맺어져 있는 경우, 한 번 조회가 발생할 때 연관되어 있는 테이블에서 데이터들을 또 조회해 연관정보를 채우게 된다.

즉, 1개의 쿼리를 조회하기 위해 연관된 데이터를 위한 N개의 쿼리가 추가로 발생하는 문제로 정의할 수 있다.

이 때, 연관관계를 맺으면서 데이터를 바로가져와 매핑하는가? 나중에 해당 정보를 사용하는 경우 쿼리를 발생시켜 매핑하는가? 에 따라 전략을 정할 수 있는데 이를 fetchType 이라고 한다.

fetchType

fetchType에는 2가지가 존재하는데 fetchType.EAGER, fetchType.LAZY 가 존재한다.
JPA에서는 기본적으로 fetchType.LAZY의 전략이 기본으로 설정되어 있다.
(N+1 문제가 발생할 것을 우려해서)

fetchType.EAGER

Member Class - 유저

// 어노테이션 생략
Public Class Member{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

Post Class - 게시글

// 어노테이션 생략
Public Class Member{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    
    @Lob
    private String content;
    
    @ManyToOne(fetch = fetchType.EAGER)
    @JoinColumn(name = "userId")
    private Member member;
}

게시글(Post)와 유저(Member)는 N:1의 연관관계를 맺고 있고 현재는 예시를 위해 fetchType.EAGER의 전략을 사용하고 있다.

이 때 게시글을 조회하는 경우, 유저의 정보를 매핑하기 위해 유저를 조회하는 쿼리가 추가로 발생해 게시글 조회 + 유저 조회 의 두 번의 조회가 발생한다.
즉, 게시글을 조회하기 위한 의도에서 추가적인 유저 쿼리가 발생하는 것이다.

따라서 필요한 경우 데이터를 조회하는 전략인 fetchType.LAZY를 기본으로 사용하면 예기치 못한 N+1 문제를 피할 수 있다.

fetchType.LAZY

Member Class - 유저

// 어노테이션 생략
Public Class Member{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

Post Class - 게시글

// 어노테이션 생략
Public Class Member{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    
    @Lob
    private String content;
    
    @ManyToOne(fetch = fetchType.LAZY)
    @JoinColumn(name = "userId")
    private Member member;
}

그렇다면 과연 fetchType.LAZY의 경우는 N+1을 막을 수 있는 것인가?

생각해보면 그렇지도 않다.
예를 들어 [제목]-[작성자] 와 같이 게시글 목록을 뿌리면서 작성자의 이름을 적는 즉 유저의 정보가 필요한 경우를 생각해보면 쉽게 알 수 있다.
모든 게시글 N개를 조회해와 목록을 뿌리면서 결과적으로 유저의 정보를 N번 사용하게 된다. 이 때 추가적인 N개의 쿼리가 발생하게 된다.
이렇게 되면 결국 N+1을 피할 수 없게 된다.

결론

JPA에서는 N+1의 문제로 인해 기본 전략을 fetchType.LAZY로 사용하지만 이 마저도 N+1 문제를 피할 수 없다.

그렇다면 이를 해결할 수 있는 방법은 무엇인가?

fetch Join

기본전략인 fetchType.LAZY를 기본으로 사용하되 연관관계의 데이터가 바로 필요한 경우 JPQL의 fetch join 키워드를 활용해 조회 한번에 필요한 데이터를 함께 불러오는 것이다.

하지만 fetch join을 사용할 때에도 고려사항이 몇 가지 있기에 fetch join에 대한 공부는 추가로 더 필요할 것 같다.

profile
하루하루 조금씩이라도
post-custom-banner

0개의 댓글