전에 게시글들을 보았으면 내가 얼마나 멍청하게 댓글들을 불러왔는지 알 수 있다.
사실 블로그에 글을 쓰고 인지를 하여 그냥 모든 개발이 끝나고 리팩토링 해야지 했는데 자꾸 걸려서 미리 리팩토링 하게 되었다.

전에는 댓글을 페이지네이션 하나 없이 모든 댓글 + 대댓글을 다 불러왔다. 사실 소량의 데이터면 상관은 없는데
사용자의 사용성은 나눠서 보여주는 게 낫기 때문에 한 번에 다 보여주는 것보단
대댓글은 숨기고 보여주고 할 필요가 있다고 생각했다.
페이지네이션은 잘 마쳤다!
(페이지네이션에 대한 내용은 전에 게시글 페이지네이션 했던 글 봐주세요)
이제 댓글에 대댓글이 있는지 정보를 클라이언트에 보내줘야 클라이언트가
-대댓글 더보기..-
뭐 이런 UI를 달아줄 수 있기 때문이다.
백엔드 쪽에선 단순히 true/false만 판단하면 되니까
"이건 가볍겠지" 라고 생각했다.
그래서 아래처럼 jpa를 사용하여 호출했다
commentRepository.existsByParentComment(comment);
이렇게 사용하여 실행하여 로그에 찍힌 쿼리를 보고
살짝 멈칫했다.

SELECT 1 FROM comment WHERE parent_comment_id = ? LIMIT 1;
쿼리는 단순하다.
parent_comment_id가 내가 찾는 댓글의 ID인 row가 있는지만 보면 된다.
근데 DB가 이걸 찾는 방법이 문제다.
WHERE parent_comment_id = ? <- 조건
parent_comment_id에 인덱스가 없으면 DB는
comment 테이블의 모든 행을 처음부터 끝까지 다 흝으면서, parent_comment_id가 내가 찾는 값이랑 같은 게 있나 찾아본다!!!
생각만 해도 끔찍하지 않나요?
이런 걸 Full Table Scan이라고 부르는데 댓글이 뭐 수십 개 수백 개까진 괜찮아도
수천, 수만(내 프로젝트에서 이럴일은 없지만..)개가 넘어가면 쿼리 속도가 슬슬 느려질 것이다.
그래서 나는 인덱스를 사용했다.
나는 초기 개발 단계여서 그냥 comment Entity 코드에 인덱스를 정의해 놓았다.
@Entity
@Table(name = "comment", indexes = {
@Index(name = "idx_parent_comment_id", columnList = "parent_comment_id")
})
public class Comment {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_comment_id")
private Comment parentComment;
// ...
}
개발이 이미 진행되고 리팩토링을 할 때는 DB에 직접
CREATE INDEX idx_parent_comment_id ON comment (parent_comment_id);
이렇게 적어도 된다.
이렇게 하면 DB가 parent_comment_id만 따로 정리된 구조(B-Tree)에 저장하고 필요할 때 바로 찾아갈 수 있다.
살짝 이해가 안 될 수도 있어서 쉽게 풀어보면
나처럼 인덱스를 추가하고 나서의 DB는 하나하나 바보처럼 다 찾는 게 아니라
parent_comment_id 컬럼만 따로 정리된 자료구조(B-Tree 인덱스)를 사용해서 찾는다.
쉽게 말해
parent_comment_id가 123인 row?
어, 인덱스에 있네?! -> 해당 row 위치도 같이 저장돼 있으니 바로 가서 읽자!!

아직도 좀 어려울 수도 있습니다.
좀 더 쉽게 보면
인덱스가 없을 때 (Full Table Scan)
1번 row → parent_comment_id = 123인가? ❌
2번 row → parent_comment_id = 123인가? ❌
3번 row → parent_comment_id = 123인가? ✅ → 결과 리턴!
인덱스가 있을 때 (Index Seek)
인덱스(B-Tree):
parent_comment_id | Row 위치
---------------------|---------------
101 | 2번 row
112 | 5번 row
123 | 8번 row ← 찾는 값
133 | 10번 row
인덱스는 책의 목차라고 생각하면 된다!
내가 원하는 내용이 있고 목차에 그 내용이 있으면 해당 페이지로 바로 이동하고
목차에 없으면 1페이지부터 끝까지 다 찾아야 하는 느낌이라고 생각하면 된다 ~~
이해되셨죠?

'Project > mo:rack (익명 커뮤니티)' 카테고리의 다른 글
실시간 채팅 구현 전 개념 정리 (2) | 2025.06.30 |
---|---|
로그인 사용자가 어떤 댓글에 좋아요를 눌렀는가에서 발생한 N+1 문제 해결 (0) | 2025.06.27 |
게시글 조건 조회 - QueryDSL (동적 쿼리) feat. 페이징 (2) | 2025.06.15 |
게시글의 댓글을 어떻게 불러와야 할까? - N+1 문제의 늪(1) (0) | 2025.06.12 |
로그아웃 기능 (AccessToken 블랙리스트 + RefreshToken 삭제) (0) | 2025.06.06 |