Skip to content

SSE 이벤트 통합 replay - Redis Stream 기반 Last-Event-ID 복구 #750

Description

@m-a-king

  • SSE 이벤트에 id/seq 가 전혀 없어(name + data 만) 클라이언트가 재연결 시 끊김 동안 발생한 이벤트를 스트림 차원에서 복구할 수단이 없다. 연결이 30분 타임아웃으로 주기적으로 강제 재연결되는 구조라 유실이 구조적으로 발생한다.
  • 이 작업의 실제 동인은 아이템 등록 화면의 silent-sync(TOURNAMENT_ITEM_PARSED) 유실이다. 참여자 화면의 PENDING 로딩 카드를 끝내는 신호가 SSE 단독 채널·비영속이라, 놓치면 카드가 낡은 채 남는다.
  • 1차 설계(PR SSE 재연결 시 놓친 알림 replay (Last-Event-ID 기반) #751, 닫음)는 id 를 알림 PK 에 앵커해 notification 만 replay 했다. silent-sync 가 복구 대상에서 빠져 목적이 닫히지 않았고, "스트림 절반만 신뢰 가능" 한 비대칭과 FE 재조회 계약 병행 부담이 남았다.

무엇을

  • 유저별 Redis Stream(sse:events:{userId})을 SSE 이벤트 로그로 두고, 모든 데이터 이벤트(notification·silent-sync)를 emit 시점에 적재한다 (XADD + MAXLEN trim + TTL).
  • SSE id 필드 = stream entry id (불투명 문자열). 재연결 시 Last-Event-ID 로 초과분을 적재된 원본 그대로(name·payload) 발생 순서대로 replay 한다.
  • 상한 초과는 replay 통째 생략 + 기존 목록/배지 재조회 fallback. MAXLEN 을 REPLAY_LIMIT 보다 크게 둬 trim 으로 생긴 중간 구멍을 replay 하는 경우를 배제한다 (trim 이 일어난 위험 케이스는 잔존 건수가 반드시 상한을 넘어 skip 으로 귀결).
  • live 전송과 replay 의 payload 를 같은 직렬화 결과(JSON 문자열) 로 통일하고, Redis 저장 직렬화 호환성 테스트를 함께 둔다 (blue-green 배포 중 구·신버전이 같은 스트림을 읽는 규약).
  • Redis 장애 시 degrade: 적재 실패한 이벤트는 id 없이 live 전송만 한다 (id 없는 이벤트는 클라 lastEventId 를 갱신하지 않아 복구 기준점을 오염시키지 않는다).
  • 중복 계약: register 후 replay 순서로 유실 대신 중복을 택하고, 클라이언트는 이벤트 id(stream id)로 dedup 한다.
  • notification-sse-spec.md·OpenAPI 문서 갱신 + 통합 테스트.

알려진 한계 (명시)

  • 연결이 살아 있는 동안의 서버측 드롭(executor 큐 포화 등)은 emit 경로에서 적재되므로 로그에도 안 남을 수 있다. 다음 재연결 replay 로도 복구되지 않는 그 틈은 기존 재조회 fallback 이 커버한다.
  • Redis 적재는 MySQL 트랜잭션과 원자적이지 않다 (DB 통합 로그 대비 트레이드오프로 인지하고 선택).

Metadata

Metadata

Assignees

Labels

feat외부 가시적 새 기능

Fields

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions