SSE 재연결 시 놓친 알림 replay (Last-Event-ID 기반) - #751
Closed
m-a-king wants to merge 1 commit into
Closed
Conversation
- notification 이벤트에만 SSE id(알림 PK)를 싣는다. 별도 카운터 대신 DB 영속 값을 써 재시작·blue-green 과 무관하게 단조 증가하고, payload 의 id 와 같은 값이라 클라 dedup 키를 겸한다 - id 없는 이벤트(connect·silent-sync·하트비트)는 SSE 프로토콜상 클라 lastEventId 를 갱신하지 않으므로, 비영속이라 replay 불가능한 이벤트가 복구 기준점에서 자연 제외된다 - subscribe 가 Last-Event-ID 헤더를 받아 초과분을 발생 순서(id asc)로 그 연결에만 replay 한다. register 후 replay 순서로 "유실 대신 중복"을 택했고, 클라가 id 로 dedup 하는 계약을 스펙에 명시 - 상한(100건) 초과 공백은 일부만 보내면 replay 구간 뒤에 조용한 구멍이 남으므로 통째로 생략하고 기존 목록/배지 재조회 계약에 맡긴다 - 숫자가 아닌 Last-Event-ID 는 400 으로 끊지 않고 첫 연결로 취급한다 — EventSource 자동 재연결 루프를 깨지 않기 위함 - replay 조회는 기존 idx_notifications_user_id_id (user_id, id) 인덱스가 그대로 받쳐 마이그레이션이 없다
|
Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다. |
Collaborator
Author
|
설계를 통합 이벤트 로그(Redis Stream) 방식으로 전환하며 새 PR 로 대체합니다. 이 PR 의 replay 는 id 를 알림 PK 에 앵커해 notification 이벤트만 복구합니다. 그런데 이 작업의 실제 동인은 아이템 등록 화면의 silent-sync(TOURNAMENT_ITEM_PARSED) 유실이라, 비영속 이벤트가 복구 대상에서 빠지는 이 설계로는 목적이 닫히지 않습니다. 스트림의 절반만 신뢰 가능한 비대칭과 FE 재조회 계약 병행 부담도 남습니다. 후속 PR 에서 모든 SSE 데이터 이벤트(notification·silent-sync)를 유저별 Redis Stream 에 적재하고 Last-Event-ID 를 stream entry id 로 통일해 균일하게 replay 합니다. 이슈 #750 본문을 새 설계로 갱신했습니다. |
m-a-king
added a commit
that referenced
this pull request
Jul 17, 2026
- 유저별 Redis Stream(sse:events:{userId})을 SSE 이벤트 로그로 두고 notification·silent-sync 를 emit 시점에 적재한다. stream entry id 를 SSE id 필드에 실어, 재연결 시 Last-Event-ID 초과분을 종류 구분 없이 원본 그대로 replay 한다
- 1차 설계(알림 PK 앵커, PR #751 닫음)는 비영속 silent-sync 가 복구 대상에서 빠져 이 작업의 동인(아이템 등록 화면 파싱 동기화 유실)을 못 닫았다. SSE 의 lastEventId 는 스트림당 스칼라 하나라 두 id 공간을 섞을 수 없어 통합 이벤트 로그로 전환
- 저장소를 인메모리가 아닌 Redis 에 둔 이유: blue-green 배포 전환이 곧 전 연결 동시 재연결 시점인데 인메모리 버퍼는 그 순간 비어 있고, 스케일아웃 시 유저별 단조 id 채번도 중앙 저장소가 필요하다
- payload 를 한 번 직렬화(JSON 문자열)해 live 전송·로그·replay 가 같은 바이트를 공유한다. Redis 객체 저장 규약대로 직렬화 호환성 테스트(payload 스냅샷 + 스트림 필드 키 고정)를 함께 둔다
- 상한 초과 공백은 replay 통째 생략(부분 replay 는 뒤에 조용한 구멍을 남긴다). MAX_LEN(200) > REPLAY_LIMIT(100) 관계로 trim 이 만든 구멍을 연속인 척 replay 하는 경우를 배제하고, 관계 자체를 테스트로 고정
- 적재 실패는 id 없이 live 전송만 하는 degrade — id 없는 이벤트는 클라 lastEventId 를 갱신하지 않아 복구 기준점을 오염시키지 않는다
- 스냅샷 실측 중 발견: createdAt 은 JacksonConfig 의 KST 변환(+09:00 오프셋)으로 나가는데 스펙 문서는 "오프셋 없음"으로 낡아 있었다. 실측대로 정정
- 파일별로 중복이던 recording emitter 4개를 support/RecordingSseEmitter 로 통합(#578 의 공유 test double 항목). 와이어 JSON 트리 단언으로 바뀌어 직렬화 회귀도 함께 잡는다
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Situation
name+data만) 클라이언트가 재연결할 때 끊김 동안 발생한 알림을 스트림 차원에서 복구할 수단이 없었다.Task
Action
설계 결정
id와 같은 값이라 클라 dedup 키를 겸한다notification이벤트만connect·silent-sync·하트비트가 복구 기준점을 오염시키지 않게 자연 제외된다구현
LocalSseDelivery가 notification 이벤트 빌드를 한 곳(notificationEvent)에 모아 SSEid필드에 알림 PK 를 싣고, 라이브(deliver)와 replay(replayTo)가 같은 셰입을 공유한다.SseReconnectReplayer(신규)가Last-Event-ID초과분을 발생 순서(id asc)로 조회해 재연결한 그 연결에만 흘려보낸다. 조회는 리포지토리의 짧은 트랜잭션으로 끝나고 emitter write 는 트랜잭션 밖이다.subscribe가Last-Event-ID헤더를 optional 로 받는다. 숫자가 아니거나 양수가 아니면 무시하고 첫 연결로 취급한다.idx_notifications_user_id_id (user_id, id)인덱스가 그대로 받친다. 마이그레이션 없음.계약 문서
notification-sse-spec.md: id 필드, Last-Event-ID replay, 상한, 중복 dedup, 희귀 race 한계(PK 발급 순서와 커밋 순서 어긋남)를 명문화. WEB 은 EventSource 가 자동으로 처리하고, APP 은 마지막 id 저장·전송 예시를 추가했다.NotificationSseApi: Last-Event-ID 헤더 파라미터와 replay 계약을 OpenAPI 설명에 반영했다.Result
연관 이슈