개요
실시간 채팅은 순서 보장, 다중 인스턴스 브로드캐스트, 장애 복구가 동시에 풀려야 하는 문제였습니다. WebSocket 메시지를 Kafka로 흘리고 roomId 파티션 키로 순서를 고정했으며, Redis Pub/Sub로 서버 간 브로드캐스트를 처리했습니다. REST API는 JPQL 프로젝션과 Cache Aside로 가볍게 만들고, WebSocket·Kafka 구간은 멱등성과 DLT까지 넣어 복구 가능성을 검증했습니다.
메시지 파이프라인
- ›WebSocket(STOMP) → Kafka produce (partition key = roomId로 같은 방 순서 보장)
- ›Consumer Group 1 (chat-persistence): DB 저장 + 멱등성 체크 (messageKey UUID + DB UK)
- ›Consumer Group 2 (chat-broadcast): Redis Pub/Sub → 모든 서버 인스턴스에 브로드캐스트
- ›Consumer Group 3 (chat-read-receipt): 읽음 처리 + unreadCount 갱신
- ›장애 복구: manual offset commit + 3회 재시도 → DLT(Dead Letter Topic) 격리
성능 최적화
- ›N+1 쿼리: 채팅방 목록 2N+1회 → JPQL Constructor Expression 1회 쿼리
- ›Redis Cache Aside: 채팅방 목록 캐싱 TTL 5분, 3가지 무효화 전략 (방 생성, 메시지 수신, 읽음 처리)
- ›DB 인덱스: EXPLAIN ANALYZE 기반 5개 인덱스 설계, 모든 쿼리 < 1.5ms
- ›k6 REST API: 200 VU, RPS 937 → 1,598 (+70%), p50 54ms → 16ms (-69%)
- ›k6 WebSocket: 100 VU, 579 동시세션 (2대 스케일아웃 시 1,158 세션)
프로덕션 품질
- ›Rate Limiting: STOMP ChannelInterceptor + 슬라이딩 윈도우 10msg/sec
- ›온라인/오프라인 상태: Redis presence TTL 60초 + WebSocket 이벤트 + Pub/Sub
- ›Graceful Shutdown + Health Check (Spring Actuator)
- ›Prometheus + Grafana 모니터링 (Micrometer 커스텀 메트릭 5개, 대시보드)
- ›Testcontainers 통합 테스트 20개 (PostgreSQL, Kafka, Redis 자동 구동)
아키텍처
전체 아키텍처▼
ERD
전체 ERD▼
시퀀스 다이어그램
메시지 전송 & 브로드캐스트▼
읽음 처리▼
Consumer 장애 복구▼
문제 원인
- 01여러 서버 인스턴스에서 동시에 메시지를 발행하면 채팅방 내 메시지 순서가 깨질 수 있었습니다.
- 02서버를 2대로 스케일아웃하면 WebSocket 세션이 공유되지 않아 다른 서버의 클라이언트에게 메시지를 전달할 수 없었습니다.
- 03Consumer가 메시지 처리 중 실패하면 메시지가 유실되거나 중복 저장될 수 있었습니다.
- 04채팅방 목록 API에서 N+1 쿼리가 발생해 방 10개 기준 21회 쿼리가 실행되어 응답이 느렸습니다.
해결 과정
- 01Kafka partition key = roomId로 같은 방의 메시지를 동일 파티션에 할당해 순서를 보장했습니다.
- 02Kafka Consumer Group 2가 Redis Pub/Sub로 메시지를 발행하고, 모든 서버가 구독해 크로스 서버 브로드캐스트를 구현했습니다.
- 03manual offset commit + 멱등성(messageKey UUID + DB UK)으로 중복 저장을 방지하고, 3회 재시도 실패 시 DLT로 격리했습니다.
- 04JPQL Constructor Expression으로 DTO 프로젝션 단일 쿼리를 작성하고, Redis Cache Aside(TTL 5분)로 반복 조회 부하를 제거했습니다.
결과
- 01k6 REST API 부하테스트(200 VU): RPS 937 → 1,598 (+70.5%), p50 54ms → 16ms (-69.5%), 에러율 0%.
- 02k6 WebSocket 부하테스트(100 VU): 579 동시세션(단일), 1,158 동시세션(2대 스케일아웃), 연결 실패 0%.
- 03EXPLAIN ANALYZE: 모든 주요 쿼리 < 1.5ms, 5개 커스텀 인덱스로 Sequential Scan 제거.
- 04Consumer 장애 복구: 멱등성 + DLT 격리로 메시지 유실·중복 없이 처리. 통합 테스트 20개 전체 통과.