[프로젝트] 댓글 조회시 N+1 문제 해결, 성능 비교

2025. 11. 26. 13:27·project

상품/ 레퍼런스 댓글 관련 기능을 구현하기 위해 Jpa 연관관계 설정과 조회 로직을 구성하였다.

그러나 실제로 API를 호출해본 결과, 단순 조회임에도 불구하고 예상보다 많은 수의 쿼리가 발생하는 것을 확인할 수 있었으며,

사용자의 데이터 양이 증가할수록 쿼리 수가 급격히 늘어나 성능 저하로 이어지는 구조임을 확인할 수 있었다.

 

 

N+1 문제란?

-> 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오는 현상

 

기존 코드 방식

public List<CommentResponse> getComments(Long userId, CommentableType type, Long commentableId) {
        // 1. 부모 댓글 목록 조회 (쿼리 1회)
        List<Comment> parentComments = commentRepository.findParentCommentsByCommentable(type, commentableId);

        // 2. 부모 댓글 목록을 순회 (N회 반복)
        return parentComments.stream() 
                .map(parent -> {
                    // 2-1. 부모 댓글 좋아요 상태 조회 (쿼리 N회 발생)
                    boolean isLiked = commentLikeRepository.existsByCommentIdAndUserId(parent.getId(), userId); 
                    
                    // 2-2. 해당 부모의 자식 댓글 조회 (쿼리 N회 발생)
                    List<Comment> children = commentRepository.findChildCommentsByParentId(parent.getId()); 

                    // 2-3. 자식 댓글 목록 순회 (총 N * M회 반복)
                    List<Boolean> childLikedList = children.stream() 
                            .map(child -> commentLikeRepository.existsByCommentIdAndUserId(child.getId(), userId)) // 쿼리 N * M회 발생
                            .collect(Collectors.toList());

                    return commentConverter.toDtoWithChildren(parent, userId, isLiked, children, childLikedList);
                })
                .collect(Collectors.toList());
    }
단계 동작 쿼리 횟수 (N=부모댓글 수, M=자식댓글 수)
1. 부모 댓글 조회 findParentCommentsByCommentable 1회
2. 부모 좋아요 상태 조회 existsByCommentIdAndUserId(parent.getId(), ...) N회
3. 자식 댓글 조회 findChildCommentsByParentId(parent.getId()) N회
4. 자식 좋아요 상태 조회 existsByCommentIdAndUserId(child.getId(), ...) N * M회
총 쿼리 수   1 + 2N + NM

 

  • 응답 속도 저하: 댓글 개수가 늘어날수록 쿼리 수가 선형적으로 증가하여 응답 시간이 기하급수적으로 느려진다. (예: 부모 10개, 자식 총 50개면 약 1 + 20 + 50 = 71번 쿼리 실행)
  • DB 부하 증가: 짧은 시간 안에 수많은 단순 쿼리가 DB에 집중되어 전체 서비스 성능을 저하시킨다.

 

 

총 쿼리 횟수: $1 + N + N + M = 1 + 2N + M... 회

이러한 비효율적인 방식이 N+1 문제이다.

예를 들어, 게시글에 부모 댓글 10개(N=10)와 총 50개의 자식 댓글(M=50)이 있다면 약 71번의 쿼리가 실행되어 성능이 매우 저하된다.

 

 

 

JpaRepository 에서 @Query를 통해 Fetch join을 사용하면, 프록시 객체를 사용하는 대신에 한번에 조회하여 N+1 문제를 해결할 수 있다.

 

배치 조회(Batch Fetching) 란? 

- 데이터를 하나씩 개별적으로 가져오는 대신, 필요한 모든 데이터를 몇번의 큰 쿼리로 한번에 가져와 메모리 상에서 조합하는 전략이다.

 

 @Query("SELECT c FROM Comment c JOIN FETCH c.user WHERE c.commentableType = :type AND c.commentableId = :id AND c.parentComment IS NULL ORDER BY c.createdAt DESC")
    List<Comment> findParentCommentsByCommentable(@Param("type") CommentableType type, @Param("id") Long id);

   @Query("SELECT c FROM Comment c JOIN FETCH c.user WHERE c.parentComment.id = :parentId ORDER BY c.createdAt ASC")
    List<Comment> findChildCommentsByParentId(@Param("parentId") Long parentId);

   // 여러 부모 댓글 ID에 속하는 모든 자식 댓글을 한 번에 조회. (N+1 문제 방지)
   @Query("SELECT c FROM Comment c JOIN FETCH c.user JOIN FETCH c.parentComment WHERE c.parentComment.id IN :parentIds ORDER BY c.createdAt ASC")
   List<Comment> findChildCommentsByParentIds(@Param("parentIds") List<Long> parentIds);

 

단계별 처리 과정

  • 데이터 준비: 쿼리 3회로 parentComments, childrenComments, likedCommentIds (Set)를 가져온다.
  • 구조화 (Map 생성): childrenComments를 부모 ID를 키로 하는 Map<Long, List<Comment>>으로 변환하여, 각 부모 댓글에 해당하는 자식 댓글을 O(1) 시간에 찾을 수 있도록 준비한다.
  • 반복문 조합:
  • for (Comment parent : parentComments) 반복문을 실행한다. (N회)
  • 좋아요 확인: likedCommentIds.contains(parent.getId())를 통해 메모리(Set) 상에서 즉시 좋아요 여부를 확인한다. (DB 쿼리 0회)
  • 자식 댓글 변환: childrenByParentId Map에서 자식 댓글 리스트를 가져와서, 다시 반복문을 돌며 DTO로 변환하고 좋아요 여부를 메모리에서 확인한다. (DB 쿼리 0회)
  • 최종 DTO를 리스트에 담아 반환한다.

 

핵심은 데이터베이스 접근 횟수를 줄이는 것이다.

 

 

결과

 

단계 동작 쿼리 횟수
1. 부모 댓글 조회 findParentCommentsByCommentable 1회
2. 모든 자식 댓글 일괄 조회 findChildCommentsByParentIds(parentIds) 1회
3. 모든 댓글의 좋아요 상태 일괄 조회 findLikedCommentIdsByUserAndCommentIds(...) 1회
4. Java 메모리에서 조합 (반복문 사용) 0회
총 쿼리 수   3회 (댓글 수와 관계없이 고정)

 

 

이 방식은 댓글 수에 상관없이 총 3번의 쿼리만 실행되도록 보장하여 N+1 문제를 해결 할 수 있다!

 

 

 

 

 

'project' 카테고리의 다른 글

[프로젝트] 주식 체결 알림 기능 추가 - 2  (1) 2025.01.23
[프로젝트] 주식 체결 알람 기능 추가 - 1  (2) 2025.01.15
'project' 카테고리의 다른 글
  • [프로젝트] 주식 체결 알림 기능 추가 - 2
  • [프로젝트] 주식 체결 알람 기능 추가 - 1
zioni
zioni
  • zioni
    jiwon's dev.log
    zioni
  • 전체
    오늘
    어제
    • 분류 전체보기 (78) N
      • spring & java (13)
      • Algorithm (14) N
      • PS (37)
      • project (3)
      • experience (3) N
      • etc (6)
      • study (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
  • 공지사항

  • 인기 글

  • 태그

    백준
    자바
    java
    백준2525
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
zioni
[프로젝트] 댓글 조회시 N+1 문제 해결, 성능 비교
상단으로

티스토리툴바