WebRTC routing становится критичным для voice AI, где важна непрерывность аудиопотока и минимальная latency. Разбираем, как переработка маршрутизации меняет поведение системы под нагрузкой.
Проблема проявляется не сразу — до момента, когда система масштабируется до глобального real-time трафика. В классической модели WebRTC «один порт на сессию» возникает давление на инфраструктуру: требуется большой диапазон UDP-портов, который сложно экспонировать и защищать в Kubernetes. Одновременно ICE и DTLS остаются stateful, и каждая сессия требует стабильного владельца. На этом этапе добавляется третий фактор — глобальная маршрутизация. Первый hop начинает определять пользовательский опыт: лишние миллисекунды превращаются в паузы, перебивания и «рваный» диалог.
Выбор архитектуры упирается в форму нагрузки. SFU (selective forwarding unit) хорошо работает для multiparty сценариев, где нужно централизованно управлять потоками, кодеками и RTCP. Но при 1:1 взаимодействии, где каждый RTT влияет на ощущение «живого» разговора, SFU добавляет лишний слой. В этом контексте был выбран transceiver-подход: WebRTC завершается на edge-сервисе, после чего медиа преобразуется во внутренние протоколы для inference. Это компромиссное решение. Оно убирает WebRTC-сложность из backend-сервисов, но требует аккуратной маршрутизации пакетов и управления состоянием на edge.
Реализация началась с монолитного Go-сервиса на базе Pion, который обрабатывал signaling и media termination. Однако модель «один порт на сессию» быстро стала узким местом. Альтернатива — один порт на сервер — решает проблему портов, но не проблему маршрутизации в распределённой среде: первый пакет может попасть не в тот инстанс. Решение — разделить роли. Stateless relay отвечает за приём пакетов и их маршрутизацию, а stateful transceiver остаётся единственной точкой владения сессией (ICE, DTLS, SRTP).
Ключевой момент — deterministic routing первого пакета. Здесь используется ICE ufrag (username fragment) как встроенный механизм маршрутизации. В ufrag кодируется минимальная информация о целевом transceiver. Relay читает первый STUN-пакет, извлекает ufrag и направляет трафик в нужный backend. После этого создаётся лёгкая in-memory сессия, и последующие RTP/RTCP пакеты идут по уже установленному маршруту без повторного анализа. При сбое relay маршрут может быть восстановлен через следующий STUN-пакет или через кэш (Redis), где хранится соответствие client IP:port → transceiver.
Такая схема меняет экономику инфраструктуры. Вместо тысяч открытых UDP-портов остаётся небольшой фиксированный набор endpoint’ов. Это упрощает безопасность и балансировку нагрузки. Более того, появляется возможность глобального ingress через распределённый relay-слой. Пакет входит в сеть ближе к пользователю, снижая latency, jitter и packet loss до попадания в backbone. При этом signaling геораспределяется отдельно, что позволяет закрепить сессию за конкретным кластером и сократить время установления соединения.
Результаты носят скорее архитектурный характер. Конкретные метрики не приводятся, но описывается снижение latency на первом hop и более стабильное поведение real-time сессий. Важнее другое: backend-сервисы больше не обязаны быть WebRTC peer’ами. Это упрощает масштабирование inference, orchestration и speech-пайплайнов. Система становится ближе к обычной microservice-модели, где WebRTC ограничен edge-слоем.
Общий вывод выглядит прагматично. Сложность не исчезает — она перемещается в тонкий слой маршрутизации. Но именно там она лучше контролируется. Использование protocol-native механизма (ICE ufrag) позволяет избежать внешних lookup’ов и держать routing на packet path. В результате система сохраняет стандартное поведение WebRTC для клиентов, но внутри работает по правилам, которые лучше соответствуют highload и cloud-native инфраструктуре.