DEVELOPMENT NOTE / Modu Square

Kafka 기반 묶음 알림 구현

알림 기능 구현

  • 댓글·좋아요 이벤트를 받는 Notification Service를 추가하고 게시글 작성자에게 알림 제공
  • 작성자 본인의 반응은 알림에서 제외하며 게시글 정보가 없을 때만 Article Service에서 보완 조회
  • 웹 상단 알림 버튼에서 최근 알림을 조회하고 클릭하면 해당 게시글로 이동

초기에는 이벤트 한 건을 알림 한 건으로 저장.

댓글·좋아요 이벤트 1건 → 알림 저장 1건 → 사용자 알림 목록 1행

문제 발생

인기 게시글 한 건에 5분 동안 좋아요 100개와 댓글 20개가 들어오는 상황을 재현.

  • 이벤트 120건이 알림 120건으로 노출돼 같은 게시글이 알림 목록을 차지
  • 저장 건수와 목록 조회 비용도 이벤트 수에 비례해 증가
  • Kafka에서 같은 이벤트가 재전달되면 알림 수치가 중복될 가능성

조회수는 발생량이 많고 알림 가치가 낮아 대상에서 제외.

같은 인기 게시글의 댓글과 좋아요가 개별 알림으로 반복되는 화면

개선 전 — 댓글·좋아요 이벤트마다 같은 게시글 알림이 한 행씩 추가

해결

사용자·게시글·5분 구간을 하나의 키로 묶고 같은 구간의 댓글과 좋아요 수를 누적.

도메인 서비스댓글 · 좋아요반응 이벤트Kafkaat-least-once이벤트 수신Notification Service중복 이벤트 차단게시글 · 5분 단위 집계원자적 누적Redis 알림 묶음댓글 20개 · 좋아요 100개알림 한 건 조회알림 UI게시글별 반응 요약
댓글·좋아요 이벤트를 게시글과 5분 구간별로 묶고, 중복 이벤트는 집계 전에 차단
long windowStartMillis = occurredAtMillis / bundleWindowMillis * bundleWindowMillis;
String notificationId = articleId + ":" + windowStartMillis;

redisTemplate.execute(
        ADD_TO_BUNDLE,
        List.of(eventKey, bundleKey, userIndexKey),
        retentionSeconds,
        articleId,
        title,
        occurredAtMillis,
        countField,
        notificationId
);
  • Redis Hash에 게시글 제목과 댓글·좋아요 수 저장
  • HINCRBY로 반응 수를 누적하고 사용자별 Sorted Set에는 최근 묶음 ID만 저장
  • 이벤트 120건을 댓글 20개 · 좋아요 100개 알림 한 건으로 표시

같은 게시글의 댓글과 좋아요를 한 건으로 묶어 표시하는 화면

개선 후 — 5분 동안 발생한 댓글과 좋아요를 게시글별 알림 한 건으로 집계

중복 확인과 수치 증가를 Lua Script 한 번으로 처리.

if redis.call('SET', KEYS[1], '1', 'NX', 'EX', ARGV[1]) == false then
    return 0
end
redis.call('HINCRBY', KEYS[2], ARGV[5], 1)
redis.call('ZADD', KEYS[3], ARGV[4], ARGV[6])
return 1
  • 처음 들어온 이벤트 ID만 묶음 수치에 반영
  • 중복 확인·Hash 증가·Sorted Set 갱신을 Redis에서 원자적으로 실행

테스트

검증 항목결과
5분 내 고유 이벤트좋아요 100건 · 댓글 20건
동일 이벤트 재전달수치 증가 없음
생성된 알림1건
5분 구간 경계 통과새 알림 1건 생성
  • Article·Like·Comment API → Outbox → Kafka → Notification Service → Redis 전체 흐름에서도 게시글별 알림 한 건 확인

판단과 한계

  • 고정 구간 경계 직전과 직후의 반응은 실제 간격이 짧아도 두 알림으로 분리
  • 현재 UI는 30초 polling 방식이며 실시간 push와 읽음 상태 관리는 후속 과제