-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR]Zzim리스트 조회 시, User,Place,Photo,PostCategory 관련 N+1 문제 해결 #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| @Override | ||
| public Map<Long, PostCategory> findPostCategoriesByPostIds(List<Long> postIds) { | ||
| List<PostCategoryEntity> postCategoryEntities = postCategoryRepository.findPostCategoriesByPostIds(postIds); | ||
|
|
||
| return postCategoryEntities.stream() | ||
| .map(PostCategoryMapper::toDomain) // PostCategoryEntity -> PostCategory 변환 | ||
| .collect(Collectors.toMap( | ||
| postCategory -> postCategory.getPost().getPostId(), | ||
| postCategory -> postCategory, | ||
| (existing, replacement) -> existing // 중복 방지 | ||
| )); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: 말했다시피 2차 스프린트 때 하나의 게시물이 여러 카테고리를 갖도록 수정될 것 같습니다! 고려해서 다시 한번 생각해주셔도 좋을 것 같아요.
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Table(name = "photo") | ||
| @BatchSize(size = 10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Post와 Photo가 1:N 관계라서 @BatchSize가 적용되지 않을 것 같습니다!
| @Query("SELECT p FROM PhotoEntity p WHERE p.post.postId IN :postIds GROUP BY p.post.postId") | ||
| List<PhotoEntity> findFirstPhotosByPostIds(@Param("postIds") List<Long> postIds); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 해당 쿼리는 게시물의 "첫 번째" 사진만을 가져와야 하는데, 현재 쿼리는 해당 로직이 없는 것 같습니다.
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Table(name = "post_category") | ||
| @BatchSize(size = 10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 마찬가지로 @BatchSize가 적용되지 않아 불필요한 코드로 보입니다.
| } | ||
| @Override | ||
| public Map<Long, Photo> findFirstPhotosByPostIds(List<Long> postIds) { | ||
| List<Photo> photos = photoRepository.findFirstPhotosByPostIds(postIds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 이렇게 되면 실질적으로 (photoRepository의) findFirstPhotosByPostIds는 첫 번째 사진을 가져오는 메서드가 아니게 되는 것 같습니다. 메서드명에 혼돈이 생기고, 불필요한 조회도 발생할 것 같아요.
| .collect(Collectors.toMap( | ||
| photo -> photo.getPost().getPostId(), | ||
| photo -> photo, | ||
| (existing, replacement) -> existing // 중복 방지 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 해당 부분에서 게시물 당 사진 1개만 남기고 있긴 하지만, 조회 메서드 자체를 수정하는게 나아보입니다.
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import org.hibernate.annotations.BatchSize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: 이렇게 사용되지 않는 import는 최종 커밋 전에 깔끔하게 청소해주면 더 좋을 것 같아요!
| @EntityGraph(attributePaths = {"photos", "postCategories", "postCategories.category"}) | ||
| @Query("SELECT p FROM PostEntity p WHERE p.postId = :postId") | ||
| Optional<PostEntity> findPostWithPhotosAndCategories(@Param("postId") Long postId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 현재 Entity 분리를 감안했을 때 해당 부분이 의도대로 동작할지 의문입니다.
| @Query("SELECT pc FROM PostCategoryEntity pc WHERE pc.post.postId IN :postIds") | ||
| List<PostCategoryEntity> findPostCategoriesByPostIds(@Param("postIds") List<Long> postIds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 부분 구현이 가장 예상했던 형태에 가까운 것 같습니다.
| void saveMenu(Menu menu); | ||
| void savePhoto(Photo photo); | ||
| void saveScoopPost(User user, Post post); | ||
| Map<Long, PostCategory> findPostCategoriesByPostIds(List<Long> postIds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 개별 게시물이 여러 카테고리를 갖도록 변경된다는 점이 반영되면 좋을 것 같습니다.
| .map(zzimPost -> zzimPost.getPost().getPostId()) | ||
| .toList(); | ||
|
|
||
| Map<Long, Photo> firstPhotos = zzimPostPort.findFirstPhotosByPostIds(postIds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 결과적으로는 의도대로 동작할 것 같지만, 위의 피드백처럼 postRepository의 findFirstPhotosByPostIds 쿼리 자체에서 첫 번째 사진만 가져오도록 하는 수정 고려해주세요!
| .toList(); | ||
|
|
||
| Map<Long, Photo> firstPhotos = zzimPostPort.findFirstPhotosByPostIds(postIds); | ||
| Map<Long, PostCategory> postCategories = postPort.findPostCategoriesByPostIds(postIds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 같은 코멘트를 여러 번 남기고 있지만, 카테고리 복수 선택 가능! 꼭 기억해주세요.
| Place place = post.getPlace(); | ||
| Photo photo = zzimPostPort.findFistPhotoById(post.getPostId()); | ||
| PostCategory postCategory = postCategoryPort.findPostCategoryByPostId(post.getPostId()); | ||
| Photo photo = firstPhotos.get(post.getPostId()); // 🔥 Batch 조회된 결과 사용 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
왜 주석이 "Batch 조회된 결과 사용" 인가요?
| Photo photo = zzimPostPort.findFistPhotoById(post.getPostId()); | ||
| PostCategory postCategory = postCategoryPort.findPostCategoryByPostId(post.getPostId()); | ||
| Photo photo = firstPhotos.get(post.getPostId()); // 🔥 Batch 조회된 결과 사용 | ||
| PostCategory postCategory = postCategories.get(post.getPostId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: 이 부분도 카테고리 복수 선택 가능 고려해야 할 겁니다.
|
|
||
| // //사용자 지도 리스트 조회 | ||
| // public ZzimCardListResponseDTO getZzimCardList(ZzimGetCardCommand command) { | ||
| // List<ZzimPost> zzimPostList = zzimPostPort.findUserByUserId(command.getUserId()); | ||
| // | ||
| // Map<Long, ZzimPost> uniquePlacePostMap = new LinkedHashMap<>(); | ||
| // | ||
| // for (ZzimPost zzimPost : zzimPostList) { | ||
| // Place place = zzimPost.getPost().getPlace(); | ||
| // if (place == null) { | ||
| // throw new BusinessException(PlaceErrorMessage.PLACE_NOT_FOUND); | ||
| // } | ||
| // | ||
| // Long placeId = place.getPlaceId(); | ||
| // if (!uniquePlacePostMap.containsKey(placeId)) { | ||
| // uniquePlacePostMap.put(placeId, zzimPost); | ||
| // } | ||
| // } | ||
| // | ||
| // List<ZzimCardResponseDTO> zzimCardResponses = uniquePlacePostMap.values().stream() | ||
| // .map(zzimPost -> { | ||
| // Post post = zzimPost.getPost(); | ||
| // Place place = post.getPlace(); | ||
| // Photo photo = zzimPostPort.findFistPhotoById(post.getPostId()); | ||
| // PostCategory postCategory = postCategoryPort.findPostCategoryByPostId(post.getPostId()); | ||
| // | ||
| // CategoryColorResponseDTO categoryColorResponse = new CategoryColorResponseDTO( | ||
| // postCategory.getCategory().getCategoryId(), | ||
| // postCategory.getCategory().getCategoryName(), | ||
| // postCategory.getCategory().getIconUrlColor(), | ||
| // postCategory.getCategory().getTextColor(), | ||
| // postCategory.getCategory().getBackgroundColor()); | ||
| // | ||
| // | ||
| // return new ZzimCardResponseDTO( | ||
| // place.getPlaceId(), // placeId 추가 | ||
| // place.getPlaceName(), | ||
| // place.getPlaceAddress(), | ||
| // post.getTitle(), | ||
| // photo.getPhotoUrl(), | ||
| // place.getLatitude(), | ||
| // place.getLongitude(), | ||
| // categoryColorResponse | ||
| // ); | ||
| // }) | ||
| // .collect(Collectors.toList()); | ||
| // | ||
| // return new ZzimCardListResponseDTO(zzimCardResponses.size(), zzimCardResponses); | ||
| // } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 주석들도 최종 커밋 시 꼭 정리해주세요! (혼동 방지)
airoca
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코멘트 남긴 부분들 체크해주시고, 꼭!!!! 수정된 API 전부 테스트 해주셔야 합니다.
f3757a2 to
982f9d7
Compare
📝 Work Description
Zzim 리스트 조회 시 발생했던 N+1 문제를 해결하기 위해, User, Place, Photo, PostCategory 조회 방식을 최적화했습니다.
기존에는 개별적으로 쿼리를 실행하여 데이터를 가져왔으나, 전역 Batch Size 설정 + IN 절 조회 방식으로 쿼리를 최적화하여 성능을 개선했습니다.
페이징을 고려하여 Fetch Join 대신 BatchSize 적용을 활용했습니다.
1. User, Place N+1 문제
@BatchSize를 추가하여 기본 BatchSize를 사용하지 않는 환경에서도 동일한 효과를 기대할 수 있도록 했습니다.2. Photo, PostCategory N+1 문제
⚙️ Issue
🔨 Changes
1. User, Place 조회 방식 변경:
전역 BatchSize 설정(hibernate.default_batch_fetch_size) 적용.
UserEntity와 PlaceEntity에
@BatchSize(size = 100)추가.2. Photo, PostCategory IN 절 조회 적용:
기존에는 개별 Post별로 Photo와 PostCategory를 각각의 쿼리로 조회.
새로운 쿼리 메서드를 추가하여 IN 절로 다수의 Post에 대한 Photo와 PostCategory 데이터를 한 번에 가져옴.
결과적으로 Photo와 PostCategory 조회에서 발생하던 N+1 문제 해결.
3. Adapter 및 Port 인터페이스 수정:
4. Repository 수정: