RETROSPECTIVE · 회고
새로고침해도 끊기지 않는 WebRTC 통화: Redis 통화 세션으로 재연결하기
Network
음성채팅 서비스를 만들면서 가장 오래 붙잡은 문제는 "통화 중 새로고침"이었다. 새로고침하면 RTCPeerConnection, 마이크 스트림, 소켓 연결이 전부 사라지기 때문에 통화가 그대로 끊긴다.
무엇을 서버에 남겨야 하나
연결 자체는 되살릴 수 없으니, 다시 연결하는 데 필요한 정보만 서버에 둔다. Redis 해시에 통화방 단위로 저장했다.
HSET call_room:{roomId}
caller a@example.com
callee b@example.com
startedAt 1718000000000 // 통화 시간 표시 유지용
callerEnded false
calleeEnded false
재연결 흐름
- 새로고침한 쪽이 소켓에 다시 접속하면
call:reconn을 보낸다. - 서버는 Redis에서 세션을 찾아 누가 caller인지, 언제 시작했는지를 양쪽에 알려준다.
- 양쪽은 새
RTCPeerConnection을 만들고 offer/answer/ICE를 다시 교환한다. - 화면의 통화 시간은
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에 잠시 보관하고, 컴포넌트가 준비되면 꺼내서 처리하는 방식으로 해결했다.
남은 개선점
- 통화방 조회에
KEYS call_room:*를 쓰고 있어 운영 환경에서는 사용자별 인덱스 키나SCAN으로 바꿔야 한다. - 네트워크만 바뀌는 경우에는 연결을 새로 만들 필요 없이
restartIce()로 ICE만 재시작하는 편이 가볍다.