2025.01.13
이전 글에서는 밸런스 게임 조회 기능을 구현하면서 페이징 처리, 날짜 포맷팅, 참여 가능 여부 계산, 이전/다음 게임 이동 기능을 설명했다.
이번 글에서는 밸런스 게임 조회 시 함께 제공되는 댓글 목록과 댓글 작성자의 선택 정보를 효율적으로 조회하기 위한 방법을 다룬다.
단순한 JPA 기본 조회 방식만 사용하면, 밸런스 게임 조회 시 댓글 작성자 정보와 사용자 선택 정보를 각각 조회하면서 N+1 문제가 발생할 수 있다. 이를 해결하기 위해 다음의 2가지 최적화 전략을 적용했다.
- @EntityGraph를 사용해 댓글과 작성자 정보를 한 번의 쿼리로 조회
- 댓글 작성자의 선택 정보를 사전에 조회하여 Map을 사용한 임시 저장 방식으로 반복 쿼리 방지
1. 최적화 내용
@EntityGraph로 댓글 작성자 정보 조회 최적화
댓글 작성자의 게임 선택 정보를 조회할 때, 각 댓글마다 DB를 조회하게 되면 역시 N+1 문제가 발생한다. 이를 방지하기 위해 모든 선택 정보를 미리 조회하여 Map에 임시 저장했다.
CommentRepository
@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
@EntityGraph(attributePaths = {"writer"})
@Query("SELECT c FROM Comment c WHERE c.game = :game")
List<Comment> findCommentsWithWriterByBalanceGame(BalanceGame game);
}
- 하나의 쿼리로 모든 게임의 사용자 선택 정보를 조회한다.
- Map<게임ID, Map<작성자ID, 선택값>> 형태로 구성하여, 댓글 작성자의 선택 정보를 반복 쿼리 없이 O(1)로 조회 가능하다.
- 이후 댓글 DTO 변환 시 Map에서 선택 정보를 조회하여 별도의 DB 접근 없이 O(1) 시간 복잡도로 처리한다.
사용자 선택 정보 Map 임시 저장
댓글 작성자의 게임 선택 정보를 조회할 때, 작성자마다 DB를 조회하게 되면 역시 N+1 문제가 발생한다. 이를 방지하기 위해 모든 선택 정보를 미리 조회하여 Map에 캐싱했다.
getBalanceGame의 사용자 선택 정보 조회 로직
// 사용자 선택 정보 조회
List<BalanceGameChoice> choices = balanceGameChoiceRepository.findByGameIds(gameIds);
Map<Long, Map<Long, EChoice>> allChoicesByGame = choices.stream()
.collect(Collectors.groupingBy(
choice -> choice.getGame().getId(),
Collectors.toMap(
choice -> choice.getUser().getId(),
BalanceGameChoice::getChoice
)
));
- 하나의 쿼리로 모든 게임의 사용자 선택 정보를 조회한다.
- Map<게임ID, Map<작성자ID, 선택값>> 형태로 구성하여, 댓글 작성자의 선택 정보를 반복 쿼리 없이 O(1)로 조회 가능하다.
- 이후 댓글 DTO 변환 시 Map에서 선택 정보를 조회하여 별도의 DB 접근 없이 O(1) 시간복잡도로 처리한다.
2. 개선 전과 개선 후
구분 | 개선 전 | 개선 후 |
댓글 작성자 조회 | 댓글마다 개별 쿼리 발생 (N+1) | @EntityGraph로 한 번에 조회 |
사용자 선택 정보 | 댓글마다 선택 정보 개별 조회 | 선택 정보 전체 조회 후 Map으로 임시 저장 |
전체 쿼리 수 | 댓글 수 x 2~3배 | 게임 수 + 댓글 수 정도로 감소 |
개선 후 댓글 조회 흐름
- 게임 리스트 조회 (Page<BalanceGame>)
- 게임 ID 리스트 추출
- 사용자 선택 정보 일괄 조회 → Map<게임 ID, Map<작성자 ID, 선택값>> 생성
- 각 게임에 대해
- 댓글 + 작성자 정보 조회 (@EntityGraph)
- 댓글 작성자 선택값 Map에서 조회
- BalanceGameChoiceListResponseDto로 변환
- 게임 ID 리스트 추출
결론 및 인사이트
최적화 작업을 통해 댓글, 작성자 정보, 그리고 선택 정보까지 연관된 모든 데이터를 중복 쿼리 없이 효율적으로 조회할 수 있었다. 댓글 조회 기능은 단순히 댓글만 불러오는 것이 아니라, 연관된 작성자나 선택 정보까지 함께 조회되기 때문에 데이터 로딩을 전략적으로 구현하는 것이 필요했다.
- @EntityGraph는 단순한 연관 로딩 최적화에 유용하다.
- 선택 정보와 같이 사용자-게임 간 교차되는 정보는 사전에 조회하여 Map 임시 저장하는 것이 효율적이다.
- 이를 통해 실제 댓글 수만큼 발생할 수 있었던 N+1 쿼리를 방지했다.
반응형
'Projects > 청하-청년을 위한 커뮤니티 서비스' 카테고리의 다른 글
[청하] Open API 호출 시 500 오류 발생 및 해결 과정 (0) | 2025.05.23 |
---|---|
[청하] 청년정책 Open API 개편에 따른 데이터 비교 (기존 vs 개선후) (1) | 2025.05.15 |
[청하] TimeFormatter 클래스 리팩터링 (feat. @UtilityClass) (0) | 2025.04.04 |
[청하] 청년 정책 검색 기능 - (2) 구현 (0) | 2025.04.04 |
[청하] Swagger UI 개선 - API 문서 가독성 향상 (0) | 2025.04.03 |
댓글