Voice-Link에서 WebRTC와 TURN, LiveKit, Spring Boot 연동이 중요한 이유: 통화 연결률과 품질을 실제 운영 관점에서 정리

1. 문제

Voice-Link 같은 실시간 음성 서비스의 핵심 문제는 “버튼은 눌렸는데 실제 음성이 안정적으로 오가지 않는 상황”을 얼마나 줄이느냐에 있습니다.
일반적인 웹 서비스는 요청과 응답이 잠깐 오가면 끝나지만, 음성 서비스는 수 분 동안 네트워크 상태가 계속 흔들리는 환경을 버텨야 합니다.
그래서 로그인 API가 정상이고 매칭이 성공했다고 해서, 실제 통화 경험까지 보장되지는 않습니다.

특히 WebRTC 기반 통화에서는 시그널링 연결과 미디어 연결이 다르게 실패할 수 있습니다.
앱 화면에서는 상대가 입장한 것처럼 보여도, 실제 음성 패킷은 NAT나 방화벽 문제 때문에 통과하지 못할 수 있습니다.
이 경우 사용자는 “연결된 것 같은데 안 들린다”는 가장 답답한 형태의 장애를 겪습니다.

Voice-Link 코드베이스에 있는 구조를 보면, 이 문제를 단순 프론트엔드 이슈로 다루지 않고 백엔드 운영 문제까지 확장해서 보고 있다는 점이 중요합니다.
application.yml에는 livekit.url, livekit.api-url, app.match.*, app.call-quality.*, spring.data.redis.* 같은 키가 분리돼 있습니다.
즉 통화 연결, 세션 수명주기, 품질 수집, 실시간 상태 전파를 각각 운영 가능한 단위로 나누려는 설계가 이미 들어가 있습니다.

  • 통화방에는 들어갔는데 음성이 안 오가는 문제
  • 네트워크가 잠깐 흔들릴 때 재접속과 종료를 어떻게 구분할지의 문제
  • 통화 종료 사실을 양쪽 클라이언트에 언제, 어떤 기준으로 전달할지의 문제
  • 나중에 “왜 끊겼는지”를 운영자가 추적할 수 있어야 하는 문제

결국 Voice-Link에서 필요한 기술은 “음성 통화 기능” 자체가 아니라, 음성 통화가 실패하는 현실을 견디는 기술입니다.
그래서 WebRTC, TURN, LiveKit, Redis, SSE, 품질 타임라인 저장은 각각 따로 있는 부가 요소가 아니라, 하나의 서비스 경험을 지키는 연결 고리로 봐야 합니다.

2. 왜 필요한가

Voice-Link에서 이 기술이 필요한 이유는 “통화 연결 성공”과 “통화 상태 일관성”을 동시에 잡아야 하기 때문입니다.
실시간 음성 서비스는 단순히 통화방을 생성하는 것만으로 끝나지 않습니다.
누가 들어왔는지, 누가 나갔는지, 실제로 세션을 종료해도 되는지, 네트워크 단절인지 사용자의 이탈인지까지 구분해야 서비스가 안정적으로 보입니다.

이 지점에서 WebRTC와 TURN/STUN/NAT traversal이 먼저 필요합니다.
docs/turn-stun-flow.md가 설명하듯, 클라이언트는 제한된 네트워크 환경에서 직접 미디어 경로를 확보하지 못할 수 있습니다.
이때 STUN은 외부에서 보이는 주소를 찾는 역할을 하고, TURN은 직접 연결이 실패했을 때 미디어를 대신 중계하는 생존 경로가 됩니다.

Voice-Link에서 이 기술을 어떻게 적용할 수 있는지도 코드 구조가 이미 보여줍니다.
백엔드는 LiveKit 연결 정보를 내려주고, 클라이언트는 그 경로를 기반으로 실시간 통화를 붙입니다.
그런데 기업망이나 공용망처럼 제약이 큰 환경에서는 TURN/TLS 같은 우회 경로가 없으면 “매칭은 성공했지만 통화는 실패”하는 케이스가 반복될 수 있습니다.

여기서 LiveKit 웹훅 처리가 중요해집니다.
LiveKitWebhookServiceroom_finished, participant_left, participant_disconnected, participant_joined 이벤트를 받아 세션 종료를 처리합니다.
특히 참가자 이탈 직후 바로 종료하지 않고 유예 시간을 둔 뒤 재입장을 확인하는 로직은, 일시적인 네트워크 흔들림을 “통화 종료”로 오판하지 않기 위한 운영 장치입니다.

또 하나 필요한 축은 실시간 상태 전달입니다.
CallEventStreamingService는 SSE emitter를 유지하고 주기적으로 heartbeat를 보내며, 통화 종료나 연장 이벤트를 사용자에게 밀어줍니다.
이 구조가 없으면 음성 세션은 끝났는데 앱 화면이 늦게 반응하거나, 한쪽만 종료 상태를 보는 식의 UX 불일치가 생깁니다.

즉 Voice-Link에서 WebRTC는 음성을 보내기 위한 기술이고, TURN은 연결 실패를 줄이기 위한 기술이며, LiveKit 웹훅과 Spring Boot는 그 음성 세션을 서비스 로직과 맞물리게 하는 기술입니다.
이 세 가지가 붙어야만 “통화가 된다”가 아니라 “통화가 왜 그렇게 됐는지 서비스가 설명 가능하다”는 수준으로 올라갑니다.

3. 기대효과

이 구조의 가장 큰 기대효과는 연결 실패를 줄이는 것보다도, 실패를 진단 가능한 상태로 바꾼다는 데 있습니다.
실시간 음성 서비스는 장애가 발생하지 않는 시스템이 아니라, 장애를 빨리 분류하고 복구해야 하는 시스템에 가깝습니다.
그래서 성공률 자체만큼 중요한 것이 원인 분해 능력입니다.

첫째, TURN을 제대로 운영하면 제한된 네트워크에서의 연결 생존율이 올라갑니다.
직접 연결이 어려운 사용자도 릴레이 경로를 통해 통화에 참여할 수 있으므로, 특정 통신사나 사내망에서만 반복되던 실패율을 줄일 수 있습니다.
실무적으로는 “재현이 어려운 통화 불가” 이슈를 줄이는 효과가 큽니다.

둘째, 통화 품질 타임라인이 생기면 추상적인 불만을 데이터로 바꿀 수 있습니다.
CallQualityService는 Redis 리스트에 세션별 snapshot을 저장하고, 최대 개수를 유지하면서 만료 시간을 설정합니다.
또한 재연결이 발생했거나 연결 상태가 끊김으로 바뀌면 진단 리포트를 만들 수 있게 설계돼 있습니다.

이 방식은 RTT, jitter, packet loss 같은 지표를 단순 로그가 아니라 “시간 흐름”으로 보게 해줍니다.
예를 들어 통화 시작 직후는 안정적이었는데 특정 시점부터 지터가 급증했는지, 재연결 직전에 패킷 손실이 누적됐는지를 세션 단위로 판단할 수 있습니다.
운영팀 입장에서는 사용자 체감과 네트워크 지표를 연결할 수 있다는 점이 큽니다.

셋째, 웹훅과 SSE가 붙으면 상태 불일치가 줄어듭니다.
미디어 서버에서 발생한 종료 이벤트를 백엔드가 받고, 이를 다시 사용자 화면으로 전파하면 통화 종료 기준이 단일화됩니다.
이렇게 해야 결제 시간, 연장 로직, 후기 작성, 다음 액션 유도 같은 후속 흐름도 어긋나지 않습니다.

  • 연결 실패 원인을 “매칭 문제”와 “미디어 경로 문제”로 분리 가능
  • 재접속과 완전 종료를 구분해 불필요한 세션 종료 감소
  • 품질 이슈를 사용자 불만이 아니라 타임라인 데이터로 확인 가능
  • 향후 녹취, 자막, 음성 이벤트 처리 기능을 붙일 때 세션 기준점 확보

특히 녹취, 자막, STT, VAD 같은 후속 기능도 이런 기반 위에서만 안정적으로 붙습니다.
음성 인식 자체보다 먼저 필요한 것은 “언제 통화가 시작됐고, 어느 시점에 품질이 흔들렸고, 언제 완전히 종료됐는지”를 서비스가 알고 있는 구조입니다.
그 기준 없이 STT만 붙이면 결과 품질보다 운영 혼선이 먼저 커집니다.

4. 구현 방향

구현 방향의 핵심은 WebRTC 미디어 경로, TURN 우회 경로, LiveKit 이벤트, Spring Boot 세션 로직, Redis 품질 저장을 하나의 운영 흐름으로 묶는 것입니다.
기술을 많이 쓰는 것이 중요한 것이 아니라, 각 기술이 언제 책임을 넘겨받는지 명확해야 합니다.
Voice-Link에서는 이 경계가 비교적 선명하게 보입니다.

첫 단계는 연결 경로를 안정화하는 것입니다.
클라이언트는 LiveKit을 통해 방에 참여하고, 네트워크 제약이 있을 때는 외부 TURN을 통해 릴레이 경로를 확보해야 합니다.
여기서 운영 포인트는 TURN이 “평소에는 조용하지만, 막히면 통화가 거의 안 되는” 인프라라는 점입니다.

둘째 단계는 세션 수명주기를 백엔드가 소유하는 것입니다.
LiveKitWebhookService처럼 참여자 입장과 이탈 이벤트를 받아 실제 통화 종료를 판정해야, 프론트엔드 단의 일시적인 끊김과 서버 기준의 종료를 분리할 수 있습니다.
특히 유예 시간 후 참가자 수를 다시 확인하는 방식은 모바일 네트워크 환경에서 매우 실용적입니다.

셋째 단계는 품질을 저장하고 해석하는 것입니다.
app.call-quality.* 설정처럼 보존 기간과 세션당 최대 snapshot 수를 제한해 Redis 메모리 사용량을 통제해야 합니다.
이 값이 없으면 품질 관측 기능은 금방 쌓이지만, 운영 환경에서는 가장 먼저 메모리 압박의 원인이 될 수 있습니다.

넷째 단계는 사용자 상태 동기화입니다.
CallEventStreamingService의 heartbeat는 단순 장식이 아니라, 죽은 SSE 연결을 빨리 걷어내고 실제 연결된 사용자에게만 이벤트를 보내기 위한 장치입니다.
이 부분이 약하면 통화 종료는 이미 처리됐는데 앱 화면은 그대로 남아 있는 어색한 상태가 생깁니다.

  • 관측 포인트: 통화 시작 성공률, 재연결 횟수, 끊김 직전 RTT/jitter/packet loss 변화
  • 장애 포인트: TURN 도달 불가, 웹훅 누락, SSE 연결 유실, Redis 적체
  • 개선 포인트: 재연결 임계값 조정, 세션 종료 유예 시간 튜닝, 품질 리포트 자동화
  • 확장 포인트: VAD 기반 구간 분리, 녹취 저장 트리거, STT 후 자막 생성 파이프라인

Voice-Link에서 이 기술을 어떻게 적용할 수 있는지 한 문장으로 정리하면 이렇습니다.
LiveKit이 실시간 미디어를 담당하고, TURN이 연결 실패를 막고, Spring Boot가 통화 세션의 진실 원본이 되며, Redis와 SSE가 품질 관측과 상태 전파를 맡는 구조로 정리하는 것입니다.
이렇게 해야 향후 STT나 음성 이벤트 처리도 통화 세션 경계에 맞춰 안전하게 붙일 수 있습니다.

String key = "call:quality:" + request.sessionId() + ":timeline";
redisTemplate.opsForList().rightPush(key, payload);
redisTemplate.opsForList().trim(key, -properties.getMaxSnapshotsPerSession(), -1);
redisTemplate.expire(key, Duration.ofSeconds(properties.getRetentionSeconds()));

if (shouldReportDiagnosis(request)) {
    var summary = CallQualitySessionSummaryResponse.from(
        request.sessionId(),
        readTimeline(request.sessionId())
    );
    diagnosisReporter.report(summary);
}

위 로직이 중요한 이유는 단순 저장이 아니라 “문제 발생 시점의 연속된 맥락”을 남기기 때문입니다.
음성 서비스는 한 번의 에러 로그보다, 끊기기 직전 수 초 동안 무슨 일이 있었는지가 더 중요합니다.
그래서 세션별 타임라인과 진단 보고는 운영 품질을 끌어올리는 핵심 장치가 됩니다.

5. 비즈니스 가치

이 구조의 비즈니스 가치는 “실시간 음성 기능 보유”가 아니라, 통화 성공률과 신뢰도를 서비스 자산으로 바꾼다는 데 있습니다.
사용자는 WebRTC나 TURN을 보지 않지만, 연결이 잘 되고 끊겨도 이유를 설명받을 수 있는 서비스에는 더 오래 남습니다.
특히 Voice-Link처럼 통화 경험 자체가 제품 핵심인 서비스에서는 기술 안정성이 곧 제품 신뢰도입니다.

연결 실패가 줄어들면 단순히 CS가 줄어드는 수준으로 끝나지 않습니다.
매칭 후 실제 대화로 이어지는 비율이 올라가고, 통화 중 이탈률이 줄고, 재시도 과정에서의 사용자 피로도도 낮아집니다.
즉 통화 인프라 안정화는 전환율, 체류 경험, 후기 품질에 모두 영향을 줍니다.

또한 품질 타임라인과 종료 이벤트 체계는 운영 조직에도 직접적인 가치를 줍니다.
“안 됐다”는 신고를 받을 때마다 감으로 대응하는 대신, 어느 구간에서 재연결이 있었고 종료 이벤트가 어떻게 처리됐는지를 근거로 볼 수 있습니다.
이 데이터는 장애 대응 속도를 높이고, 반복 장애를 구조적으로 줄이는 재료가 됩니다.

향후 녹취, 자막, 음성 이벤트 처리, STT 연동을 붙일 때도 이 기반은 그대로 재사용됩니다.
실시간 음성을 텍스트 자산으로 바꾸려면 먼저 세션 경계와 품질 상태가 안정적이어야 합니다.
즉 음성 AI 기능은 WebRTC 운영이 끝난 뒤의 다음 단계가 아니라, 지금의 통화 인프라 품질 위에서만 제대로 성립합니다.

요약하면 Voice-Link의 실시간 음성 품질은 WebRTC 단일 기술로 해결되지 않고, TURN 우회 경로, LiveKit 이벤트 처리, Redis 기반 품질 타임라인, SSE 상태 전파가 함께 맞물릴 때 비로소 안정화됩니다.
중요한 것은 통화 기능을 “붙였다”가 아니라, 연결 실패와 끊김을 운영 가능한 데이터로 바꾸는 구조를 갖추는 것입니다.
그 구조가 갖춰져야 이후의 STT, 자막, 녹취, 음성 이벤트 처리도 실제 서비스 가치로 이어집니다.

댓글 남기기