Spring - N+1 (DB 성능 이슈)

2023. 4. 15. 13:43·❌ Error Handling

❌ Spring - N+1 (DB 성능 이슈)

원인

  • 이 문제는 데이터베이스에서 정보를 조회할 때 발생합니다.
  • 연관된 엔티티를 로딩할 때, 한 번의 쿼리로 충분한 데이터를 가져올 수 있음에도 불구하고 N번의 추가 쿼리를 실행하여 성능 저하를 일으키는 문제를 의미합니다.
  • 주로 Eager Fetching 전략이나, Lazy Fetching을 사용하면서 적절한 최적화를 하지 않았을 때 발생합니다.

예시

  1. Fetch 전략을 Eager(즉시 로딩)으로 한 경우 발생
  2. Fetch 전략을 Lazy(지연 로딩)으로 한 경우, 객체 그래프 탐색 시 발생

예시 1의 경우

  1. findAll()을 하면 JPQL구문이 생성되고 그 구문을 북석한 SQL이 생성 & 실행됨
  2. DB의 결과를 받아 엔티티의 인스턴스를 생성
  3. 영속성 컨텍스트에 검색하려는 엔티티와 연관된 엔티티가 있는지 확인
  4. 없다면 2번에서 만들어진 엔티티의 인스턴스 개수에 맞게 select 쿼리 발생 (N+1 발생)

예시 2의 경우

  1. findAll()을 하면 JPQL구문이 생성되고 그 구문을 분석한 SQL이 생성 & 실행됨
  2. DB의 결과를 받아 엔티티의 인스턴스를 생성
  3. 연관된 객체를 사용하는 시점에 영속성 컨텍스트에 검색하려는 엔티티와 연관된 엔티티가 있는지 확인
  4. 없다면 2번에서 만들어진 엔티티의 인스턴스 개수에 맞게 select 쿼리 발생 (N+1 발생)

해결 - 3가지 방법

Fetch Join

  • Repository에서 별도의 메소드를 만들어줘야 함
  • @Query 어노테이션에서 "join fetch 엔티티.연관된엔티티" 구문 생성
  • Inner Join으로 수행됨
public interface TeamRepository extends JpaRepository<Team, Long> {
    @Query("select t from Team t join fetch t.users")
    List<Team> findAllFetchJoin();
}
List<Team> all = teamRepository.findAllFetchJoin();
System.out.println("============== N+1 확인용 ===================");
all.stream()
    .forEach(team -> {team.getUsers().size();}
);

@EntityGraph

  • @EntityGraph(attributePaths = "users") 와 같은 Annotations을 추가함으로써 Lazy가 아닌 Eager 조회로 가져오도록 설정
  • Fetch Join과 다르게 Join 문이 Outer Join으로 수행 (성능 저하)
  • 둘 다 카테시안 곱이 발생하여 Rich 수 만큼 users의 중복 데이터가 생기는 상황 발생
    • 중복을 제거하기위해 Set 컬렉션 사용 (순서 필요 시 LinkedHashSet 사용)
    • JPQL을 사용하므로 distinct를 사용하여 중복 제거
public interface RichRepository extends JpaRepository<Rich, Long> {
    @EntityGraph(attributePaths = "users")
    @Query("select a from Rich a")
    Set<Rich> findAllJoinFetch();
}

Batch Size

  • 이 옵션은 N+1을 해결하는 방법이 아닌, 발생하더라도 select * from user where team_id = ? 이 아닌 select * from user where team_id in (?, ?, ? ) 방식으로 N+1 문제가 발생하게 하는 방법이다.
  • 이렇게하면 100번 일어날 N+1 문제를 1번만 더 조회하는 방식으로 성능 최적화 가능
  • 간단한 yml 수정으로 인해 in 쿼리가 나가게 됨
spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 1000
저작자표시

'❌ Error Handling' 카테고리의 다른 글

Github Actions - every step must define a uses or run key  (0) 2023.05.24
Kotlin - mark, reset not supported  (0) 2023.04.30
Spring - Lazy Initialization Exception  (0) 2023.04.15
Spring - Redis System Exception  (0) 2023.04.15
Spring - Circular Dependency Error  (0) 2023.04.15
'❌ Error Handling' 카테고리의 다른 글
  • Github Actions - every step must define a uses or run key
  • Kotlin - mark, reset not supported
  • Spring - Lazy Initialization Exception
  • Spring - Redis System Exception
신건우
신건우
조용한 개발자
  • 신건우
    우주먼지
    신건우
  • 전체
    오늘
    어제
    • 분류 전체보기 (422)
      • 📘 Frontend (71)
        • Markup (1)
        • Style Sheet (2)
        • Dart (8)
        • Javascript (12)
        • TypeScript (1)
        • Vue (36)
        • React (2)
        • Flutter (9)
      • 📘 Backend (143)
        • Java (34)
        • Concurrency (19)
        • Reflection (1)
        • Kotlin (29)
        • Python (1)
        • Spring (42)
        • Spring Cloud (5)
        • Message Broker (5)
        • Streaming (2)
        • 기능 개발 (5)
      • 💻 Server (6)
        • Linux (6)
      • ❌ Error Handling (11)
      • 📦 Database (62)
        • SQL (31)
        • NoSQL (2)
        • JPQL (9)
        • QueryDSL (12)
        • Basic (4)
        • Firebase (4)
      • ⚙️ Ops (57)
        • CS (6)
        • AWS (9)
        • Docker (8)
        • Kubernetes (13)
        • MSA (1)
        • CI & CD (20)
      • 📚 Data Architect (48)
        • Data Structure (10)
        • Algorithm (8)
        • Programmers (17)
        • BaekJoon (5)
        • CodeUp (4)
        • Design Pattern (4)
        • AI (0)
      • ⚒️ Management & Tool (8)
        • Git (7)
        • IntelliJ (1)
      • 📄 Document (10)
        • Project 설계 (6)
        • Server Migration (3)
      • 📄 책읽기 (2)
        • 시작하세요! 도커 & 쿠버네티스 (2)
      • 🎮 Game (4)
        • Stardew Vally (1)
        • Path of Exile (3)
  • 블로그 메뉴

    • 링크

      • Github
    • 공지사항

    • 인기 글

    • 태그

      GStreamer #Pipeline
      Lock #Thread #Concurrency
      React #Markdown
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.0
    신건우
    Spring - N+1 (DB 성능 이슈)
    상단으로

    티스토리툴바