이번엔 JPA에서 일어나는 N+1 문제를 알아보려고 한다
제목엔 1+N이라 적었는데 좀 더 직관적 이해가 되는 것 같아 저렇게 했다ㅎㅎ
1+N 문제가 일어나는 경우
JPA 리포지토리를 사용해 메서드를 호출할 때, 일대다 관계를 가진 엔터티를 조회할 경우, 의도한 첫번째 쿼리 외에 추가로 N개의 쿼리가 발생하는 문제다.
Team과 Member가 있다. 이 관계는 1 : N 관계다. 예를 들어 10팀이 있고 각 팀은 여러 멤버를 가진다.
팀을 조회하는 상황을 보자
case 1. 즉시로딩 EAGER
@OneToMany(fetch = FetchType.EAGER)
findAll()을 실행하면
1. select * from team 쿼리를 날리고
2. 패치타입이 eager 니까 member도 가져옴
3. 팀 전체 조회 1번 + 각 팀마다 멤버 조회해서 10번 = 총 11번의 조회를 한다
-- 1.
select * from team;
-- 2.
select * from member where team.id = 1;
select * from member where team.id = 2;
...
select * from member where team.id = 10;
case 2. 지연 로딩 LAZY
@OneToMany(fetch = FetchType.LAZY)
findAll()을 실행하면
1. select * from team 쿼리를 날리고
2. 패치타입이 lazy니까 멤버를 아직 조회하지 않음
3. 비지니스 로직에서 팀마다 총인원을 구하는 경우 -> 1팀 ~ 10팀까지 조회함
4. 결국 1 + 10 = 11번의 조회를 실행
해결방법
그렇다면 이런 문제를 어떻게 해결할까?
방식은 여러가지가 있다.
1. Fetch Join
1+N 번애 걸쳐 따로 조회하지 않고 두 테이블을 join해서 한번에 조회하면 된다
애초에 다 불러오는 것이다.
JPQL을 직접 작성 join fetch t.members
@Query("select t from Team t join fetch t.members")
List<Team> findAllJoinFetch();
SELECT t.*, m.*
FROM team t
INNER JOIN member m ON t.id = m.team_id;
inner join을 사용한다
그러나 단점도 있다
- 1:N 관계가 두 개 이상인 경우 사용 불가 -> 한개 컬렉션만 가능
- JPA Paging 사용 불가능 -> 전체 데이터를 가져오게 되어 데이터가 많으면 out of memory로 서버가 터짐
2. @EntityGraph (간편한 어노테이션)
JPQL을 직접 짜지 않고, 메서드 위에 어노테이션으로 가져올 필드명을 지정하는 방법
@EntityGraph(attributePaths = {"members"})
@Query("select t from Team t")
List<Team> findAllEntityGraph();
SELECT t.*, m.*
FROM Team t
LEFT OUTER JOIN Member m ON t.id = m.team_id;
- left otuer join을 사용 -> null 데이터가 포함될 수 있음 -> inner join보다 성능 안좋음
- fetch join 처럼 페이징시 메모리 이슈가 발생
3. Batch Size (in 쿼리 최적화)
통합해서 1번이 아니라, n번 실행될 것을 in 절을 사용하여 획기적으로 줄이는 방법
(1 + N -> 1 + N/BatchSize)
YAML 파일에 설정
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 1000
지연 로딩 상태에서, t.getMembers()를 호출할 때 team 1000개까지 한꺼번에 가져옴
팀이 총 2000개라고 예시를 두면, 이때 실행되는 sql
-- 1. 팀 전체 조회
SELECT * FROM Team;
-- 2. 필요할 때 IN 쿼리로 한 번에 조회
SELECT * FROM Member WHERE team_id IN (1, 2, 3, ..., 1000);
SELECT * FROM Member WHERE team_id IN (1001, 1002, 1003, ..., 2000);
- batch size가 너무 크면 성능이슈가 있을 수 있으니 보통 100~1000개로 설정
- 페이징 문제 해결 -> Fetch Join의 단점인 컬렉션 페이징 OutOfMemory 문제를 해결
'spring | spring boot' 카테고리의 다른 글
| AWS 아마존웹서비스 educate 계정 달라짐 (0) | 2022.12.18 |
|---|---|
| SpringBoot thymeleaf rest api 게시판 model값 주고받기 (0) | 2022.12.09 |
| SpringBoot - Jasypt를 이용한 개인정보 암호화 (0) | 2022.03.30 |
| springboot-개발환경 분리하기 application.yaml (0) | 2022.03.24 |
| JSON object, array parsing (0) | 2022.02.15 |
댓글