일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- oauth
- marksense.ai
- google cloud
- Spring Boot
- idToken
- google 로그인
- react native
- skt fellowship 3기
- 졸프
- C++
- google login
- @Transactional
- STT
- matplotlib
- 코드업
- YOLOv5
- Expo
- AWS
- 순환참조
- javascript
- 양방향 매핑
- JPA
- 커스텀 데이터 학습
- yolo
- pandas
- OG tag
- html
- 2021 제9회 문화공공데이터 활용경진대회
- Loss Function
- Spring
- Today
- Total
민팽로그
[JPA] N+1 문제 본문
저번 프로젝트를 진행할 땐 N+1 문제를 신경쓰지 않았다. 나중에 공부를 하다보니 이런 문제가 발생할 수 있구나 정도로만 알고 있었다.
그리고 이번 프로젝트를 진행하면서 sql 쿼리를 콘솔에서 확인하다보니, 의도한 것보다 select 쿼리가 훨씬 많이 발생하고 있다는 것을 발견하고 N+1 문제를 정리해보게 되었다.
N+1 문제란?
1. 즉시 로딩과 지연 로딩
- 즉시 로딩: 연관 관계에 있는 엔티티를 사용하지 않아도 한번에 바로 로딩. 비어있는 내용은 프록시 객체로 대신함.
- 지연 로딩: 연관 관계에 있는 엔티티를 바로 로딩하지 않고 사용하는 시점에서 로딩
연관된 모든 엔티티를 매번 즉시 로딩하는 것은 좋은 설계가 아니다. 해당 엔티티의 조회가 필요할 때마다 조회하는게 좋다.
2. 기본 패치 전략
- @ManyToOne, @OneToOne: FetchType.EAGER(즉시 로딩)
- @OneToMany: FetchType.LAZY(지연 로딩)
실제로 사용할 땐 꼭 필요한 상황이 아니라면 가능한 모든 연관 관계 패치 전략을 지연 로딩으로 설정하는게 좋다.
3. N+1 문제
예시로 들 엔티티는 Spot과 SpotTag이고 1:N 관계이다. 패치 전략은 지연 로딩을 사용했다.
SpotTag 테이블의 상위 5개 데이터를 조회하는 테스트 코드를 아래와 같이 작성했다.
@Test
@DisplayName("N+1 문제 발생 테스트")
void testSpotTagRepository() {
List<SpotTag> spotTags = spotTagRepository.findTop5By();
for (SpotTag spotTag : spotTags) {
System.out.println("---- spotTag.spot 조회: " + spotTag.getSpot().getSpotName());
}
}
결과 쿼리를 보면 1개가 아니라 총 6개의 쿼리가 발생했다.
맨 위 분홍색 네모박스에 해당하는 쿼리를 보면, Spot 테이블과 조인되지 않는 것을 확인할 수 있다. 지연 로딩 전략을 사용했기 때문에 연관 관계에 있는 Spot 엔티티에 대한 정보는 가지고 오지 않았기 때문이다.
이 상황에서 Spot 엔티티의 데이터를 가져오려 했기 때문에 파란색 네모박스처럼 5개의 추가 select 쿼리가 발생한다. 즉 원래 의도한 쿼리 1개 + 5개의 조회 건수에 대한 추가 쿼리가 발생하여 총 6개의 쿼리가 발생하는 것이다. 이러한 문제가 N+1 문제이다.
해결 방법
연관관계에 있는 엔티티에 접근할 필요가 없다면 접근하지 않는 것이 기본이다. 접근이 필요하다면 패치조인, @EntityGraph 어노테이션 사용 등의 방법으로 N+1 문제를 해결할 수 있다.
1. 패치 조인
패치 조인은 JPQL에서 성능 최적화를 위해 지원하는 조인이다. 연관된 엔티티까지 함께 가져올 수 있다.
패치 조인은 다음과 같이 사용한다.
테스트 코드를 다시 실행하고 결과를 확인해 보니,
이전과는 다르게 inner join을 통해 Spot테이블과 SpotTag 테이블을 조인한 후 모두 select한다.
만약 패치 조인을 사용하지 않고 일반 조인을 사용한다면 어떻게 될까?
아래와 같이 쿼리를 수정하고 결과를 확인해보자.
실행 결과 조인은 되었지만 select 절을 보면 Spot의 컬럼은 포함되어 있지 않다. 또한 N+1 문제가 다시 발생하는 것을 확인할 수 있다.
물론 이 방법으로도 DTO를 사용해 프로젝션 해준다면 N+1 문제를 해결할 수 있지만 유연성이 떨어져 효율적이지 않다.
2. @EntityGraph
이 어노테이션을 사용하여 N+1 문제를 해결할 수 있지만 패치 조인과 차이점은 left outer 조인이라는 것이다.
위와 같이 사용한다.
결과 쿼리를 보면 left outer join으로 조인하며 select 절에 Spot과 SpotTag의 컬럼이 모두 포함되어 있는 것을 확인할 수 있다. N+1 문제 또한 발생하고 있지 않다.
또한 위와 같이 이름을 붙여주고 사용할 수도 있다.
reference
https://www.youtube.com/watch?v=MpXdx8-qWzo&t=596s
'프로젝트 > 감성 스팟 가이드(2022 한이음)' 카테고리의 다른 글
순환 참조 문제 해결: DTO 사용 (0) | 2022.07.31 |
---|---|
log 설정 (0) | 2022.07.31 |
MZ와 "가보자고" : 프로젝트 준비 과정 (0) | 2022.07.31 |