일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- infcon 2024
- 항해99
- 빈 조회 2개 이상
- 디자인패턴
- spring batch 5.0
- 코딩테스트 준비
- 단기개발자코스
- jwttoken
- 구글 OAuth login
- Python
- 파이썬
- 취업리부트코스
- 커스텀 헤더
- 99클럽
- @FeignClient
- 프로그래머스
- 개발자 취업
- DesignPattern
- 빈 충돌
- 1주일회고
- JavaScript
- KPT회고
- TiL
- 프로그래머스 이중우선순위큐
- Spring multimodule
- 개발자부트캠프추천
- jwt
- 전략패턴 #StrategyPattern #디자인패턴
- 인프콘 2024
- 디자인 패턴
- Today
- Total
m1ndy5's coding blog
Ditto 프로젝트 N+1 문제 2편 - N+1 문제 해결과 외래키 본문
Ditto 프로젝트 N+1 문제 2편 - N+1 문제 해결과 외래키
정민됴 2024. 2. 27. 02:27프로젝트에서 N+1의 문제가 어디서 날까~ 하고 찾아봤더니 한 군데서도 발생하지 않았다...ㅋㅋㅋㅋㅋㅋ
이미 모듈도 잘게 쪼개놨고 외래키가 그닥 많이 쓰이고 있지 않았기 때문에 오히려 외래키를 없애야겠다는 생각이 들었다.
그래도 N+1 문제에 대해 공부했으니 N+1 문제의 해결법과 외래키에 대해서 정리하는 시간을 가져보려고 한다!
1. Lazy Loading과 Fetch Join을 사용해 쿼리 작성
Lazy Loading은 필요할 때만 연관된 데이터를 가져오는 전략이고 Fetch Join은 특정 연관된 데이터를 즉시 로딩하는 방법이다.
이를 조합하여 효과적으로 N+1 문제를 해결할 수 있다.
@Entity
public class Post {
// ... 다른 필드들
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Comment> comments;
// ... 다른 메서드들
}
예시로 @OneToMany로 매핑이된 comments 리스트가 Post 엔티티에 있다고 가정하자
예를들어 Post를 가져올 때 해당 comments 리스트까지 모두 가져오고 싶다면
List<Post> posts = entityManager.createQuery(
"SELECT p FROM Post p JOIN FETCH p.comments", Post.class)
.getResultList();
이러한 쿼리를 사용해서 가져올 수 있다.
또는 리포지토리에서
@Query("SELECT p FROM Post p JOIN FETCH p.comments")
List<Post> findAllFetchJoin();
이런식으로 쿼리를 직접 작성해준다면 한 번의 쿼리로 게시물과 그에 딸린 댓글들을 함께 가져오게 된다.
2.@EntityGraph 사용
@EntityGraph를 사용하면 엔티티 그래프를 정의하여 특정 엔티티를 로딩할 때 필요한 연관된 엔티티들도 함께 로딩할 수 있다.
@Entity
public class Post {
// ... 다른 필드들
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<Comment> comments;
// ... 다른 메서드들
}
@Entity
public class Comment {
// ... 다른 필드들
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
// ... 다른 메서드들
}
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
@EntityGraph(attributePaths = "comments")
List<Post> findAll();
}
List<Post> posts = postRepository.findAll();
이렇게 하면 findAll 메서드를 호출할 때 게시물과 그에 딸린 댓글이 함께 로딩된다.
@EntityGraph는 특정 쿼리 메서드에만 적용할 수도 있다.
이제 외래키에 대해 생각을 해보자!!
이전에 lock에 관련된 게시글을 올렸을 때 실무에서 외래키를 쓰지 않는 이유 중 하나로 lock의 전파가 발생해 데드락이 일어날 확률이 높아지는 것이 있었다. 또 다른 이유들도 찾아봤는데,
1. 성능 문제
대규모 트랜잭션 처리나 데이터베이스 쿼리 수행 시 외래키 제약 조건은 성능에 영향을 미칠 수 있다.
특히 데이터베이스가 복잡한 쿼리를 처리하거나, 많은 테이블 간의 조인이 필요한 경우에는 외래키로 인한 성능 저하가 발생할 수 있다.
2. 데이터 마이그레이션 및 유지보수의 어려움
데이터베이스 스키마를 변경하거나 테이블 간의 관계를 조정할 때 외래키가 존재하면 데이터 마이그레이션과 유지보수가 복잡해질 수 있다.
외래키 제약이 없는 경우에 변경이 더 유연하게 이루어질 수 있다.
3. 데이터 독립성 유지
외래키 제약이 있는 경우 두 테이블 간의 강력한 의존성이 생기기 때문에 데이터 독립성이 낮아진다.
특정 테이블을 다른 데이터베이스로 이동하거나 독립적으로 유지해야하는 경우에는 외래키를 사용하지 않을 수 있다.
등등의 이슈가 있었고 따라서 실무에서는 외래키를 잘 사용하지 않는다고 한다.
내 프로젝트에서 또한 외래키가 아니면 안돼!! 하는 부분은 없었고 오히려 불필요하게 쓰이고 있었기 때문에 연관관계 매핑대신 일반 Long field로 id값을 가지고 있게 설계를 변경했다.
결론
N+1 문제를 해결하는 방법에는 Fetch join을 사용하는 것과 @EntityGraph를 사용하는 것이 있으며, 내 프로젝트에서는 해당 외래키로 부모 엔티티에서 특정 필드를 가져오는 것과 같은 로직은 없었고 오히려 불필요하게 매핑이 되어있었기 때문에 연관관계를 없애주었다.
'Toy Projects > Ditto - Discuss Today's Topic' 카테고리의 다른 글
Ditto 프로젝트 docker-compose 1편 - Docker란, Docker Cache, Docker Network (0) | 2024.03.07 |
---|---|
Ditto 프로젝트 종목 불러오기 with batchUpdate (1) | 2024.02.29 |
Ditto 프로젝트 N+1 문제 1편 - FetchType Lazy, Eager (0) | 2024.02.26 |
Ditto 프로젝트 동시성 문제 해결하기 2편 - 낙관적 락을 사용해 동시성 해결하기 (feat. Deadlock, DirtyChecking, Retry) (1) | 2024.02.24 |
Ditto 프로젝트 동시성 문제 해결하기 1편 - 낙관적 락 VS 비관적 락 (1) | 2024.02.24 |