Pull Request · #312
실시간 알림 채널 통합
TL;DR
댓글 스레드 v1의 마지막 조각인 실시간 WebSocket 알림을 구현합니다. 서버 측 Redis Pub/Sub 브로커를 도입해 수평 확장을 보장하고, 클라이언트는 커넥션 단절 시 지수 백오프로 자동 재연결합니다. 기존 이메일 다이제스트 큐와 통합해 "조용한 시간대" 정책을 함께 지원합니다.
변경 전 / 후
이전
- 댓글 작성 후 페이지 새로고침 필요
- 알림: 이메일 배치(5분 주기) 단독
- 단일 인스턴스 인메모리 이벤트 버스
- 모바일 백그라운드 시 알림 유실
이후
- WebSocket으로 새 댓글 즉시 표시 (≤300 ms)
- 알림: WebSocket + 이메일 다이제스트 통합
- Redis Pub/Sub 기반 수평 확장 지원
- FCM 푸시 알림으로 모바일 백그라운드 커버
파일 변경 내역
▶ notification/WebSocketNotificationService.java NEW +312
WebSocket STOMP 메시지 브로드캐스트 서비스. Redis 채널 구독 후 연결된 모든 세션에 이벤트를 전파합니다.
+ @Service + public class WebSocketNotificationService implements ApplicationListener<CommentCreatedEvent> { + private final SimpMessagingTemplate messaging; + private final RedisPublisher publisher; + @Override + public void onApplicationEvent(CommentCreatedEvent event) { + publisher.publish("notification:" + event.getThreadId(), event); + } + }
▶ notification/config/WebSocketConfig.java NEW +68
STOMP over WebSocket 엔드포인트 설정. /ws 핸드셰이크 엔드포인트와 /topic 메시지 브로커를 등록합니다.
+ @Configuration + @EnableWebSocketMessageBroker + public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS(); + } + }
▶ frontend/hooks/useCommentNotification.ts NEW +147
React 훅 — STOMP 클라이언트 수명 주기 관리, 자동 재연결(지수 백오프), Zustand 스토어에 신규 댓글 merge.
+ export function useCommentNotification(threadId: string) { + const addComment = useCommentStore(s => s.addComment); + useEffect(() => { + const client = new Client({ brokerURL: WS_URL }); + client.onConnect = () => { + client.subscribe(`/topic/thread/${threadId}`, msg => { + addComment(JSON.parse(msg.body)); + }); + }; + client.activate(); + return () => client.deactivate(); + }, [threadId]); + }
▶ notification/EmailDigestService.java 수정 +28 −45
이메일 다이제스트와 WebSocket 알림 중복 발송 방지 로직 추가. 사용자 "조용한 시간대" 설정을 존중하도록 리팩터링.
- if (user.emailEnabled) { + if (user.emailEnabled && !user.isQuietHours()) { queue.enqueue(new EmailDigestJob(user, comment)); }
핵심 설계 결정
Redis Pub/Sub 기반 인스턴스 간 이벤트 전파
단순 인메모리 이벤트 버스로는 로드 밸런서 뒤에서 특정 인스턴스에 연결된 클라이언트에게만 이벤트가 전달됩니다. Redis 채널을 허브로 삼아 모든 인스턴스가 구독하도록 설계해 수평 확장 시에도 유실 없이 브로드캐스트됩니다.
지수 백오프 자동 재연결
모바일 네트워크 불안정 시 폴링 폭풍(polling storm)을 막기 위해 클라이언트 측에서
1s → 2s → 4s → 8s (최대 30s) 지수 백오프를 적용합니다. @stomp/stompjs의 reconnectDelay
콜백에서 구현, 탭이 비활성이면 재연결 시도를 중단합니다.
피처 플래그로 단계적 롤아웃
모든 사용자에게 즉시 노출하지 않고 Unleash 피처 플래그 ws_notification_v1로
10% → 50% → 100% 단계 배포합니다. 플래그가 꺼진 사용자는 기존 폴링(30초) 방식으로 폴백됩니다.
테스트 계획
롤아웃 단계
단계 1
10%
내부 사용자
단계 2
50%
진행 중 · 이상 없음
단계 3
100%
2026-05-16 예정
모니터링
D+7
Datadog 대시보드