[이전 편]
- 1편 : [Spring Boot] Spring 대용량 데이터 페이징 처리하기 1탄(Spring Data JPA + PostgreSQL)
이전편을 보고 오시면 N+1 문제 이외에 Page 객체를 사용함으로 발생한 테이블 풀스캔 문제와 인덱스 생성, 쿼리실행 계획 등 성능최적화 부분에서 알 수 있는 부분들이 있으니 안 보신분들은 보고 오시면 2편을 이해하시는데 도움이됩니다.
저는 PostgreSQL DB에 데이터 5천만건을 삽입하고 이를 최적화하는 작업을 했으며 1탄과 이어집니다.
N+1 문제 해결하기
해당 코드는 postId를 가져와서 postId를 기반으로 Hashtag 엔티티, Image 엔티티를 불러오는 로직이 존재합니다.
이 문제의 원인은 postHashtagRepository.findByPostId(post.getId()); 이부분 입니다. post.getId(); 메서드는 이미 1차캐시에 존재하기 때문에 추가적인 조회가 일어나지 않았지만 findByPostId(); 메서드는 각각의 해시태그 엔티티를 가져오기 위해서 개별 쿼리를 계속 날리게 되어 N+1 문제가 발생하게 됩니다.
이를 해결하기 위해 Hashtag, Image Repository를 수정했습니다.
@Repository
public interface PostHashtagRepository extends JpaRepository<PostHashtag, Long> {
List<PostHashtag> findByPostIdIn(List<Long> postIds);
}
@Repository
public interface ImageRepository extends JpaRepository<Image, Long> {
List<Image> findByPostIdIn(List<Long> postIds);
}
findByPostId를 사용하는 대신 findByPostIdIn()메서드를 사용하여 PostId 리스트를 인자값으로 주어서 한번에 객체를 가져올 수 있도록 하였습니다. 실행순서는 다음과 같습니다.
- 첫번째 findAll 쿼리에서 첫번째 페이지 조회 SELECT * FROM post ORDER BY created_at DESC LIMIT 20 OFFSET 0; Slice 객체에 post 엔티티 객체가 리스트로 생성
- findByPostIdIn 쿼리로 한페이지에 한번만 쿼리 실행
최적화 전 : 1페이지 -> post 쿼리 1번, Image 쿼리 20번, Hashtag 쿼리 20번 발생
최적화 후 : 1페이지 -> post 쿼리 1번, image 쿼리, hashtag 쿼리 3번 발생
첫번째 쿼리는 1~20번째까지 post 요청 쿼리
두번째 쿼리는 1~20번째 게시물 id를 사용하여 전체 hashtag 가져오는 쿼리
세번째 1~20번째 게시물id 사용해서 image 가져오는 쿼리
쿼리는 3번만 실행됩니다.
1탄에서 5천만건 테이블 풀스캔으로 서버 응답없음 상태였다가 Page 객체 -> Slice 객체로 변경 / created_at에 인덱스 생성하니 정상 동작되며 API 속도 : 254ms 였습니다.
이제 N+1 문제까지 해결하니 API 속도 : 69ms 로 최적화 되었습니다.
정리
- 대용량 데이터는 인덱스를 활용해야 한다.
- 페이징 객체의 특성을 잘 알고 사용해야한다.
- 테이블 풀스캔이 일어나는지 인덱스 스캔이 일어나는지 쿼리 실행계획 EXPLAIN ANALYZE를 꼭 실행해보자.
- N+1 문제가 일어나지 않는지 임의의 더미 대용량 데이터로 미리 테스트 해보자.
- 1차 최적화 : 응답없음 -> 254ms
- 2차 최적화 : 254ms -> 69ms
'Spring' 카테고리의 다른 글
[Spring Boot] Spring 대용량 데이터 페이징 처리하기 1탄(Spring Data JPA + PostgreSQL) (0) | 2025.02.19 |
---|---|
[Spring Boot]Gradle 의존성 implementation vs runtimeOnly vs api 차이 (0) | 2025.02.17 |
[Spring] WebClient를 사용 해야하는 이유 [RestTemplate vs WebClient] 성능비교 (0) | 2024.06.08 |
[Spring Boot] Response Entity를 사용하는 이유와 잘 사용하는 법 (0) | 2024.06.01 |
[Spring Boot] @PostConstruct 사용법 (0) | 2024.05.23 |