일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- KPT회고
- 프로그래머스
- 디자인패턴
- 1주일회고
- 개발자 취업
- 빈 충돌
- 커스텀 헤더
- spring batch 5.0
- 코딩테스트 준비
- 빈 조회 2개 이상
- 항해99
- 프로그래머스 이중우선순위큐
- DesignPattern
- TiL
- JavaScript
- Spring multimodule
- 전략패턴 #StrategyPattern #디자인패턴
- 디자인 패턴
- Python
- 구글 OAuth login
- infcon 2024
- 인프콘 2024
- 취업리부트코스
- 99클럽
- 파이썬
- 개발자부트캠프추천
- 단기개발자코스
- jwttoken
- jwt
- @FeignClient
- Today
- Total
m1ndy5's coding blog
Ditto 프로젝트 N+1 문제 1편 - FetchType Lazy, Eager 본문
Ditto 프로젝트 N+1 문제 1편 - FetchType Lazy, Eager
정민됴 2024. 2. 26. 16:52엔티티를 작성하면서 Fk로 가져오는 컬럼을 @ManyToOne을 사용해 작성했고 굳이 FetchType은 명시하지 않았다.
후에 팀원에게 코드리뷰를 받으면서 FetchType은 Lazy로 설정하는 것이 좋다라는 피드백을 받게 되었고 그 이유를 정리해 보려고 한다!
즉시 로딩 & 지연 로딩
JPA에서 엔티티 간의 관계를 매핑할 때 지연로딩과 즉시로딩 이 두 가지 전략을 사용해서 불러올 수 있는데
두 전략의 차이는 데이터베이스에서 연관된 엔티티를 언제 로딩하는지다.
지연로딩(Lazy Loading)
연관된 엔티티를 실제로 사용할 때까지 로딩을 미루는 전략
연관된 엔티티를 가져오는 것이 필요한 순간에만 로딩을 수행한다.
모든 연관된 엔티티를 미리 로딩하지 않기 때문에, 필요한 경우에만 데이터를 가져와서 성능을 최적화할 수 있다.
하지만 연관된 엔티티를 가져올 때 해당 엔티티의 수만큼 추가적인 쿼리가 발생한다.
@Entity
public class Order {
//...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;
//...
}
즉시로딩(Eager Loading)
부모 엔티티를 로딩할 때 연관된 자식 엔티티도 함께 로딩하는 전략
부모 엔티티를 가져올 때 연관된 엔티티도 즉시 가져온다.
한 번의 쿼리로 모든 관련된 엔티티를 가져와서 N+1 문제를 방지하지만 모든 연관된 엔티티의 정보들을 가져오기 때문에 필요하지 않은 경우에도 데이터를 미리 로딩하기 때문에 성능과 자원 소모 면에서 좋지 않다.
@Entity
public class Order {
//...
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "customer_id")
private Customer customer;
//...
}
Eager 로딩은 N+1이 일어나지 않을까?
그렇지 않다.
@OneToMany에서 Eager 로딩을 사용하면 N+1 문제가 발생할 수 있다.
@Entity
public class Order {
//...
@OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
private List<Item> items;
//...
}
// Eager 로딩 예제에서 주문 엔터티를 가져올 때 (1번의 쿼리)
List<Order> orders = entityManager.createQuery("SELECT o FROM Order o", Order.class)
.getResultList();
// 각 주문에 대해 연관된 상품 엔터티를 가져올 때 (추가적인 N번의 쿼리)
for (Order order : orders) {
List<Item> items = order.getItems(); // 추가적인 쿼리 발생 (N번)
}
Order 엔티티를 조회할 때 모든 Item 엔티티를 함께 가져오려고하면 N+1문제가 발생할 수 있다.
또한 findAll()과 같은 리스트를 불러오는 메서드를 사용하면 N+1 문제가 발생한다.
그렇다면 항상 Lazy Loading을 사용해야할까?
위 설명을 읽었을 때 내가 만든 엔티티는 사실상 많은 엔티티와 연관이 없어서 @ManyToOne에서는 Eager Loading을 사용해도 큰 무리가 없어보였다. 하지만 처음의 설계가 그대로 가는 것은 아니기 때문에 나중에 갑자기 엔티티가 많아져서 Lazy Loading으로 바꾸고 보수공사를 하는 것보다 처음부터 많아질 때를 고려하여 Lazy Loading으로 바꾸고 N+1문제를 해결하는 것이 더 객체지향적이라고 생각했다.
그래도 설계 변경이 절대 일어나지 않을 예정이고 한번에 불러오는 엔티티가 그렇게 많지 않다면 사실 Eager Loading을 쓰는 것도 나쁘지 않아보인다. 간단하기도 하고ㅎㅎ
N+1 문제란?
관계형 데이터베이스에서 데이터를 가져올 때 발생하는 쿼리 수의 증가로 인해 성능이 저하되는 문제
N : 첫 번째 쿼리로 가져온 엔티티들의 목록(부모 엔티티)
1 : 각각의 부모 엔티티에 추가적으로 자식 엔티티를 가져오기 위한 쿼리
예를 들어서
// 부모 엔터티를 가져올 때
List<Order> orders = entityManager.createQuery("SELECT o FROM Order o", Order.class)
.getResultList();
// 각 부모 엔터티에 대해 자식 엔터티를 가져오려고 할 때
for (Order order : orders) {
Customer customer = order.getCustomer(); // 추가적인 N개의 쿼리 발생
}
위 예시에서 주문 엔티티를 가져올 때는 하나의 쿼리로 처리되지만 고객 엔티티를 가져오려고 할 때마다 추가적인 쿼리가 발생한다.
이 때 데이터베이스와 통신을 더 하게되고 성능 저하로 이어질 수 있다.
결론
Lazy Loading과 Eager Loading은 N+1 문제를 야기할 수 있다.(Lazy Loading에서 하위 엔티티를 불러올 경우)
특히 Eager Loading은 부모엔티티 조회시점에 모든 자식엔티티를 가져와 성능과 자원 소모 면에서 좋지 않다.
아무래도 확장성과 성능을 생각한다면 Lazy Loading을 사용하고 N+1문제를 해결하는 것이 좋아보인다.
다음에는 Lazy 전략을 사용하면서 어떻게 N+1문제를 해결했는지 포스팅하겠다~!
'Toy Projects > Ditto - Discuss Today's Topic' 카테고리의 다른 글
Ditto 프로젝트 종목 불러오기 with batchUpdate (1) | 2024.02.29 |
---|---|
Ditto 프로젝트 N+1 문제 2편 - N+1 문제 해결과 외래키 (2) | 2024.02.27 |
Ditto 프로젝트 동시성 문제 해결하기 2편 - 낙관적 락을 사용해 동시성 해결하기 (feat. Deadlock, DirtyChecking, Retry) (1) | 2024.02.24 |
Ditto 프로젝트 동시성 문제 해결하기 1편 - 낙관적 락 VS 비관적 락 (1) | 2024.02.24 |
Ditto Project Spring Batch&Scheduling 2편 - Docker, Jenkins 사용하여 Spring Batch Job 실행하기 (1) | 2024.02.17 |