1. GET API 호출 (빠름)
→ 현재 시청자 목록 조회
→ UI에 표시
2. WebSocket 연결 시작 (느림)
→ TCP 핸드셰이크
→ STOMP 연결
3. WebSocket 연결 완료
→ SUBSCRIBE /sub/contents/{contentId}/watch
→ handleSessionSubscribe 호출
→ joinSession() → 세션 생성 (DB 저장)
→ Redis Pub/Sub으로 WatchingSessionChange 브로드캐스트
→ UI 업데이트 (내가 입장한 것 표시)
다른 콘텐츠 클릭 (WebSocket 이미 연결됨)
1. 기존 콘텐츠 UNSUBSCRIBE (빠름)
→ handleSessionUnSubscribe 호출
→ leaveSession() → 세션 삭제 (DB 삭제)
→ Redis Pub/Sub으로 WatchingSessionChange 브로드캐스트
→ UI 업데이트 (내가 퇴장한 것 표시)
2. 새 콘텐츠 SUBSCRIBE (빠름)
→ handleSessionSubscribe 호출
→ joinSession() → 세션 생성 (DB 저장)
→ Redis Pub/Sub으로 WatchingSessionChange 브로드캐스트
→ UI 업데이트 (내가 입장한 것 표시)
3. GET API 호출 (나중에)
→ 현재 시청자 목록 조회
→ UI에 전체 목록 표시 (초기 상태)
속도: Refresh Token 검증은 꽤 빈번하게 일어납니다. Disk 기반인 MySQL보다 In-memory(RAM) 기반인 Redis가 훨씬 빠릅니다.
TTL (자동 삭제): MySQL은 만료된 토큰을 지우려면 별도의 스케줄러(Batch)를 돌려야 하지만, Redis는 만료 시간이 지나면 알아서 데이터를 삭제해 주므로 관리가 효율적입니다.
외부 API (동기 vs 비동기)
WebClient
예전의 RestTemplate은 요청을 보내고 응답이 올 때까지 스레드가 멈춥니다(Blocking).
WebClient는 응답을 기다리는 동안 스레드가 다른 일을 할 수 있습니다(Non-blocking). 외부 API 응답이 늦어도 내 서버 전체가 느려지지 않게 하기 위함입니다.
Kafka
카프카는 어디서 사용하였나요?
Kafka는 비동기 이벤트 처리를 위해 사용했습니다.
제가 맡은 부분은 아니지만, Spring의 @TransactionalEventListener를 사용해 DB 트랜잭션이 커밋된 후에만 이벤트를 Kafka로 발행하도록 했습니다. 이렇게 하면 DB 저장이 확정된 후에만 이벤트가 발행되어 데이터 일관성을 보장할 수 있습니다.
발행되는 이벤트는 알림 생성, DM 생성, 플레이리스트 생성, 메일 발송 등이 있고, Consumer에서는 @KafkaListener로 구독해 처리합니다.
예를 들어, 사용자가 플레이리스트를 생성하면 DB에 저장된 후 Kafka로 이벤트가 발행되고, Consumer에서 이를 구독해 팔로워들에게 알림을 보내는 방식입니다. 이렇게 하면 플레이리스트 생성 API의 응답 시간에 영향을 주지 않으면서도 비동기로 알림을 처리할 수 있습니다.
카프카는 왜 사용하셨나요?
네, 카프카를 사용하지 않으면 동기로 처리해야해서 어플리케이션이 느려질 수 있습니다.
예를 들면 따로 분리하지 않으면, 회원가입을 할때 메일이 발송해야지만 회원가입이 완료가 되지만, 따로 분리를 하면 회원가입 후 바로 회원가입 완료를 띄우고 메일 발송은 따로 카프카가 처리해주면 됩니다.
또, 확장성을 고려했습니다.
다중 서버 환경에서 특정 유저가 어떤 서버에 연결되어 있는지 알 수 없기 때문에, 카프카를 메시지 브로커로 사용하여 이벤트를 전파하고, 각 서버의 Consumer가 이를 받아 실시간 알림을 푸시하도록 구현되어 있습니다.