← 프로젝트 회고 · Network
RETROSPECTIVE · 회고

새로고침해도 끊기지 않는 WebRTC 통화: Redis 통화 세션으로 재연결하기

Network

음성채팅 서비스를 만들면서 가장 오래 붙잡은 문제는 "통화 중 새로고침"이었다. 새로고침하면 RTCPeerConnection, 마이크 스트림, 소켓 연결이 전부 사라지기 때문에 통화가 그대로 끊긴다.

무엇을 서버에 남겨야 하나

연결 자체는 되살릴 수 없으니, 다시 연결하는 데 필요한 정보만 서버에 둔다. Redis 해시에 통화방 단위로 저장했다.

HSET call_room:{roomId}
caller a@example.com
callee b@example.com
startedAt 1718000000000 // 통화 시간 표시 유지용
callerEnded false
calleeEnded false

재연결 흐름

  1. 새로고침한 쪽이 소켓에 다시 접속하면 call:reconn을 보낸다.
  2. 서버는 Redis에서 세션을 찾아 누가 caller인지, 언제 시작했는지를 양쪽에 알려준다.
  3. 양쪽은 새 RTCPeerConnection을 만들고 offer/answer/ICE를 다시 교환한다.
  4. 화면의 통화 시간은 startedAt 기준으로 이어서 표시한다.

영원히 남는 세션 막기

한쪽만 종료했거나 새로고침 후 돌아오지 않으면 세션이 계속 남는다. 한쪽 종료 시점부터 3분 유예 타이머를 걸고, 그때까지 재접속이 없으면 세션을 지운다.

function start3MinTimeout(roomId) {
if (roomTimeouts.has(roomId)) return;
roomTimeouts.set(roomId, setTimeout(async () => {
const s = await redis.hgetall(`call_room:${roomId}`);
if (s.callerEnded === "true" && s.calleeEnded === "true") return;
await redis.del(`call_room:${roomId}`);
}, 3 * 60 * 1000));
}

같이 겪은 문제: offer가 너무 빨리 온다

수락 직후 통화 컴포넌트가 마운트되기 전에 offer와 ICE candidate가 먼저 도착해 유실됐다. 받은 offer를 sessionStorage에 잠시 보관하고, 컴포넌트가 준비되면 꺼내서 처리하는 방식으로 해결했다.

남은 개선점

  1. 통화방 조회에 KEYS call_room:*를 쓰고 있어 운영 환경에서는 사용자별 인덱스 키나 SCAN으로 바꿔야 한다.
  2. 네트워크만 바뀌는 경우에는 연결을 새로 만들 필요 없이 restartIce()로 ICE만 재시작하는 편이 가볍다.
Gunmo Lee
새로고침해도 끊기지 않는 WebRTC 통화: Redis 통화 세션으로 재연결하기 | Gunmo's Dev Life