230314 - Fetch

Cornchip·2023년 3월 14일
0

Today-I-Learned

목록 보기
27/28

목차
1. Fetch
2. N+1 query problem



1. Fetch

1) Fetch

  • 애플리케이션이 DB로부터 데이터를 가져오는 것
  • DB와 통신하여 데이터를 읽는 것에는 큰 비용이 소모되기 때문에, 똑똑하게 가져오는 전략이 필요
  • eager: 프로그램 코드가 쿼리를 날리는 시점에 데이터를 즉시 가져오기
    • ex: select a.id from A a inner join B b on a.b_id = b.id (b를 보지 않았지만 일단 다 가져옴)
  • lazy: 가져오려는 데이터를 애플리케이션에서 실제로 접근할 때 가져오기
    • ex: select a.id from A; (select b from B b where b.id = ?)
  • lazy 전략은 기본적으로
    • ORM의 특징이자 기능적 장점
    • 더 빠르고 경제적인 쿼리 (적절히만 사용한다면)
    • 잘못 사용하면 데이터 접근 에러 (ex: LazyInitializationException)

2) Fetch 기본 전략(default setting)

  • 각 JPA 연관관계 애노테이션은 기본 fetch 전략을 가지고 있다.
  • 기본 세팅의 핵심은 "어느 쪽이 효율적인가"
    • @OneToOne: FetchTpe.EAGER
    • @ManyToOne: FetchTpe.EAGER
    • @OneToMany: FetchTpe.LAZY
    • @ManyToMany: FetchTpe.LAZY

3) fetch 전략의 설정 (실전)

  • 효율성 - 데이터가 어느 쪽으로 더 자주 사용될 것 같은가 예측
  • default 내버려두기: 필요한 시점에 최선의 방식으로 데이터를 가져옴
  • LAZY 사용: 연관 관계가 있는 엔티티에서 자식 엔티티만 가져오는 시나리오일 때
    • 프로그래머가 로직 흐름에서 join을 의식하고 있지 않음
    • LAZY 세팅이 후속 쿼리 발생 방지를 보장하지는 않음
      • ex: 불러들인 자식 엔티티가 서비스 레이어 어딘가에서 결국 부모 엔티티 필드를 건드렸을 경우
  • EAGER 사용: 연관 관계가 있는 엔티티에서 무조건 다 가져오는 시나리오일 때
    • 프로그래머가 join을 사용해야 하는 상황임을 인지하고 있음
    • EAGER 세팅이 join 동작을 보장하지는 않음
      • ex: Spring Data JPA 쿼리 메소드 findAll()
      • JPQL을 직접 작성해서 join을 영속성 컨텍스트에 알려줘야 함 (ex: querydsl)


2. N+1 query problem

  • 나는 한 번 쿼리를 날렸을 뿐인데, 1+N개의 쿼리가 더 생겼다.

    이벤트를 조회했는데,
    이벤트들이 바라보는 장소들이 하나하나 select되어 버린다.


3가지 방법

  1. 똑똑한 lazy
  • 비즈니스 로직을 면밀히 분석하여, 불필요한 연관 관계 테이블 정보를 불러오는 부분을 제거
  • 가장 똑똑하고 효율적인 방법

  1. eager fetch + join jpql
  • join 쿼리를 직접 작성한느 방법은 다양 (@Query, querydsl, ...)
  • 쿼리 한 번에 오긴 하겠지만, join 쿼리 연산 비용과 네트워크로 전달되는 데이터가 클 수 있음

  1. 후속 쿼리를 in으로 묶어주기: N+1 -> 1+1로 I/O 줄일 수 있음
  • 하이버네이트 프로퍼티: default_batch_fetch_size
  • 스프링부트에서 쓰는 법: spring.jpa.properties.hibernate.default_batch_fetch_size
  • 100 ~ 1000 사이를 추천
  • 모든 쿼리에 적용되고, 복잡한 도메인에서 join 쿼리를 구성하는 것이 골치아플 때 효율적
profile
cornchip

0개의 댓글