OpenAI의 WebRTC 문제

3 hours ago 2
  • WebRTC는 회의 통화처럼 낮은 지연을 우선해 네트워크가 나쁠 때 오디오 패킷을 적극적으로 버리지만, Voice AI에서는 느린 응답보다 음성 프롬프트 손상이 응답 품질을 더 크게 해칠 수 있음
  • TTS는 실시간보다 빠르게 오디오를 만들 수 있어 클라이언트 버퍼링으로 짧은 네트워크 장애를 숨길 수 있지만, WebRTC는 도착 시간 기준 렌더링과 작은 지터 버퍼 때문에 패킷을 제때 보내도록 인위적으로 대기해야 함
  • WebRTC는 임시 포트, ICE, DTLS, SCTP 등으로 연결 설정과 운영이 복잡하며, 단일 포트 다중화에서는 STUN, SRTP/SRTCP, DTLS, TURN 패킷을 각 연결로 라우팅하기 어려움
  • OpenAI가 빠른 연결 설정을 요구해도 WebRTC는 시그널링과 미디어 서버 절차를 합쳐 최소 8 RTT가 들 수 있으며, P2P 지원 구조 때문에 서버가 고정 IP를 가져도 같은 절차를 거쳐야 함
  • 대안으로 WebSocketsQUIC/WebTransport가 제시되며, QUIC은 CONNECTION_ID, QUIC-LB, preferred_address를 통해 단일 포트, 주소 변경, 상태 없는 로드밸런싱, anycast와 unicast 조합을 더 단순하게 지원함

WebRTC가 Voice AI에 맞지 않는 이유

  • WebRTC는 회의 통화처럼 빠른 왕복 대화에 맞춰 설계되어, 네트워크 상태가 나쁠 때 지연을 낮게 유지하려고 오디오 패킷을 적극적으로 버림
  • Voice AI에서는 사용자가 느린 응답을 조금 더 기다리더라도 프롬프트가 정확히 전달되는 편이 더 중요함
    • 예를 들어 “세차장에 걸어갈지 운전해 갈지” 같은 음성 프롬프트가 손상되면, 이후 응답 품질도 나빠질 수 있음
  • 브라우저의 WebRTC 오디오 구현은 실시간 지연을 강하게 전제하며, Discord에서 시도했을 때 WebRTC 오디오 패킷 재전송은 불가능했다고 함
    • 업데이트로 일부 WebRTC 관계자들은 오디오 NACK 활성화가 가능할 수 있다고 봤지만, Discord에서는 올바른 SDP 조작 방법을 찾지 못했고 WebRTC 지터 버퍼가 매우 작다는 한계도 남음
  • Voice AI 에이전트가 언젠가 대화 수준의 지연 시간에 도달하더라도, 지연을 줄이는 데는 트레이드오프가 있으며 음성 프롬프트를 일부러 열화시키는 선택이 가치 있는지는 불확실함

TTS와 WebRTC의 버퍼링 문제

  • 텍스트 음성 변환(TTS)은 실시간보다 빠르게 오디오를 생성할 수 있음
    • 예를 들어 GPU가 2초 동안 8초 분량의 오디오를 생성한다면, 이상적으로는 생성 중인 2초 동안 오디오를 스트리밍하고 클라이언트가 8초 동안 재생하면서 로컬 버퍼를 확보할 수 있음
    • 이렇게 하면 짧은 네트워크 장애가 있어도 사용자가 알아차리지 못할 가능성이 있음
  • WebRTC는 이런 방식과 맞지 않음
    • WebRTC는 버퍼링이 없고 도착 시간 기준으로 렌더링하며, 타임스탬프는 강한 재생 기준이 되지 않는다고 봄
    • 비디오까지 포함되면 문제가 더 까다로워짐
  • OpenAI 같은 서비스는 패킷이 재생되어야 할 정확한 시점에 도착하도록, 각 오디오 패킷 전송 전에 인위적으로 대기해야 함
    • 네트워크 혼잡이 생기면 해당 오디오 패킷은 손실되고 재전송되지 않음
  • 결과적으로 인위적 지연을 넣은 뒤 “낮은 지연”을 위해 패킷을 적극적으로 버리는 구조가 되며, 이는 YouTube 영상을 버퍼링하지 않고 화면 공유로 보여주는 것과 비슷함
  • WebRTC는 오디오에 대해 20ms에서 200ms까지 동적으로 조정되는 지터 버퍼를 두며, 이는 네트워크 지터를 완화하기 위한 것이지만 실시간보다 빠르게 전송할 수 있다면 필요하지 않다고 봄

포트와 연결 식별의 한계

  • TCP 서버는 보통 443 같은 포트를 열고 연결을 받으며, 연결은 소스·목적지 IP와 포트 조합으로 식별됨
    • 예: 123.45.67.89:54321 -> 192.168.1.2:443
  • 휴대폰이 WiFi에서 셀룰러로 전환되거나 NAT가 소스 IP·포트를 바꾸면 TCP 연결은 끊기고 새 연결을 맺어야 함
    • TCP와 TLS 핸드셰이크에는 최소 2~3 RTT가 들며, 라이브 스트리밍에서는 사용자가 네트워크 끊김을 느낄 수 있음
  • WebRTC는 이 문제를 풀기 위해 각 연결마다 임시 목적지 포트를 할당하는 방식을 전제함
    • 세션을 목적지 IP·포트만으로 식별하면, 소스 IP·포트가 바뀌어도 같은 사용자로 인식할 수 있음
  • 하지만 OpenAI의 구조와 맞물리면 이 방식은 대규모 운영에서 문제가 됨
    • 서버가 사용할 수 있는 포트 수에는 한계가 있음
    • 방화벽은 임시 포트를 자주 차단함
    • Kubernetes 환경과도 잘 맞지 않음

WebRTC 서비스가 단일 포트 다중화로 가는 이유

  • 많은 서비스는 WebRTC 사양을 그대로 따르지 않고, 여러 연결을 단일 포트에 다중화
  • Twitch에서는 WebRTC 서버를 UDP:443에서 운영했음
    • 원래 443은 HTTPS/QUIC 포트지만, 이렇게 하면 더 많은 방화벽을 통과할 수 있었음
    • Amazon 사내 네트워크는 약 30개 포트만 허용했다고 함
  • Discord는 CPU 코어마다 하나씩 50000-50032 포트를 사용함
    • 이 방식은 더 많은 사내 네트워크에서 차단될 수 있음
  • 단일 포트 다중화의 큰 문제는 WebRTC가 여러 표준을 묶은 구조라는 데 있음
    • UDP 위로 직접 올라가는 프로토콜이 5개 있으며, 패킷이 어떤 프로토콜인지 구분하는 일 자체는 어렵지 않지만 각 패킷을 어떤 연결로 라우팅할지가 어려움
  • 프로토콜별 라우팅 난점

    • STUN
      • 고유한 ufrag를 선택해 이를 기준으로 라우팅할 수 있음
    • SRTP/SRTCP
      • 브라우저가 임의의 ssrc 값을 선택하며, 보통은 이를 바탕으로 라우팅할 수 있음
    • DTLS
      • RFC9146의 광범위한 지원을 기대해야 하는 상황임
    • TURN
      • 구현 경험이 없다고 밝힘
    • OpenAI는 STUN만 파싱하고 이후 DTLS, RTP, RTCP는 캐시된 상태로 불투명하게 처리한다고 밝힘
    • 이는 사용자 소스 IP·포트가 바뀌지 않기를 기대하는 구조로 해석됨
    • 브라우저가 같은 ssrc를 임의 생성할 수도 있음
    • 충돌이 생기고 소스 IP·포트 매핑이 없으면, Discord는 가능한 각 복호화 키로 패킷 복호화를 시도해 맞는 키를 찾는 방식으로 연결을 식별한다고 함

WebRTC 연결 설정의 왕복 지연

  • OpenAI는 “세션이 시작되자마자 사용자가 말할 수 있도록 빠른 연결 설정”을 요구사항 중 하나로 제시했지만, WebRTC 연결 설정에는 최소 8 RTT가 든다고 봄
  • 시그널링 서버 예시

    • WHIP 같은 시그널링 서버 기준으로 다음 왕복이 필요함
      • TCP에 1 RTT
      • TLS 1.3에 1 RTT
      • HTTP에 1 RTT
  • 미디어 서버

    • ICE에 1 RTT
    • DTLS 1.2에 2 RTT
    • SCTP에 2 RTT
    • 일부 프로토콜은 파이프라이닝으로 0.5 RTT를 피할 수 있어 정확한 계산은 복잡하지만, 전체적으로 많은 왕복이 필요함
    • 이 절차는 WebRTC가 P2P를 지원해야 하기 때문에 생기며, 서버가 고정 IP를 갖고 있어도 같은 과정을 거쳐야 함
    • 시그널링 서버와 미디어 서버가 같은 호스트나 프로세스에서 실행될 때는 중복되고 비싼 핸드셰이크가 두 번 일어남

WebRTC를 포크하게 되는 구조

  • WebRTC는 많은 한계 때문에 사실상 프로토콜 포크를 유도한다고 봄
  • WebRTC는 약 45개의 RFC와 TWCC, REMB 같은 사실상 표준 초안으로 구성되어 구현 부담이 큼
  • 브라우저 구현은 Google이 소유하고 Google Meet에 맞춰져 있어, 회의 앱에는 존재론적 위협이 된다고 봄
  • Google Meet을 제외한 회의 앱들이 네이티브 앱 설치를 유도하는 이유도 WebRTC 사용을 피하기 위해서라고 봄
  • Discord는 네이티브 클라이언트에서 WebRTC를 크게 포크해 SDP, ICE, STUN, TURN, DTLS, SCTP, SRTP 등의 대부분을 구현하지 않지만, 웹 클라이언트를 위해서는 여전히 전체를 구현해야 함
  • OpenAI도 자금이 충분하겠지만, WebRTC를 포크하기보다 브라우저 지원이 있는 다른 방식으로 대체하는 편이 낫다고 봄

대안: WebSockets와 QUIC

  • Voice AI에서 WebRTC 대신 시작할 대안으로 WebSockets가 제시됨
    • 기존 TCP/HTTP 인프라를 활용할 수 있음
    • 커스텀 WebRTC 로드밸런서를 만들 필요가 없음
    • Kubernetes와 잘 맞고 확장 가능하다고 봄
  • Head-of-line blocking은 이 맥락에서 단점이 아니라 바람직한 사용자 경험일 수 있다고 봄
    • 음성 프롬프트의 일부가 빠지는 것보다 순서대로 전달되는 편이 더 낫다는 전제임
  • 언젠가 일부 패킷을 드롭하거나 우선순위를 둬야 하는 시점이 오면, OpenAI는 MoQ처럼 WebTransport를 활용해야 한다고 봄
  • QUIC 연결 설정은 QUIC+TLS 1 RTT로 가능해 WebRTC의 다중 핸드셰이크보다 단순함

QUIC Connection ID의 장점

  • QUIC은 소스 IP·포트 기반 라우팅을 버리고, 모든 패킷에 CONNECTION_ID를 포함함
    • CONNECTION_ID는 0~20바이트 길이일 수 있음
    • 중요한 점은 수신자가 이 값을 선택한다는 점임
  • QUIC 서버는 각 연결마다 고유한 CONNECTION_ID를 생성할 수 있음
    • 단일 포트를 쓰면서도 소스 IP·포트가 바뀐 연결을 식별할 수 있음
    • 소스 주소가 바뀌면 TCP처럼 연결을 끊지 않고 QUIC이 자동으로 새 주소로 전환함
  • RFC9146의 아이디어는 QUIC에서 가져온 것이라고 봄

상태 없는 로드밸런싱

  • OpenAI의 로드밸런서는 많은 로드밸런서처럼 공유 상태에 의존함
    • 소스 IP·포트에서 백엔드 서버로 가는 매핑을 저장해야 함
    • 로드밸런서가 재시작하거나 크래시할 수 있으므로 이 매핑 저장소가 필요함
  • OpenAI는 Redis 인스턴스를 사용해 소스 IP·포트와 백엔드 서버 매핑을 저장함
    • 단순하고 쉬운 방식으로 평가함
  • QUIC-LB는 데이터베이스 없이 더 단순한 방식을 제공함
    • 클라이언트가 QUIC 연결을 시작하면 로드밸런서가 패킷을 정상 백엔드 서버로 전달함
    • 백엔드 서버는 핸드셰이크를 완료하면서 자신의 ID를 CONNECTION_ID에 인코딩함
    • 이후 모든 QUIC 패킷에는 백엔드 서버 ID가 들어감
  • 로드밸런서는 암호화 키나 라우팅 테이블 없이 처음 몇 바이트를 디코딩해 해당 서버로 전달하면 됨
    • 서버가 재부팅되어도 이 방식은 유지될 수 있음
  • 상태가 없다는 것은 전역 상태도 없다는 뜻임
    • 로드밸런서가 전역 anycast 주소에서 수신하고, 표시된 백엔드 서버로 전역 전달할 수 있음
    • Cloudflare는 이를 광범위하게 사용한다고 함
  • AWS NLB는 QUIC-LB를 사용하는 QUIC 로드밸런싱을 제공함

Anycast와 Unicast 조합

  • OpenAI 기준으로는 연결을 지역 로드밸런서에 할당하는 구조로 보이며, 기능적으로는 동작하지만 Anycast가 더 나은 접근으로 제시됨
  • QUIC의 preferred_address는 로드밸런싱에 중요한 기능으로 평가됨
  • 동작 방식

    • 전 세계의 여러 백엔드 서버가 같은 anycast 주소 1.2.3.4를 광고함
    • 클라이언트가 1.2.3.4로 연결을 시도하면 인터넷 라우터가 패킷을 서버 중 하나로 전달함
    • 각 QUIC 서버는 고유한 unicast 주소 5.6.7.8도 가질 수 있음
    • anycast는 핸드셰이크에 사용하고, 상태 있는 연결은 unicast로 유지함
  • 예시 흐름

    • 서버는 1.2.3.4와 5.6.7.8에서 QUIC 패킷을 수신함
    • 클라이언트는 1.2.3.4로 QUIC 핸드셰이크 패킷을 보냄
    • 서버는 QUIC 연결을 만들며 preferred_address=5.6.7.8을 알림
    • 클라이언트는 이후 패킷을 5.6.7.8로 보냄
    • 서버가 과부하되어 새 연결을 받고 싶지 않으면 1.2.3.4 광고를 중단하면 됨
    • 기존 연결은 unicast에 있으므로 끊기지 않음
    • anycast 주소가 사실상 헬스체크처럼 동작함
    • 이 구조에서는 별도 로드밸런서가 필요하지 않다고 봄

한계와 결론

  • OpenAI 엔지니어들은 매우 뛰어나며, 즉시 대규모 확장을 해야 하는 압박을 받고 있다고 인정함
  • 다만 Voice AI에서 WebRTC는 명백한 선택처럼 보여도 제품 적합성이 좋지 않고 확장도 어렵다고 봄
  • MoQ도 Voice AI에 완벽히 맞는 것은 아님
    • 1:1 오디오에서는 캐시와 팬아웃 의미론의 많은 부분이 쓸모없음
    • 그래도 QUIC은 사용해야 한다는 결론임
Read Entire Article