media에서는 downstream WebRTC 연결을 종료하고, 추론과 오케스트레이션을 위한 backend 서비스와 upstream 연결을 유지함
OpenAI는 이 서비스를 수요 변화에 따라 확장·축소되고 호스트 사이를 이동할 수 있는 Kubernetes 위에서 운영하려 했음
기존의 세션당 포트 1개 WebRTC 모델은 Kubernetes 환경과 맞지 않았으며, 큰 공개 UDP 포트 범위를 노출·보안·유지해야 했음
높은 동시성에서 세션당 포트 1개는 매우 큰 UDP 포트 범위를 노출하고 관리해야 함
클라우드 로드밸런서와 Kubernetes 서비스는 서비스당 수만 개의 공개 UDP 포트를 전제로 설계되지 않았고, 포트 범위가 늘어날수록 로드밸런서 설정, 헬스체크, 방화벽 정책, 롤아웃 안전성이 복잡해짐
큰 UDP 포트 범위는 외부에서 도달 가능한 표면을 넓히고 네트워크 정책 감사를 어렵게 만들어 보안에도 불리함
Kubernetes에서는 pod가 계속 추가, 제거, 재스케줄링되므로 각 pod가 크고 안정적인 포트 범위를 예약·광고해야 하면 탄력성이 취약해짐
단일 포트와 세션 소유권 문제
많은 WebRTC 시스템은 포트 수 문제를 줄이기 위해 서버당 단일 UDP 포트와 애플리케이션 수준 역다중화를 사용함
서버당 단일 포트 설계는 포트 수를 줄이지만, fleet 전체에서 각 세션의 소유권을 보존해야 하는 두 번째 문제를 만듦
ICE와 DTLS는 상태가 있는 프로토콜이므로 세션을 만든 프로세스가 해당 세션의 패킷을 계속 받아야 연결 검사를 검증하고, DTLS 핸드셰이크를 완료하고, SRTP를 복호화하고, ICE restart 같은 이후 세션 변경을 처리할 수 있음
같은 세션의 패킷이 다른 프로세스에 도착하면 설정이 실패하거나 미디어가 깨질 수 있음
OpenAI의 목표는 공개 인터넷에 작고 고정된 UDP 표면만 노출하면서도 모든 패킷을 해당 WebRTC 세션을 소유한 transceiver로 라우팅하는 것이었음
검토한 접근
세션별 고유 IP:port는 직접 클라이언트-서버 미디어 경로를 제공하고 데이터 경로에 포워딩 계층이 없지만, 세션당 공개 UDP 포트 1개가 필요하고 큰 포트 범위가 Kubernetes·클라우드 로드밸런서·보안에 맞지 않음
서버별 고유 IP:port는 세션별 노출보다 공개 UDP footprint가 훨씬 작고 한 공유 소켓이 많은 세션을 역다중화할 수 있지만, 공유 로드밸런싱 fleet 전체에서는 첫 패킷이 잘못된 인스턴스에 도착할 수 있어 세션을 소유한 프로세스로 결정적으로 보내는 방법이 필요함
TURN relay는 클라이언트가 TURN relay 주소와 포트에만 도달하면 되고 edge에서 정책을 중앙화할 수 있지만, TURN allocation이 설정 왕복을 추가하고 TURN 서버 사이에서 allocation 이동이나 복구가 여전히 어려움
stateless forwarder + stateful terminator인 OpenAI의 relay + transceiver 구조는 작은 공개 UDP footprint를 유지하고 transceiver가 전체 WebRTC 세션을 소유하지만, 미디어가 소유 transceiver에 도달하기 전 forwarding hop이 하나 추가되고 relay와 transceiver 사이의 맞춤 조정이 필요함
relay + transceiver 아키텍처
OpenAI가 배포한 구조는 패킷 라우팅과 프로토콜 종료를 분리함
신호 처리는 세션 설정을 위해 transceiver에 도달하고, 미디어는 먼저 relay로 들어감
relay는 작은 공개 footprint를 가진 경량 UDP forwarding 계층이며, transceiver는 그 뒤에 있는 상태 있는 WebRTC endpoint임
relay는 미디어를 복호화하지 않고, ICE 상태 머신을 실행하지 않으며, 코덱 협상에도 참여하지 않음
relay는 목적지를 고르기에 충분한 패킷 metadata만 읽고, 세션을 소유한 transceiver로 패킷을 전달함
transceiver는 여전히 정상적인 WebRTC 흐름을 보고 모든 프로토콜 상태를 소유함
클라이언트 관점에서는 WebRTC 세션이 바뀌지 않음
첫 패킷 라우팅과 ICE ufrag 활용
이 구조의 핵심은 relay가 패킷 경로 자체에서 첫 클라이언트 패킷을 라우팅하는 것임
WebRTC 세션에는 이미 프로토콜 네이티브 라우팅 훅이 있으며, 이것이 ICE username fragment, 즉 ufrag임
ufrag는 세션 설정 중 교환되고 STUN 연결 검사에 다시 실리는 짧은 식별자임
OpenAI는 server-side ufrag에 relay가 목적지 cluster와 소유 transceiver를 추론할 수 있을 만큼의 라우팅 metadata를 담도록 생성함
signaling 중 transceiver는 세션 상태를 할당하고 SDP answer에 공유 relay VIP와 UDP port를 반환함
VIP는 relay fleet 앞단의 가상 IP 주소이며, port와 결합해 여러 relay 인스턴스 뒤에서도 클라이언트에 203.0.113.10:3478 같은 단일 안정 목적지를 제공함
클라이언트의 첫 media-path 패킷은 보통 STUN binding request이며, ICE는 이를 사용해 광고된 주소에 패킷이 도달할 수 있는지 검증함
relay는 첫 STUN 패킷에서 server ufrag를 읽고, 라우팅 힌트를 해독하고, 세션을 소유한 transceiver로 패킷을 전달할 만큼만 파싱함
각 transceiver는 세션당 소켓 하나가 아니라 내부 IP:port에 바인딩된 운영체제 endpoint인 공유 UDP 소켓에서 수신함
relay가 클라이언트 source IP:port에서 transceiver 목적지까지의 세션을 만들면, 이후 DTLS, RTP, RTCP 패킷은 ufrag를 다시 해독하지 않고 그 세션 안에서 흐름
relay의 세션은 packet forwarding을 위한 in-memory session, monitoring counter, session 만료와 정리를 위한 timer만 가진 최소 상태로 유지됨
relay가 재시작되어 세션을 잃어도 다음 STUN 패킷이 ufrag 라우팅 힌트로 세션을 다시 만듦
경로가 설정되면 Redis cache가 <client IP + Port, transceiver IP + Port> mapping을 보관해 다음 STUN 패킷이 오기 전에도 더 이른 복구가 가능함
Global Relay와 가까운 진입 경로
공개 UDP 표면을 작고 안정적인 주소·포트 수로 줄인 뒤, OpenAI는 같은 relay 패턴을 전 세계에 배포할 수 있었음
Global Relay는 같은 packet-forwarding 동작을 구현하는 지리적으로 분산된 relay ingress point fleet임
넓은 지리적 ingress는 사용자의 패킷이 먼 region까지 공용 인터넷을 먼저 가로지르지 않고, 지리와 네트워크 topology상 가까운 relay에서 OpenAI 네트워크로 들어오게 해 첫 client-to-OpenAI hop을 줄임
이 방식은 트래픽이 backbone에 도달하기 전의 지연, 지터, 피할 수 있는 loss burst를 낮춤
OpenAI는 signaling에 Cloudflare geo와 proximity steering을 사용해 초기 HTTP 또는 WebSocket 요청이 가까운 transceiver cluster에 도달하게 함
요청 context가 세션 위치와 클라이언트에 광고할 Global Relay ingress point를 결정함
SDP answer는 Global Relay 주소를 제공하고, ufrag는 Global Relay가 지정 cluster로 media를 라우팅하고 relay가 목적지 transceiver로 라우팅할 수 있는 충분한 정보를 담음
geo-steered signaling과 Global Relay를 함께 사용하면 setup과 media 모두 가까운 entry path에 놓이면서도 세션은 하나의 transceiver에 고정됨
이 구조는 signaling과 첫 ICE 연결 검사의 왕복 시간을 줄여 사용자가 말하기 시작하기까지 기다리는 시간을 직접 줄임
relay 구현 방식
relay 서비스는 Go로 작성됐고, 의도적으로 구현 범위를 좁게 유지함
Linux에서는 kernel networking stack이 네트워크 인터페이스에서 UDP packet을 받아 socket으로 전달하고, relay는 userspace의 일반 Go process로 socket에서 packet header를 읽음
relay는 소량의 flow state를 업데이트하고 WebRTC를 종료하지 않은 채 패킷을 전달함
OpenAI는 userspace process가 더 높은 packet rate를 위해 network queue를 직접 polling하는 kernel-bypass framework를 사용하지 않았으며, 그 방식은 운영 복잡성을 추가할 수 있다고 판단함
주요 설계 선택
프로토콜 종료 없음: relay는 STUN header와 ufrag만 파싱하고, 이후 DTLS, RTP, RTCP에는 cached state를 사용해 packet을 opaque하게 유지함
일시적 상태: flow state와 관측성을 위해 client address에서 transceiver destination으로 가는 작고 짧은 timeout의 in-memory map을 유지함
수평 확장성: 여러 relay instance가 load balancer 뒤에서 병렬로 실행되며, relay state가 hard WebRTC state가 아니므로 재시작 시 traffic drop이 작고 flow recovery가 빠름
효율화 조치
SO_REUSEPORT는 여러 relay worker가 같은 machine에서 같은 UDP port에 bind할 수 있게 하는 Linux socket option이며, kernel이 들어오는 packet을 worker들에 분배해 단일 read loop 병목을 피함
runtime.LockOSThread는 각 UDP-reading goroutine을 특정 OS thread에 고정함
SO_REUSEPORT와 thread pinning을 함께 쓰면 같은 flow의 packet이 같은 CPU core에 머무는 경향이 있어 cache locality가 좋아지고 context switching이 줄어듦
pre-allocated buffer와 최소 복사는 parsing과 allocation overhead를 낮춰 Go의 garbage collection을 피하는 데 도움을 줌
이 구현은 비교적 작은 relay footprint로 OpenAI의 global real-time media traffic을 처리했기 때문에, OpenAI는 kernel bypass 경로를 택하지 않고 더 단순한 설계를 유지함
결과와 교훈
이 아키텍처는 수천 개 UDP 포트를 노출하지 않고 Kubernetes에서 WebRTC media를 실행할 수 있게 함
작고 고정된 UDP 표면은 보안과 load balancing을 더 쉽게 만들고, 큰 공개 포트 범위를 예약하지 않고도 infrastructure가 확장될 수 있게 함
이 설계는 client의 표준 WebRTC 동작을 보존하고, OpenAI 워크로드에서 SFU 없는 설계가 기본값으로 적절했음을 확인함
대부분 세션은 point-to-point이고 지연에 민감하며, inference service가 WebRTC peer처럼 동작하지 않을 때 더 쉽게 확장됨
복잡성은 모든 backend service나 custom client behavior가 아니라 얇은 routing layer에 두는 것이 더 적합했음
protocol-native field에 routing metadata를 encoding하면서 결정적 first-packet routing, 작은 공개 UDP footprint, 전 세계 사용자 가까이에 ingress를 둘 수 있는 유연성을 얻음
특히 중요했던 선택
edge에서 protocol semantics 보존: client는 계속 표준 WebRTC를 사용하므로 browser와 mobile interoperability가 유지됨
어려운 session state를 한곳에 유지: transceiver가 ICE, DTLS, SRTP, session lifecycle을 소유하고 relay는 packet만 전달함
setup에 이미 있는 정보로 routing: ICE ufrag가 hot-path lookup dependency 없이 first-packet routing hook을 제공함
kernel bypass보다 common case 최적화 우선: SO_REUSEPORT, thread pinning, low-allocation parsing을 신중히 사용한 좁은 Go 구현만으로 OpenAI 워크로드에 충분했음
실시간 음성 AI는 infrastructure가 지연을 느껴지지 않게 만들 때 동작하며, OpenAI는 client가 WebRTC에 기대하는 동작을 바꾸지 않고 WebRTC 배포 형태를 바꾸는 방식을 택함