❌ Spring - N+1 (DB 성능 이슈)
원인
- 이 문제는 데이터베이스에서 정보를 조회할 때 발생합니다.
- 연관된 엔티티를 로딩할 때, 한 번의 쿼리로 충분한 데이터를 가져올 수 있음에도 불구하고 N번의 추가 쿼리를 실행하여 성능 저하를 일으키는 문제를 의미합니다.
- 주로 Eager Fetching 전략이나, Lazy Fetching을 사용하면서 적절한 최적화를 하지 않았을 때 발생합니다.
예시
- Fetch 전략을 Eager(즉시 로딩)으로 한 경우 발생
- Fetch 전략을 Lazy(지연 로딩)으로 한 경우, 객체 그래프 탐색 시 발생
예시 1의 경우
- findAll()을 하면 JPQL구문이 생성되고 그 구문을 북석한 SQL이 생성 & 실행됨
- DB의 결과를 받아 엔티티의 인스턴스를 생성
- 영속성 컨텍스트에 검색하려는 엔티티와 연관된 엔티티가 있는지 확인
- 없다면 2번에서 만들어진 엔티티의 인스턴스 개수에 맞게 select 쿼리 발생 (N+1 발생)
예시 2의 경우
- findAll()을 하면 JPQL구문이 생성되고 그 구문을 분석한 SQL이 생성 & 실행됨
- DB의 결과를 받아 엔티티의 인스턴스를 생성
- 연관된 객체를 사용하는 시점에 영속성 컨텍스트에 검색하려는 엔티티와 연관된 엔티티가 있는지 확인
- 없다면 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 |