메인 프로젝트 (5)메인페이지 - N +1 문제

InSeok·2022년 12월 8일
0

프로젝트

목록 보기
5/13

Post

연관관계

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Post extends Auditable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = " post_id")
    private Long id;

    @Column(name = "content", nullable = false)
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name ="member_id")
    private Member member;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Picture> pictures = new ArrayList<>();

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Likes> likes = new ArrayList<>();

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PostTag> postTags = new ArrayList<>();

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PostComment> postComments = new ArrayList<>();
  • Post Entity는 화면과 같이다수의 연관 관계가 설정된 자식 엔티티들이 존재하는데 , Picture, Like, CommentOneToMany 연관관계를, TagManyToMany 연관관계를 형성하고있다.
  • 부모인 Member Entity 와는 ManyToOne연관 관계를 맺고있다.

N + 1 문제

  • 이번 Main Project에서는 Pre-Project에서 만났던 N+1과 같은 DB와 관련된 문제를 해결하고, 성능을 최적화 하기 위해 노력을 기울였다.
  • 게시물 정보를 불러오기 위해 Post 엔티티를 조회할 때, 연관된 자식 엔티티도 함께 조회할 경우 N+1 문제가 발생한다. 그렇다면, 지금부터 N+1문제가 무엇인지, 그리고 어떻게 해결하는지 알아보자.

N+1 문제란?

  • 쿼리 1번으로 N건의 부모 엔티티를 조회했을 때, 조회된 부모의 수 만큼 자식 테이블 쿼리가 N번 추가 발생하는 현상을 말한다.

Why?

Fetch Type

Eager

  • JPQL과 함께 사용시 N+1 문제 발생
    • JPA가 JPQL로 SQL을 생성할 때는 글로벌 Fetch 전략을 무시하고 JPQL만 가지고 생성하기 때문에 find() 메소드로 조회할 때와 달리 Join을 통해서 연관관계를 가져오지 않기 때문에 N+1 문제 발생
  • 사용하지 않는 데이터도 매번 불러오게 된다

Lazy

  • 전체 데이터 가져온 이후 연관 관계인 하위 엔티티를 사용할때 다시 조회할때 발생

How?

Fetch Join

  • 특정 엔티티를 조회할 때 연관된 엔티티 혹은 컬렉션을 함께 한번에 조회하는 기능

Batch Size

  • 지정한 수만큼 in절에 부모 Key를 사용하게 해주는 옵션

해결과정

문제 1. MulitpleBagFetchException

  • Post 엔티티를 조회할 때, N +1 문제를 해결하기 위해 OneToMany 관계인 엔티티 여러곳Fetch Join을 적용 → MulitpleBagFetchException 에러 발생

원인

Fetch Join의 조건

  1. ToOne 관계는 몇개든 사용 가능
  2. ToMany 관계는 1개만 사용 가능

해결 1.

  • OneToMany 관계의 자식 엔티티들중 가장 데이터가 많은 자식쪽에 Fetch Join을 사용하고, ToOne 관계의 자식엔티티들은 모두 Fetch Join을 적용하여 한방 쿼리 수행
  • 그렇다면 나머지 OneToMany관계의 자식엔티티들은 FetchJoin없이 어떻게 N+1문제를 해결할 수있을까?
    • BatchSize를 적용하여, 부모 엔티티의 Key가 1개씩 사용 되던 조건문을 in절로 묶어서 한번에 조회 함으로써 N+1 문제를 해결
  • BatchSize 적용과정

  • 부모 엔티티가 지연로딩된 ToMany 관계의 자식 엔티티(컬렉션)을 최초 조회 할때 이미 조회하여 영속성 컨텍스트에서 관리되고 있는 부모 엔티티들의 ID를 모아서 Where 조건문에 WHERE Y.X_ID IN (?, ?, ?...) 와 같은 SQL IN 구문에 담아 자식엔티티 조회 쿼리를 날려 필요한 자식엔티티 데이터들을 한번에 조회

문제 2. Pagination + Fetch Join 문제

  • 위에서 설명한 방법을 적용하였더니, MultipleBagFetchException에러는 발생하지 않았으나, 페이징할때 사용하던 기존의 SQL LIMIT 구문이 등장하지 않음
  • 쿼리결과를 전부 메모리에 적재한 뒤 어플리케이션 단에서 Pagination 작업을 수행한다는 경고 로그가 발생

원인

  • OneToMany 관계를 Join하면 카타시안 곱 이 적용되어 DB Row수가 뻥튀기되어 조회되기 때문에 Limit 구문을 사용하는 쿼리로 페이지네이션을 적용하기 어려워 조회된 결과를 모두 메모리로 가져와 JPA가 페이지네이션 계산을 진행
  • OneToMany 관계의 컬렉션을 Fetch Join하면서, 동시에 Pagination API를 사용하면 OutOfMemoryError가 발생할 수 있어, 이 둘을 동시에 사용해서는 안된다.
  • 반대로 ManyToOne 관계의 엔티티를 Fetch Join 할때는 DB Row의 수가 변경되지 않아 문제없이 Pagination이 적용가능
  • ToMany 관계의 자식엔티티들은 FetchJoin없이 어떻게 N+1문제를 해결할 수있을까?

해결 2.

  • BatchSize를 적용
    • Batch Size를 적용한 결과 where comments0_.post_id in (?, ?, ?)를 포함하는 Comment 조회 쿼리 1개로 줄어들었다.
  • 최종적으로 ToOne 관계인 자식 엔티티는 FetchJoin을 , ToMany 관계인 자식엔티티는 BatchSize를 적용하여 N+1과 문제와 Pagination관련 문제를 해결
profile
백엔드 개발자

0개의 댓글