마지막 업데이트 2026-07-22
다중 인스턴스 SFU에서 인스턴스의 생존(heartbeat)을 추적하고, 죽은 인스턴스가 Redis에 남긴 room/peer 메타를 한 명의 leader가 안전하게 정리(reaper)하는 인프라. room 소유권·setter 모델은 #5 Router 매니저, 본 문서는 그 짝인 생존/정리 계층.
@handover-redis): 설계 의도 원본은 docs/handover/jacob/redis-infra.md(PR #634). 의도는 코드·커밋이 진실(추측 금지).
각 인스턴스가 heartbeatIntervalMs마다 자기 생존을 Redis에 기록한다.
tick():
multi()
.set(instanceHeartbeat:{id}, now, EX ttl) // 개별 키(TTL)
.zadd(heartbeatZset, now, id) // ZSET score=마지막 갱신 시각
+ room pin TTL refresh (자기 소유 room, ttl×3) // eval은 multi 밖
heartbeatZset의 score가 각 인스턴스의 마지막 heartbeat 시각. reaper가 이걸로 stale을 판정.cutoff = now - staleThresholdMs() // staleThreshold = heartbeatTtl × 1.5
zrangebyscore(heartbeatZset, -inf, cutoff) → stale 후보 인스턴스 ID
score(마지막 heartbeat)가 cutoff보다 오래된 인스턴스가 후보. 단 후보 ≠ 즉시 정리 — two-phase 재확인을 거친다(아래).
모든 인스턴스가 reaper를 돌리면 충돌·중복 정리가 생긴다. 그래서 leader lease를 잡은 인스턴스만 reaper를 실행한다.
| 동작 | 구현 |
|---|---|
획득(tryAcquireLeader) | reaper:leader 키를 SET NX(lease 90s TTL) |
갱신(refreshLease, 30s) | CAS: 자기 instanceId가 적힌 lease만 EXPIRE 연장(남의 lease 실수 연장 방지) |
해제(releaseLease) | shutdown 시 자기 lease만 CAS DEL → 다른 인스턴스가 빠르게 인계 |
인스턴스가 죽으면 lease(90s TTL)가 만료되어 60초+α 내에 다른 인스턴스가 leader 인계. refreshLease 실패 시 leaderHeld=false로 자진 강등.
findStaleInstances → 후보 없으면 return.isReallyDead로 heartbeat 재확인.reapInstance. (자기 자신은 skip)-- 각 room을 owner CAS로 원자 삭제 local owner = GET roomInstance:{roomId} if owner ~= deadInstanceId then return 0 end -- 이미 takeover됨 → skip DEL room, roomInstance, guestState, ... (7키) SREM instance:dead:rooms, roomId return 1
instance:{dead}:rooms / :peers SMEMBERS로 죽은 인스턴스 소유 목록 수집.게스트가 빠르게 재접속하면 새 upsertPeer가 roomGuest 포인터를 새 peerId로 갱신한다. 이때 변경된 인덱스 키마다 이전 값에서 명시적으로 SREM하지 않으면 이전 매핑이 영구 stale로 남는다(id-032). upsert는 "이전 인덱스 정리 후 새 매핑" 순서를 지킨다.
모든 Redis 키·pub/sub 채널은 STAGE/브랜치별 prefix로 격리된다(#640, id-009). 같은 Redis 인스턴스를 dev/prod 또는 dev 브랜치 간 공유해도 이벤트·키가 충돌하지 않는다. 새 키/채널 추가 시 반드시 key-prefix.ts 정책을 거칠 것.
owner==dead 검사를 빼면, 이미 다른 인스턴스가 takeover한 room을 지워버린다.id-032).REDIS_URL 비면 heartbeat/reaper 모두 no-op(단일 인스턴스 가정). 다중 인스턴스 운영엔 Redis 필수.| 파일/심볼 | 역할 |
|---|---|
| redis/client.ts (tick L160) | heartbeat 발행 + room pin TTL refresh |
| redis/reaper.ts (findStaleInstances L45, reapInstance L67, REAP_ROOM_LUA L28) | stale 판정·인스턴스 정리 |
| redis/reaper-scheduler.ts (runOnce L96) | leader election·two-phase 루프 |
| redis/peer-store.ts (upsertPeer L51) | peer 매핑·stale 정리 |
| redis/key-prefix.ts | STAGE/브랜치 prefix 정책 |
| docs/handover/jacob/redis-infra.md | 설계 의도 원본 |