-
[Spring JPA] JPA N+1 문제Spring JPA 2024. 10. 13. 20:07
N + 1 문제는 하나의 쿼리를 통해 데이터를 조회할 때, 연관된 엔티티들을 지연 로딩(Lazy Loading) 또는 즉시 로딩(Eager Loading)으로 추가 조회하면서, 의도하지 않은 N개의 추가 쿼리가 실행되는 현상을 의미한다.
언제 발생할까?
- JPA Repository를 사용하여 엔티티 조회 메서드를 호출할 때, 주로 SELECT(READ) 쿼리에서 발생합니다.
누가 발생 시킬까?
- 1 대 다(1:N) 또는 다 대 1(N:1) 연관 관계를 가진 엔티티를 조회할 때 발생합니다.
- 주로 Fetch 전략에 의해 발생합니다. 특히 EAGER와 LAZY 로딩 전략에 따라 다르게 발생합니다.
어떤 상황에서 발생할까?
1. EAGER Fetch 전략을 사용하여 데이터를 조회하는 경우:
- 연관된 엔티티를 즉시 조회하므로, 첫 번째 조회 쿼리 외에도 연관된 하위 엔티티들을 위한 추가 쿼리가 발생합니다.
2. LAZY Fetch 전략을 사용하여 데이터를 조회하고, 이후 해당 연관 관계 엔티티를 사용할 때:
- 초기에는 연관된 엔티티를 조회하지 않지만, 연관된 엔티티에 접근하는 순간 추가 쿼리가 실행되어 N + 1 문제가 발생할 수 있습니다.
도대체 왜 발생하는가?
- JPA Repository에서 find 메서드로 엔티티를 조회할 때, JPA는 기본적으로 설정된 Fetch 전략에 따라 데이터를 조회합니다.
- 연관된 하위 엔티티를 즉시 가져오지 않고, 필요할 때 추가적으로 조회하는 경우, 이로 인해 예상치 못한 N개의 추가 쿼리가 발생하게 됩니다.
- 또한, JPQL은 기본적으로 Fetch 전략을 무시하고, JPQL에 정의된 것만 가지고 SQL을 생성하기 때문에 발생할 수 있습니다.
Fetch 전략에 따른 문제
1. EAGER Fetch 전략일 때:
- JPQL이 생성한 SQL을 통해 첫 번째 쿼리에서 상위 엔티티(1)를 조회합니다.
- 이후 JPA가 EAGER 전략에 의해 해당 엔티티의 연관된 하위 엔티티들(N)을 즉시 조회합니다.
- 이 과정에서 N + 1 문제가 발생할 수 있습니다.
2. LAZY Fetch 전략일 때:
- JPQL이 생성한 SQL을 통해 상위 엔티티(1)를 조회하지만, 하위 엔티티는 지연 로딩(Lazy)되므로 추가 조회는 발생하지 않습니다.
- 그러나 하위 엔티티에 접근하는 순간, 해당 하위 엔티티에 대한 추가 쿼리가 발생하게 되므로, 이때도 N + 1 문제가 발생할 수 있습니다.
문제 해결 방법
1. Fetch Join 사용
- JPQL 또는 QueryDSL을 사용할 때, 연관된 엔티티들을 Fetch Join으로 함께 조회하면 N + 1 문제를 방지할 수 있다
왜냐하면 N + 1 발생하는 이유가 한쪽 테이블만 조회하고 연결된 다른 테이블은 따로 조회하기 때문이다
fetch join으로 함께 조회하면 문제가 발생하지 않을 것이다
SELECT i FROM Information i JOIN FETCH i.greatInformation
하지만 단점이 있다
- 패치조인은 한번에 연관된 테이블을 가져오는 형식이라 연관된 엔티티의 수가 많을 경우에 쿼리가 복잡하면 성능이 저하된다
- 페이징 처리가 불가능하다
- 연관관계가 다대일, 일대다 관계면 하위 엔티티가 여러개라서 상위 엔티티 데이터가 중복 조회가 일어 날 수 있다
다대다 관계일때는 패치 조인을 피하는게 더 나을 것 같다
2. Entity Grapgh 사용
- 특정 조회 시점에 EntityGraph를 통해 연관 엔티티를 함께 조회하도록 설정할 수 있다
@EntityGraph(attributePaths = {"greatInformation"})
장점:
- Fetch Join보다 더 선택적으로 연관된 엔티티를 함께 조회할 수 있습니다.
- 코드 가독성이 향상되며, 필요한 시점에만 추가적인 엔티티를 가져오므로 유연성이 높습니다.
단점:
- Eager Loading처럼 동작할 수 있기 때문에 불필요한 엔티티까지 함께 가져오는 상황이 발생할 수 있습니다.
- 모든 관계를 일괄적으로 설정할 수 없고, 조회할 때마다 세밀하게 설정해야 하는 부담이 있습니다.
3. 배치 사이즈 설정
- @BatchSize를 설정하여 한번에 가져오는 엔티티 수를 조정함으로써 추가적인 쿼리의 발생을 줄일 수 있다
@BatchSize(size = 100)
장점:
- 성능 최적화에 큰 도움이 됩니다. 연관된 엔티티들을 일정량씩 배치로 가져오기 때문에 성능 저하를 방지할 수 있습니다.
- N + 1 문제를 해결하면서도 추가적인 쿼리 수를 제어할 수 있습니다.
단점:
- 설정된 배치 크기에 따라 성능이 좌우될 수 있습니다. 배치 사이즈가 너무 작으면 성능에 크게 영향을 주지 않으며, 너무 크면 메모리 사용량이 늘어날 수 있습니다.
'Spring JPA' 카테고리의 다른 글
[Spring JPA] entity의 생성일, 수정일 어떤식으로 할까 (0) 2024.09.11 [Spring] 즉시(Eager) 로딩 과 지연(Lazy) 로딩 (0) 2024.08.11 [Spring] DB 상태 테이블, Enum 관리 Converter 사용해보자 (0) 2024.07.04 [Spring] OSIV 란? (0) 2024.05.24