[Server] 게시글 좋아요 수 조회 전략 : COUNT 쿼리 VS 반정규화

2026. 1. 1. 00:23·Server

시작하며

게시글 목록을 불러올 때 각 게시글의 좋아요 수를 함께 보여주는 기능을 구현했습니다.

좋아요 수를 보여줄 때 COUNT 쿼리를 통해 보여줄지, 테이블에 반정규화 컬럼을 집어넣어 보여줄지에 대한 저의 생각을 적은 글입니다.

Redis나 Kafka 등의 기술 고려는 우선 제외했습니다. 서버 비용을 최대한 아끼고자 했고, 개발자 2명이서 시작한 프로젝트라 관리 비용을 최대한 줄이기 위해서입니다. 대규모 트래픽 상황에서의 대응 전략은 따로 글을 작성할 생각입니다.

Post 도메인과 PostReaction 도메인

전략을 설명하기 전에 프로젝트 도메인에 대해 간단하게 설명드리겠습니다.

게시글(Post) 도메인과 게시글 리액션(PostReaction) 도메인이 존재합니다.

@Builder
public record Post(
        Long id,
        String title,
        String content,
        Long authorId,
        Long boardId,
        boolean isActive,
        UUID uuid,
) {
}
@Builder
public record PostReaction(
		Long id,
		Long postId,
		Long userId,
		ReactionType reactionType,
		UUID uuid
) {
}

Post 도메인은 게시글을 담당하는 도메인입니다. PostReaction 도메인은 사용자의 반응을 담당하는 도메인입니다. 현재는 좋아요, 싫어요 타입만 존재합니다.

like_count 컬럼 (반 정규화) 전략

반정규화 전략을 사용했을 때의 프로세스를 알아보겠습니다.

사용자가 좋아요를 누른다면

  1. PostReaction 테이블에 관련 정보 저장
  2. 해당 Post의 likeCount + 1
@Builder
public record Post(
        Long id,
        String title,
        String content,
        Long authorId,
        Long boardId,
        boolean isActive,
        UUID uuid,
        int likeCount // 추가된 반 정규화 컬럼
) {
}

반정규화 전략을 사용한다면 다음과 같은 장점이 존재합니다.

장점

1. 목록 조회의 단순함

게시글 목록에서 각 게시글의 좋아요 수를 보여줘야 한다고 가정하겠습니다. 쿼리 한줄로도 조회가 가능합니다.

SELECT id, title, like_count FROM posts WHERE board_id = ? LIMIT 20

2. 정렬과 필터링

"좋아요 많은 순"으로 게시글을 정렬하거나, "좋아요 100개 이상인 게시글"을 필터링할 때 큰 차이가 납니다.

SELECT * FROM posts ORDER BY like_count DESC LIMIT 20

SELECT * FROM posts WHERE like_count >= 100

단점

1. 도메인 오염

Post 도메인의 핵심 관심사가 무엇인지 생각해보겠습니다. 제목, 내용, 작성자, 게시판 정보입니다. 사용자가 작성한 "글" 그 자체가 Post의 본질입니다. like_count는 Post의 본질이 아닙니다. 다른 사용자들의 반응을 집계한 파생 데이터입니다. 이 파생 데이터가 Post 테이블에 들어가는 순간, 도메인 경계가 무너집니다.

 

실제로 기능이 확장되는 상황을 생각해보겠습니다. 서비스가 성장하면서 "싫어요", "유용해요", "웃겨요" 같은 반응 기능을 추가합니다.

@Builder
public record Post(
        Long id,
        String title,
        String content,
        Long authorId,
        Long boardId,
        boolean isActive,
        UUID uuid,
        int likeCount,
        int dislikeCount,
        int usefulCount,
        int funnyCount
) {
}

Post 도메인이 점점 비대해집니다. 게시글과 무관한 책임을 계속 떠안게 됩니다. Post 도메인이 "게시글"이 아니라 "게시글 + 모든 집계 데이터"가 되어버렸습니다. 단일 책임 원칙이 완전히 깨진 상태입니다.

 

2. 경합 지점 발생

like_count를 Post 테이블에 두면, 좋아요를 누를 때마다 Post row를 UPDATE 해야 합니다.

UPDATE posts SET like_count = like_count + 1 WHERE id = ?

단순해 보이지만, 이게 동시성 환경에서 어떤 일을 일으키는지 생각해보겠습니다.

 

시나리오: 인기 게시글에 동시 좋아요

어떤 게시글이 저희 서비스에서 화제가 되었습니다. 1초에 100명이 좋아요를 누릅니다.

100개의 트랜잭션이 동시에 같은 row를 수정하려고 합니다. 데이터베이스는 이 요청들을 순차적으로 처리해야 합니다. row-level lock이 걸리고, 나머지 99개의 트랜잭션은 대기합니다.

 

문제는 Post 테이블은 좋아요만 처리하는 테이블이 아닙니다. 게시글 조회, 수정, 삭제도 모두 이 테이블에서 일어납니다.

좋아요 때문에 Post row에 락이 걸린 상황에서, 작성자가 게시글을 수정하려고 합니다.

UPDATE posts SET title = '수정된 제목', content = '수정된 내용' WHERE id = ?

이 쿼리는 좋아요 트랜잭션들이 끝날 때까지 대기해야 합니다. 사용자는 "저장 중..."을 한참 보게 됩니다.

좋아요라는 부가 기능이 게시글이라는 핵심 기능의 성능과 안정성을 흔드는 구조가 됩니다.

COUNT 쿼리

필요할 때 COUNT 쿼리로 계산하는 방식에 대해 알아 보겠습니다.

 

1. 목록 조회

먼저 페이징된 게시글 목록(예: 20개)을 조회합니다.

SELECT * FROM posts WHERE board_id = ? LIMIT 20

2. ID 추출

조회된 게시글들의 ID만 추출합니다.

List<Long> postIds = posts.stream()
    .map(Post::id)
    .toList();

3. 카운트 조회

PostReaction 테이블에서 추출된 ID들을 IN 절과 GROUP BY를 사용해 한 번의 쿼리로 좋아요 수를 가져옵니다.

SELECT post_id, COUNT(*) as like_count 
FROM post_reactions 
WHERE post_id IN (1, 2, 3, ..., 20) 
  AND reaction_type = 'LIKE'
GROUP BY post_id

장점

1. 도메인 오염 방지

Post 도메인은 게시글 본연의 책임만 가집니다. 반응 타입이 추가되더라도 Post 도메인을 수정할 필요가 없습니다.

 

2. 경합 지점 분리

좋아요를 누를 때 PostReaction 테이블에 INSERT만 발생합니다. Post 테이블에 UPDATE가 일어나지 않으므로, 인기 게시글에 좋아요가 몰려도 게시글 수정/삭제에 영향을 주지 않습니다.

 

3. 데이터 정합성 보장

원본 데이터(PostReaction)에서 직접 집계하므로 정합성이 자연스럽게 보장됩니다. 별도의 동기화 로직이나 보정 배치가 필요 없습니다.

 

하지만 반 정규화 전략과 다르게 좋아요 순으로 정렬 할때는 문제가 발생할 수 있습니다.

단점

1. 전체 데이터 스캔 (정렬이 필요한 기준)

인기 게시물 조회를 위해 모든 게시글의 좋아요 수를 계산해야 합니다. 데이터가 많다면 GROUP BY 이후 정렬하는 과정은 DB에 부하를 주며 실시간 응답이 불가능해집니다.

 

2. 인덱스 활용 불가

like_count는 실시간으로 계산되는 값이므로 인덱스를 태울 수 없습니다. 데이터가 늘어날수록 성능이 급격히 저하됩니다.

마치며

좋아요 기능을 구현할 때 Post 도메인에 like_count 컬럼을 직접 추가하는 방식은 추천드리지 않습니다.

 

좋아요 수 정렬이 필요하지 않고 단순히 게시글 목록 조회시 COUNT 쿼리 전략을 사용하는게 좋습니다. 도메인이 깔끔하게 유지되고, 정합성 관리 부담도 없습니다. IN 절과 GROUP BY를 활용하면 N+1 문제 없이 충분한 성능을 낼 수 있습니다.

좋아요 수 정렬이 필요하다면 Post 테이블에 컬럼을 추가하는 것이 아니라, 별도의 집계 테이블을 만들어 관리하는 방법이 있습니다.

 

저희 프로젝트에서는 COUNT 쿼리 전략을 통해 해당 기능을 구현했습니다.

'Server' 카테고리의 다른 글

[Server] 도메인 서비스(Domain Service)와 애플리케이션 서비스(Application Service)에 대한 고찰  (1) 2025.12.16
[Server] 신뢰성 확보 WITH Transactional OutBox Pattern  (0) 2025.03.21
[Server] 멱등성 보장  (0) 2025.03.21
[Server] 조회 쿼리 개선  (0) 2025.03.05
'Server' 카테고리의 다른 글
  • [Server] 도메인 서비스(Domain Service)와 애플리케이션 서비스(Application Service)에 대한 고찰
  • [Server] 신뢰성 확보 WITH Transactional OutBox Pattern
  • [Server] 멱등성 보장
  • [Server] 조회 쿼리 개선
절박한개발자
절박한개발자
깃허브 주소 : https://github.com/Kzerojun
  • 절박한개발자
    절박한개발
    절박한개발자
  • 전체
    오늘
    어제
    • 분류 전체보기 (99)
      • Server (5)
      • 프로젝트 (7)
      • Spring (7)
      • AI (1)
      • JPA (6)
      • JAVA (7)
      • Backend (3)
      • WEB (3)
      • 알고리즘-이론 (6)
      • 알고리즘-문제 (28)
      • CS (24)
        • 데이터베이스 (8)
        • Network (5)
        • OS (10)
        • LINUX (1)
      • 개발면접준비 (1)
      • 기타 (1)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    2
    CPU
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
절박한개발자
[Server] 게시글 좋아요 수 조회 전략 : COUNT 쿼리 VS 반정규화
상단으로

티스토리툴바