DEVELOPMENT NOTE / Modu Square

Kafka 기반 실시간 인기글

문제

매일 0시 스케줄러로 인기 점수를 계산하면 두 문제가 생김.

  • 게시글·댓글·좋아요·조회수를 모두 다시 읽어야 해 데이터가 늘수록 DB 조회량과 계산 시간 증가
  • 자정 이후의 반응은 다음 실행까지 반영되지 않아 최대 24시간 지연

기술 선택 이유

  • 도메인 서비스의 인기글 API 직접 호출 — 제외

    • 인기글 서비스의 장애나 지연이 게시글·댓글·좋아요·조회수의 쓰기 요청까지 전파
  • 스케줄러로 전체 데이터 재계산 — 제외

    • 전체 데이터를 반복 조회해야 하고 집계 주기 사이의 변화를 바로 반영하기 어려움
  • Kafka로 변경 사항 전달 — 채택

    • 각 서비스는 변경 이벤트만 발행. 인기글 서비스가 독립적으로 집계
    • 장애 중 쌓인 이벤트를 복구 뒤 다시 처리 가능
도메인 서비스변경 이벤트Kafka토픽별 이벤트Consumer Group인기글 집계 서비스게시글별 최신 수치댓글 · 좋아요 · 조회수인기글 Top 10Sorted Set최신값 저장점수 반영
도메인 이벤트를 Kafka로 모아 Redis 집계 상태와 당일 Top 10을 갱신

Redis 기반 실시간 집계

인기 점수 계산

private static final long LIKE_WEIGHT = 3;
private static final long COMMENT_WEIGHT = 2;
private static final long VIEW_WEIGHT = 1;

public long calculate(Long articleId) {
    return likeCountRepository.read(articleId) * LIKE_WEIGHT
            + commentCountRepository.read(articleId) * COMMENT_WEIGHT
            + viewCountRepository.read(articleId) * VIEW_WEIGHT;
}
  • 서비스 API를 다시 호출하지 않도록 Redis에 게시글별 최신 댓글·좋아요·조회수 저장
  • 좋아요 3점, 댓글 2점, 조회수 1점으로 인기 점수 계산

인기글 Top 10 저장

public void add(Long articleId, LocalDateTime time, Long score, Duration ttl) {
    redisTemplate.executePipelined((RedisCallback<?>) action -> {
        StringRedisConnection conn = (StringRedisConnection) action;
        String key = generateKey(time);
        conn.zAdd(key, score, String.valueOf(articleId));
        conn.zRemRange(key, 0, -11);
        conn.expire(key, ttl.toSeconds());
        return null;
    });
}
  • 게시글 생성일별 Sorted Set에서 점수순으로 상위 10건 유지
  • 점수 저장·하위 순위 제거·TTL 설정을 Redis Pipeline 한 번으로 처리