-
Notifications
You must be signed in to change notification settings - Fork 2
게시글 검색 및 목록 조회 성능 개선 #147
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: develop
Are you sure you want to change the base?
게시글 검색 및 목록 조회 성능 개선 #147
Conversation
|
Note
|
| 응집 / 파일(s) | 변경 요약 |
|---|---|
저장소 계층 - 배치 조회 메서드 추가 src/main/java/im/toduck/domain/social/persistence/repository/SocialImageFileRepository.java, CommentRepository.java, LikeRepository.java, SocialCategoryLinkRepository.java |
각 저장소에 배치 조회를 위한 커스텀 쿼리 메서드 추가 (findAllBySocialIdIn, countBySocialIdIn, findSocialIdsByUserAndSocialIdIn, findAllBySocialIdInWithCategory). |
프로젝션 인터페이스 src/main/java/im/toduck/domain/social/persistence/projection/SocialCommentCount.java |
소셜 ID별 댓글 수 조회용 새 프로젝션 인터페이스 추가 (getSocialId(), getCount() 메서드 포함). |
서비스 계층 - 배치 조회 메서드 추가 src/main/java/im/toduck/domain/social/domain/service/SocialBoardService.java, SocialInteractionService.java |
getSocialImagesBySocialIds, countCommentsBySocialIds, getLikedSocialIdsByUserAndSocialIds 등 배치 데이터 조회 메서드 추가. |
서비스 계층 - 메서드 시그니처 변경 src/main/java/im/toduck/domain/social/domain/service/SocialCategoryService.java |
getSocialCategoryDtosBySocial(Social) → getSocialCategoryDtosBySocialIds(List<Long>)로 변경; 반환 타입을 List<SocialCategoryDto>에서 Map<Long, List<SocialCategoryDto>>로 변경. |
매퍼 유틸리티 src/main/java/im/toduck/domain/social/common/mapper/SocialMapper.java |
toSocialResponses 배치 매핑 메서드 추가. 이미지, 댓글 수, 카테고리, 좋아요 정보를 Map/Set으로 수령하여 SocialResponse 리스트 생성. |
유스케이스 계층 src/main/java/im/toduck/domain/social/domain/usecase/SocialBoardUseCase.java, SocialProfileUseCase.java |
개별 응답 구성 로직을 buildSocialResponses 헬퍼 메서드로 대체. 배치 데이터 조회 후 매퍼를 통해 일괄 변환. |
저장소 커스텀 구현 src/main/java/im/toduck/domain/social/persistence/repository/querydsl/SocialRepositoryCustomImpl.java |
findSocialsExcludingBlocked, searchSocialsExcludingBlocked 쿼리에 user, routine Fetch Join 추가. |
시퀀스 다이어그램
sequenceDiagram
participant UC as SocialBoardUseCase
participant SCS as SocialCategoryService
participant SIS as SocialInteractionService
participant SBS as SocialBoardService
participant SM as SocialMapper
participant Repo as Repository Layer
rect rgb(200, 220, 240)
Note over UC,Repo: 기존 흐름 (개별 조회)
UC->>UC: forEach Social
UC->>SCS: getSocialCategoryDtosBySocial(single)
SCS->>Repo: query per social
Repo-->>SCS: result
SCS-->>UC: result
end
rect rgb(220, 240, 200)
Note over UC,Repo: 새로운 흐름 (배치 조회)
UC->>UC: collect socialIds
UC->>SBS: getSocialImagesBySocialIds(ids)
SBS->>Repo: findAllBySocialIdIn(ids)
Repo-->>SBS: imagesByIdMap
SBS-->>UC: imagesByIdMap
UC->>SIS: countCommentsBySocialIds(ids)
SIS->>Repo: countBySocialIdIn(ids)
Repo-->>SIS: commentCountMap
SIS-->>UC: commentCountMap
UC->>SIS: getLikedSocialIdsByUserAndSocialIds(user, ids)
SIS->>Repo: findSocialIdsByUserAndSocialIdIn
Repo-->>SIS: likedIdSet
SIS-->>UC: likedIdSet
UC->>SCS: getSocialCategoryDtosBySocialIds(ids)
SCS->>Repo: findAllBySocialIdInWithCategory(ids)
Repo-->>SCS: categoryDtosByIdMap
SCS-->>UC: categoryDtosByIdMap
UC->>SM: toSocialResponses(socials, images, counts, likes, categories)
SM-->>UC: SocialResponse List
end
예상 코드 리뷰 난이도
🎯 3 (중간) | ⏱️ ~25분
- 특히 검토 필요한 부분:
SocialBoardUseCase.java와SocialProfileUseCase.java의buildSocialResponses로직 및 데이터 흐름 일관성 확인SocialCategoryService.getSocialCategoryDtosBySocialIds메서드의 스트림 그룹화 로직 정확성- 저장소 계층의 새 쿼리 메서드들이 N+1 문제를 올바르게 해결하는지 확인 (특히 Fetch Join 적용)
SocialMapper.toSocialResponses에서getOrDefault사용 시 null/빈 데이터 처리의 안정성- 트랜잭션 범위와 읽기 전용 설정이 모든 배치 메서드에서 적절한지 확인
시
🐰 배치로 모아 한번에
데이터베이스 가벼워져
N+1의 악몽 사라지고
쿼리는 우아하게 날아다니네
토끼도 박수 짝짝!
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | PR 제목 '게시글 검색 및 목록 조회 성능 개선'은 변경 사항의 주요 목적을 명확하게 요약하며, N+1 쿼리 문제 해결을 통한 성능 최적화라는 핵심을 잘 전달합니다. |
| Description check | ✅ Passed | PR 설명이 필수 섹션을 모두 포함하고 있으며, 작업 내용, 성능 개선 효과, 테스트 환경을 명확하게 설명하고 있습니다. |
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
perf/social-list-optimization
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
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.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/main/java/im/toduck/domain/social/persistence/repository/querydsl/SocialRepositoryCustomImpl.java (1)
37-38: 페치 조인으로 N+1 문제를 효과적으로 해결했습니다.
user와routine연관관계에 대한 페치 조인을 추가하여 게시글 목록 조회 시 발생하던 N+1 쿼리를 해결했습니다.leftJoin().fetchJoin()패턴을 사용한 것이 적절합니다. 검증 결과SocialMapper.toSocialResponse()메서드에서routine필드가 실제로 응답 매핑에 사용되므로, line 82의findUserSocials메서드도routine에 대한 페치 조인을 추가하기를 권장합니다. 이렇게 하면 다른 조회 메서드들과의 일관성을 유지하고 N+1 쿼리 문제를 완전히 방지할 수 있습니다..leftJoin(qSocial.user).fetchJoin() .leftJoin(qSocial.routine).fetchJoin()src/main/java/im/toduck/domain/social/domain/usecase/SocialBoardUseCase.java (1)
270-298: N+1 쿼리 해결을 위한 일괄 데이터 조회 방식 적용이 잘 되어 있습니다.빈 리스트에 대한 early return, 각 연관 데이터(이미지, 댓글 수, 좋아요, 카테고리)의 일괄 조회 및 매핑 로직이 깔끔하게 구현되어 있습니다.
한 가지 고려사항:
sif.getSocial().getId()호출 시getSocial()이 프록시인 경우 추가 쿼리가 발생할 수 있습니다. Repository 쿼리에서@Query로 social ID를 직접 가져오거나, projection을 사용하면 더 최적화할 수 있습니다.// SocialImageFileRepository에서 projection 또는 직접 조인으로 social_id를 가져오는 방안 검토 // 예: List<Object[]> findBySocialIdInWithSocialId(List<Long> socialIds); // 또는 @Query("SELECT sif FROM SocialImageFile sif JOIN FETCH sif.social WHERE sif.social.id IN :socialIds")
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/main/java/im/toduck/domain/social/common/mapper/SocialMapper.java(2 hunks)src/main/java/im/toduck/domain/social/domain/service/SocialBoardService.java(1 hunks)src/main/java/im/toduck/domain/social/domain/service/SocialCategoryService.java(2 hunks)src/main/java/im/toduck/domain/social/domain/service/SocialInteractionService.java(3 hunks)src/main/java/im/toduck/domain/social/domain/usecase/SocialBoardUseCase.java(4 hunks)src/main/java/im/toduck/domain/social/domain/usecase/SocialProfileUseCase.java(5 hunks)src/main/java/im/toduck/domain/social/persistence/projection/SocialCommentCount.java(1 hunks)src/main/java/im/toduck/domain/social/persistence/repository/CommentRepository.java(2 hunks)src/main/java/im/toduck/domain/social/persistence/repository/LikeRepository.java(2 hunks)src/main/java/im/toduck/domain/social/persistence/repository/SocialCategoryLinkRepository.java(2 hunks)src/main/java/im/toduck/domain/social/persistence/repository/SocialImageFileRepository.java(2 hunks)src/main/java/im/toduck/domain/social/persistence/repository/querydsl/SocialRepositoryCustomImpl.java(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/im/toduck/domain/social/domain/usecase/SocialBoardUseCase.java (1)
src/main/java/im/toduck/global/util/PaginationUtil.java (1)
PaginationUtil(8-82)
src/main/java/im/toduck/domain/social/domain/service/SocialBoardService.java (1)
src/test/java/im/toduck/domain/social/domain/usecase/SocialBoardUseCaseTest.java (1)
Transactional(61-1179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: CI
🔇 Additional comments (12)
src/main/java/im/toduck/domain/social/persistence/projection/SocialCommentCount.java (1)
3-7: N+1 문제 해결을 위한 프로젝션 인터페이스가 잘 설계되었습니다.Spring Data JPA 프로젝션 패턴을 올바르게 따르고 있으며,
CommentRepository.countBySocialIdIn메서드와 함께 여러 게시글의 댓글 수를 일괄 조회하는 데 사용됩니다. 이는 PR 목표인 N+1 쿼리 문제 해결에 핵심적인 역할을 합니다.src/main/java/im/toduck/domain/social/persistence/repository/SocialImageFileRepository.java (1)
17-18: 이미지 파일 일괄 조회 메서드가 잘 구현되었습니다.여러 게시글의 이미지를 한 번의 쿼리로 조회하여 N+1 문제를 해결합니다.
deletedAt IS NULL조건으로 소프트 삭제된 이미지를 올바르게 필터링하고 있습니다.src/main/java/im/toduck/domain/social/persistence/repository/CommentRepository.java (1)
37-40: 댓글 수 일괄 집계 쿼리가 효율적으로 구현되었습니다.여러 게시글의 댓글 수를 한 번의 쿼리로 집계하여 N+1 문제를 해결합니다.
GROUP BY를 사용한 집계 방식이 적절하며, 소프트 삭제된 댓글을 제외한 것도 올바릅니다.SocialCommentCount프로젝션과의 통합도 잘 되어 있습니다.src/main/java/im/toduck/domain/social/persistence/repository/LikeRepository.java (1)
19-21: 좋아요 상태 일괄 조회가 효율적으로 구현되었습니다.사용자가 좋아요한 게시글 ID만 조회하여 불필요한 엔티티 로딩을 방지하고, 여러 게시글에 대한 좋아요 상태를 한 번의 쿼리로 확인합니다. 이는 좋아요 상태 확인 시 발생하던 N+1 문제를 효과적으로 해결합니다.
src/main/java/im/toduck/domain/social/domain/service/SocialBoardService.java (1)
190-196: 일괄 이미지 조회 메서드가 잘 구현되었습니다.null/empty 입력에 대한 방어 로직이 적절하며,
@Transactional(readOnly = true)를 사용하여 읽기 전용 트랜잭션임을 명시했습니다. 이 메서드는buildSocialResponses에서 사용되어 N+1 문제 해결에 기여합니다.src/main/java/im/toduck/domain/social/domain/usecase/SocialProfileUseCase.java (2)
160-188: N+1 문제 해결을 위한 일괄 처리 패턴이 탁월하게 구현되었습니다.
buildSocialResponses메서드는 다음과 같은 효율적인 처리 흐름을 구현합니다:
- 게시글 ID 수집
- 모든 관련 데이터를 일괄 조회 (이미지, 댓글 수, 좋아요, 카테고리)
- 데이터를 Map/Set으로 구성하여 효율적인 조회 가능
SocialMapper.toSocialResponses로 일괄 매핑이 패턴은 PR 목표대로 페이지당 쿼리 수를 41개에서 5개로 대폭 감소시키는 핵심 개선사항입니다. 빈 리스트에 대한 조기 반환도 적절합니다.
110-111: 게시글 제한 로직이 올바른 위치에 배치되었습니다.
buildSocialResponses호출 전에limit(actualLimit)를 적용하여 불필요한 데이터 처리를 방지합니다. 이는 메모리 효율성과 성능 모두에 긍정적인 영향을 미칩니다.src/main/java/im/toduck/domain/social/persistence/repository/SocialCategoryLinkRepository.java (1)
17-20: 카테고리 링크 일괄 조회에 페치 조인이 효과적으로 적용되었습니다.
JOIN FETCH scl.socialCategory를 사용하여 카테고리 엔티티를 즉시 로딩함으로써, 카테고리 데이터 접근 시 발생할 수 있는 추가 쿼리를 방지합니다. 이는SocialCategoryService에서 카테고리 DTO를 구성할 때 성능을 크게 향상시킵니다.src/main/java/im/toduck/domain/social/domain/usecase/SocialBoardUseCase.java (1)
184-185: 페이지네이션 로직이 올바르게 구현되었습니다.
hasMore와nextCursor계산 후actualLimit으로 제한한 뒤 응답을 빌드하는 순서가PaginationUtil의 의도대로 잘 적용되어 있습니다.src/main/java/im/toduck/domain/social/domain/service/SocialInteractionService.java (2)
263-271: 좋아요 상태 일괄 조회 메서드가 잘 구현되었습니다.빈 입력에 대한 early return과 Set 반환으로 O(1) lookup이 가능합니다.
Minor: Line 266의
Set.of()는 불변 Set을, Line 270의new HashSet<>()은 가변 Set을 반환합니다. 호출자가 수정하지 않으므로 문제는 없지만, 일관성을 위해 둘 다Set.copyOf()나new HashSet<>()으로 통일하는 것도 고려해볼 수 있습니다.
249-261: 쿼리 설계가 이미 중복 키를 방지합니다.
@Query주석의GROUP BY c.social.id절이 각 socialId마다 정확히 하나의 결과를 보장하므로,Collectors.toMap에서 중복 키 문제는 발생하지 않습니다. null/empty 처리도 적절하고, 일괄 조회로 N+1 문제를 해결합니다. merge function 추가는 불필요합니다.src/main/java/im/toduck/domain/social/common/mapper/SocialMapper.java (1)
91-110: 일괄 매핑 메서드가 잘 구현되었습니다.
getOrDefault를 사용한 안전한 null 처리와Set.contains()를 통한 O(1) 좋아요 상태 조회가 적절합니다. 기존toSocialResponse메서드를 재사용하여 코드 중복을 방지한 점도 좋습니다.
| public Map<Long, List<SocialCategoryDto>> getSocialCategoryDtosBySocialIds(final List<Long> socialIds) { | ||
| if (socialIds == null || socialIds.isEmpty()) { | ||
| return Map.of(); | ||
| } | ||
|
|
||
| List<SocialCategoryLink> socialCategoryLinks = socialCategoryLinkRepository.findAllBySocialIdInWithCategory( | ||
| socialIds); | ||
|
|
||
| return socialCategoryLinks.stream() | ||
| .map(SocialCategoryLink::getSocialCategory) | ||
| .map(SocialCategoryMapper::toSocialCategoryDto) | ||
| .toList(); | ||
| .collect(Collectors.groupingBy( | ||
| link -> link.getSocial().getId(), | ||
| Collectors.mapping( | ||
| link -> SocialCategoryMapper.toSocialCategoryDto(link.getSocialCategory()), | ||
| Collectors.toList() | ||
| ) | ||
| )); | ||
| } |
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.
🧩 Analysis chain
일괄 카테고리 조회로 변경이 잘 되었습니다.
API 시그니처 변경과 그룹핑 로직이 적절합니다.
link.getSocial().getId() 호출 시 N+1 쿼리 방지를 위해 findAllBySocialIdInWithCategory 쿼리가 Social도 함께 fetch하는지 확인이 필요합니다.
🏁 Script executed:
#!/bin/bash
# Repository 메서드가 Social을 JOIN FETCH 하는지 확인
rg -n "findAllBySocialIdInWithCategory" --type java -A 5Length of output: 1037
---
🏁 Script executed:
```shell
#!/bin/bash
# View the full SocialCategoryLinkRepository.java file to see @Query annotation
cat src/main/java/im/toduck/domain/social/persistence/repository/SocialCategoryLinkRepository.java
Length of output: 845
---
🏁 Script executed:
```shell
#!/bin/bash
# Check if SocialCategoryLink entity has socialId field to avoid N+1
rg -n "class SocialCategoryLink" --type java -A 30
Length of output: 3568
findAllBySocialIdInWithCategory 쿼리에 Social JOIN FETCH 누락으로 N+1 쿼리 발생
SocialCategoryLinkRepository의 @query에서 socialCategory는 JOIN FETCH되지만 social은 fetch되지 않습니다. SocialCategoryService 32번 줄의 link.getSocial().getId() 호출 시 각 링크마다 별도의 쿼리가 발생하여 N+1 문제가 발생합니다.
수정 필요:
@Query("SELECT scl FROM SocialCategoryLink scl "
+ "JOIN FETCH scl.social "
+ "JOIN FETCH scl.socialCategory "
+ "WHERE scl.social.id IN :socialIds AND scl.deletedAt IS NULL")
List<SocialCategoryLink> findAllBySocialIdInWithCategory(@Param("socialIds") List<Long> socialIds);src/main/java/im/toduck/domain/social/persistence/repository/SocialCategoryLinkRepository.java:17
🤖 Prompt for AI Agents
In
src/main/java/im/toduck/domain/social/domain/service/SocialCategoryService.java
(around lines 22-38) the stream uses link.getSocial().getId() which triggers N+1
because the repository query doesn't fetch the Social; update the repository
query in
src/main/java/im/toduck/domain/social/persistence/repository/SocialCategoryLinkRepository.java
(around line 17) to JOIN FETCH scl.social as well as JOIN FETCH
scl.socialCategory (i.e. SELECT scl FROM SocialCategoryLink scl JOIN FETCH
scl.social JOIN FETCH scl.socialCategory WHERE scl.social.id IN :socialIds AND
scl.deletedAt IS NULL), keep the same method signature and @Param, and run tests
to verify the N+1 is resolved.
📝 Jacoco Test Coverage
|
🧪 Test Results397 tests 393 ✅ 24s ⏱️ Results for commit dc90940. |
✨ 작업 내용
1️⃣ N+1 쿼리 해결
[성능 향상]
※ 로깅 문제 해결 시, 응답 속도 492ms까지 추가 단축 가능 확인
📊 성능 개선 효과 비교
테스트 환경
주요 지표
1. N+1 해결
2. N+1 및 로깅 개선
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선
✏️ Tip: You can customize this high-level summary in your review settings.