Ugrás a fő tartalomra
OpenAI

Hogyan biztosít az OpenAI alacsony késleltetésű hangalapú MI-t nagy léptékben?

Yi Zhang és William McDonald, műszaki munkatársak tollából

A hangalapú MI csak akkor hat természetesnek, ha a beszélgetés a beszéd tempójában halad. Amikor a hálózat akadályozza ezt, az emberek azonnal észreveszik a kínos szüneteket, a levágott félbeszakításokat vagy a késleltetett közbevágást. Ez fontos a ChatGPT Hang esetében, a Realtime API-val dolgozó fejlesztőknek, az interaktív munkafolyamatokban működő ügynököknek, valamint azoknak a modelleknek, amelyeknek úgy kell feldolgozniuk a hangot, hogy a felhasználó még beszél.

Az OpenAI léptékén ez három konkrét követelményt jelent:

  • Globális elérés több mint 900 millió heti aktív felhasználó számára
  • Gyors kapcsolatfelépítés, hogy a felhasználó a munkamenet kezdetekor azonnal beszélhessen
  • Alacsony és stabil médiaköridő, alacsony jitterrel és csomagvesztéssel, hogy a váltott megszólalás gördülékeny legyen

Az OpenAI valós idejű MI-interakciókért felelős csapata nemrég újratervezte WebRTC stackünket, hogy kezelje azt a három korlátot, amelyek nagy léptékben ütközni kezdtek egymással: a munkamenetenkénti egy portos médiavégződtetés nem illeszkedik jól az OpenAI infrastruktúrájához, az állapottartó ICE (Interactive Connectivity Establishment) és DTLS (Datagram Transport Layer Security) munkameneteknek stabil tulajdonjogra van szükségük, a globális útválasztásnak pedig alacsonyan kell tartania az első ugrás késleltetését. Ebben a bejegyzésben bemutatjuk az általunk felépített, szétválasztott relay plus transceiver architektúrát, amely megőrzi az ügyfelek számára a szabványos WebRTC-viselkedést, miközben megváltoztatja a csomagok útvonalát az OpenAI infrastruktúráján belül.

A WebRTC lehetővé teszi számunkra, hogy valós idejű MI-termékeket készítsünk

A WebRTC nyílt szabvány böngészők, mobilalkalmazások és szerverek közötti alacsony késleltetésű hang-, videó- és adatküldésre. Gyakran a peer-to-peer hívásokhoz kötik, de ügyfél–szerver valós idejű rendszerekhez is praktikus alapot ad, mert egységesíti az interaktív média nehéz részeit: ICE a kapcsolat felépítéséhez és a NAT (Network Address Translation) bejárásához, DTLS és SRTP (Secure Real-time Transport Protocol) a titkosított átvitelhez, kodekegyeztetés a hang tömörítéséhez és dekódolásához, RTCP (Real-time Transport Control Protocol) a minőségszabályozáshoz, valamint ügyféloldali funkciók, például visszhangkioltás és jitterpufferelés.

Ez a szabványosítás fontos az MI-termékeknél. WebRTC nélkül minden ügyfélnek más választ kellene adnia arra, hogyan hozzon létre kapcsolatot NAT-okon keresztül, hogyan titkosítsa a médiát, hogyan egyeztessen kodekeket (az átvitelhez és kitömörítéshez kiválasztott kódoló-dekódolókat), és hogyan alkalmazkodjon a változó hálózati feltételekhez. A WebRTC-vel olyan protokollstackre építhetünk, amelyet már megvalósítottak a böngészők és mobilplatformok, így saját munkánkat a valós idejű médiát a modellekhez kapcsoló infrastruktúrára összpontosíthatjuk.

Magára a WebRTC ökoszisztémára is építünk, beleértve az érett nyílt forráskódú implementációkat és azt a szabványosítási munkát, amely biztosítja a böngészők, mobilalkalmazások és szerverek együttműködését. Justin Uberti (a WebRTC egyik eredeti architektje) és Sean DuBois (a Pion megalkotója és karbantartója) alapvető munkája lehetővé tette, hogy a hozzánk hasonló csapatok kipróbált médiainfrastruktúrára építsenek ahelyett, hogy újra feltalálnák az alacsony szintű átvitel, titkosítás és torlódásszabályozás működését. Szerencsések vagyunk, hogy Justin és Sean ma már itt, az OpenAI-nál a kollégáink, és segítenek abban, hogyan hozzuk közelebb egymáshoz a WebRTC-t és a valós idejű MI-t.

Az MI számára a legfontosabb tulajdonság az, hogy a hang folyamatos adatfolyamként érkezik. Egy beszélő ügynök már akkor megkezdheti az átírást, az érvelést, az eszközök hívását vagy a beszéd generálását, miközben a felhasználó még beszél, ahelyett hogy megvárná a teljes feltöltést. Ez különbözteti meg a társalgásszerűnek ható rendszert attól, amely inkább adóvevőként működik.

Médiaarchitektúra kiválasztása

Miután a WebRTC mellett döntöttünk, a következő kérdés az volt, hol végződtessük azt (ahol elfogadjuk és birtokoljuk a WebRTC-kapcsolatot — például a peremen), és hogyan kapcsoljuk ezeket a munkameneteket az inferencia-háttérrendszerhez. A végződtetés azért fontos, mert meghatározza, hogyan kezeljük a valós idejű munkamenetállapotot, a médiaátvitelt, az útválasztást, a késleltetést és a hibák elkülönítését.

1. lehetőség: Az SFU-megközelítés az MI-t WebRTC-résztvevőként kezeli

Az SFU, vagyis a selective forwarding unit olyan médiaszerver, amely minden résztvevőtől fogad egy-egy WebRTC-adatfolyamot, és szelektíven továbbítja a többi résztvevőnek. Ebben a modellben az SFU minden résztvevő számára külön WebRTC-kapcsolatot végződtet, és az MI egy másik résztvevőként csatlakozik a munkamenethez. Ez jól illeszkedhet olyan eleve több résztvevős termékekhez, mint a csoporthívások, tantermek vagy együttműködő megbeszélések. Egy helyen tartja a hangkodekeket, az RTCP-üzeneteket, az adatcsatornákat, a rögzítést és az adatfolyamonkénti szabályokat.1

Még ügyfél–MI termékeknél is gyakran az SFU az alapértelmezett kiindulópont, mert lehetővé teszi, hogy a csapatok egy bevált rendszert használjanak újra jelzéskezelésre, médiaútválasztásra, rögzítésre, megfigyelhetőségre és jövőbeli bővítésekre, például emberi átvételre vagy további résztvevők hozzáadására.

2. lehetőség: A transceiver-megközelítés a peremen végződteti a WebRTC-t, és háttérprotokollá alakítja

A mi terhelésünk más. A legtöbb munkamenet 1:1 — egy felhasználó beszél egy modellel, vagy egy alkalmazás egy valós idejű ügynökkel —, és minden fordulóban érzékeny a késleltetésre. Ennél a forgalmi mintánál egy transceiver modellt választottunk: egy peremi WebRTC-szolgáltatás végződteti az ügyfélkapcsolatot, majd a médiát és eseményeket egyszerűbb belső protokollokká alakítja modellinferencia, átírás, beszédgenerálás, eszközhasználat és összehangolás céljára.

Ebben a kialakításban a transceiver az egyetlen szolgáltatás, amely birtokolja a WebRTC-munkamenet állapotát, beleértve az ICE-kapcsolatellenőrzéseket, a DTLS-kézfogást, az SRTP-titkosítási kulcsokat és a munkamenet életciklusát. A „végződtetés” itt azt jelenti, hogy a transceiver az a végpont, amely befejezi ezeket a kézfogásokat, és titkosítja vagy visszafejti a médiát. Az, hogy ez az állapot egy helyen marad, könnyebben átláthatóvá tette a munkamenet tulajdonjogát, és lehetővé tette, hogy a háttérszolgáltatások hagyományos szolgáltatásokként skálázódjanak ahelyett, hogy maguk is WebRTC-peerekként működnének.

Az alapvető telepítési probléma: a WebRTC találkozik a Kubernetes-szel

Miután a transceiver modellt választottuk, első implementációnk egyetlen, Pionra épülő Go-szolgáltatás volt, amely a jelzéskezelést és a médiavégződtetést is végezte. Ez működteti a ChatGPT Hangot, a Realtime API WebRTC-végpontját és számos kutatási projektet.

Operatív szempontból a transceiver szolgáltatás két feladatot lát el:

  • Jelzéskezelés: SDP-egyeztetés, kodekválasztás, ICE-hitelesítő adatok és munkamenet-beállítás
  • Média: a lefelé irányuló WebRTC-kapcsolatok végződtetése és a háttérszolgáltatások felé irányuló kapcsolatok fenntartása inferencia és összehangolás céljára

Azt akartuk, hogy a szolgáltatás úgy fusson, mint infrastruktúránk többi része: Kubernetesen, ahol a terhelések fel- és leskálázhatók, és a kereslet változásával gazdagépek között mozgathatók. A hagyományos, munkamenetenként egy portos WebRTC-modell azonban rosszul illik ehhez a környezethez, mert nagy nyilvános UDP-porttartományokra támaszkodik, amelyeket nehéz kitenni, védeni és megőrizni, miközben podok kerülnek hozzáadásra, eltávolításra vagy újraütemezésre.2

Portkimerülés

Az első probléma maga a munkamenetenként egy portos modell volt. Nagy egyidejűség mellett ez nagyon nagy UDP-porttartományok kitettségét és kezelését jelenti.

  • A felhős terheléselosztók és a Kubernetes-szolgáltatások nem arra készültek, hogy szolgáltatásonként több tízezer nyilvános UDP-port köré szerveződjenek. Minden további tartomány növeli az operatív bonyolultságot a terheléselosztó konfigurációjában, az állapotellenőrzésben, a tűzfalszabályokban és a biztonságos bevezetésben.3
  • A nagy UDP-porttartományokat nehéz védeni, mert növelik a kívülről elérhető felületet, és nehezebbé teszik a hálózati szabályok auditálását.
  • Az automatikus skálázáshoz sem illenek jól. A podok folyamatosan kerülnek hozzáadásra, eltávolításra és újraütemezésre a Kubernetesben. Ha minden podnak nagy, stabil porttartományt kell lefoglalnia és hirdetnie, az törékennyé teszi ezt a rugalmasságot.4

Ezért mozdul el sok WebRTC-rendszer az egy szerverhez egyetlen UDP-port irányába, alkalmazásszintű demultiplexeléssel e port mögött.5

Állapottapadás

Az egy szerverhez egyetlen portot használó kialakítások megoldják a portszám problémáját, de behoznak egy másodikat: meg kell őrizni minden munkamenet tulajdonjogát a teljes flottán keresztül.

Az ICE és a DTLS állapottartó protokollok. Annak a folyamatnak, amely létrehozta a munkamenetet, továbbra is meg kell kapnia annak a munkamenetnek a csomagjait, hogy ellenőrizhesse a kapcsolatpróbákat, befejezhesse a DTLS-kézfogást, visszafejthesse az SRTP-t, és feldolgozhassa a későbbi munkamenet-változásokat, például az ICE-újraindításokat. Ha ugyanazon munkamenet csomagjai egy másik folyamathoz érkeznek, a beállítás meghiúsulhat vagy a média megszakadhat.

Ez adott nekünk egy konkrét célt: kis, fix UDP-felületet tegyünk ki a nyilvános internet felé, miközben minden csomagot továbbra is ahhoz a transceiverhez irányítunk, amely a megfelelő WebRTC-munkamenet tulajdonosa.

A WebRTC-médiaarchitektúrák összehasonlítása

Ennek elérésére több lehetőséget is megvizsgáltunk, köztük a TURN-t (Traversal Using Relays around NAT), ahol egy peremi relay végződteti az ügyfélallokációkat, és azok nevében továbbítja a forgalmat.2

Megközelítés

Előnyök

Hátrányok

Egyedi IP:port munkamenetenként (más néven natív közvetlen UDP)

Közvetlen kliens–szerver médiaútvonal

Nincs továbbító réteg az adatútvonalban

Munkamenetenként egy nyilvános UDP-portot igényel

A nagy porttartományokat nehéz kitenni és védeni

Rossz illeszkedés a Kuberneteshez és a felhős terheléselosztókhoz

Egyedi IP:port szerverenként

A munkamenetenkénti kitettségnél jóval kisebb nyilvános UDP-lábnyom

Egy megosztott socket szerverenként sok munkamenetet képes demultiplexelni

Egyetlen gazdagépen jól működik, de megosztott, terheléselosztott flottán önmagában nem

Az egyetlen gazdagépen végzett munkamenet-demultiplexelés csak azután segít, hogy egy csomag elérte azt a gazdagépet; terheléselosztott flottában az első csomag továbbra is rossz példányra érkezhet, ezért továbbra is determinisztikus mód kell arra, hogy minden munkamenetet ahhoz a folyamathoz irányítsunk, amely birtokolja


TURN relay (protokollt végződtető)

Az ügyfeleknek csak a TURN relay címét és portját kell elérniük

A szabályok a peremen központosíthatók

A TURN-allokációk plusz beállítási körutakat adnak hozzá

Az allokációk áthelyezése vagy helyreállítása a TURN-szerverek között továbbra is nehéz

Állapotmentes továbbító + állapottartó végződtető (az OpenAI relay + transceiver megoldása)

Kis nyilvános UDP-lábnyom

A transceiver továbbra is birtokolja a teljes WebRTC-munkamenetet

Egy továbbítási ugrást ad hozzá, mielőtt a média eléri a tulajdonos transceivert

Egyedi koordinációt igényel a relay és a transceiver között

Architektúra-áttekintés: relay + transceiver

Az általunk bevezetett architektúra szétválasztja a csomagútválasztást a protokollvégződtetéstől. A jelzéskezelés továbbra is eljut a transceiverhez a munkamenet beállításához, míg a média először a relayen keresztül érkezik. A relay egy könnyűsúlyú UDP-továbbító réteg kis nyilvános kitettséggel, a transceiver pedig a mögötte lévő állapottartó WebRTC-végpont.

A relay állapotmentesen továbbítja a csomagokat a transceiverhez

A relay nem fejti vissza a médiát, nem futtat ICE-állapotgépeket, és nem vesz részt a kodekegyeztetésben. Éppen csak annyi csomagmetaadatot olvas, amennyi a cél kiválasztásához kell, majd továbbítja a csomagot ahhoz a transceiverhez, amely birtokolja a munkamenetet. A transceiver továbbra is normál WebRTC-folyamot lát, és továbbra is ő birtokolja a teljes protokollállapotot. Az ügyfél nézőpontjából semmi sem változik a WebRTC-munkamenetben.

Útválasztás ICE-hitelesítő adatok alapján

Ebben a felépítésben az első csomag útválasztása a kulcslépés. Egy relaynek az ügyféltől érkező első csomagot már azelőtt útba kell igazítania, hogy maga a csomagútvonal bármiféle munkamenetet tartalmazna, nem pedig úgy, hogy megáll egy külső lekérdező szolgáltatásnál.

Minden WebRTC-munkamenet már eleve tartalmaz egy protokollnatív útválasztási kapaszkodót: az ICE username fragmentet, vagyis az ufragot, egy rövid azonosítót, amelyet a munkamenet beállításakor cserélnek ki, és amely visszaköszön a STUN kapcsolatellenőrzésekben. Úgy hozzuk létre a szerveroldali ufragot, hogy az éppen elegendő útválasztási metaadatot tartalmazzon ahhoz, hogy a relay következtetni tudjon a célklaszterre és a tulajdonos transceiverre.

A szekvenciadiagram bemutatja, hogyan jön létre a kapcsolat

A jelzéskezelés során a transceiver lefoglalja a munkamenet állapotát, és visszaad egy megosztott relay VIP-t és UDP-portot az SDP-válaszban. A VIP egy virtuális IP-cím, amely a relayflotta elé kerül; a porttal együtt egyetlen stabil célcímet ad az ügyfélnek, például 203.0.113.10:3478, még akkor is, ha sok relaypéldány ül mögötte. Az ügyfél első médiás útvonalcsomagja általában egy STUN (Session Traversal Utilities for NAT) binding request, amelyet az ICE arra használ, hogy ellenőrizze: a csomagok elérik-e a hirdetett címet.

A relay csak annyit elemez ebből az első STUN-csomagból, hogy kiolvassa a szerver ufragját, dekódolja az útválasztási tippet, és továbbítsa a csomagot a tulajdonos transceiverhez. Minden transceiver egy megosztott UDP socketen figyel, vagyis egyetlen operációsrendszer-végponton, amely egy belső IP:portra van kötve, nem pedig munkamenetenként egy socketen. Miután a relay létrehozott egy munkamenetet az ügyfél forrás IP:portja és e transceivercél között, a későbbi DTLS-, RTP- és RTCP-csomagok ezen a munkameneten belül áramlanak anélkül, hogy újra dekódolni kellene az ufragot.

A relay munkamenete szándékosan minimális, mindössze egy memóriabeli munkamenetből áll, amely a csomagtovábbítást irányítja, valamint a monitorozáshoz szükséges számlálókból és a munkamenet lejáratához, illetve tisztításához szükséges időzítőkből. Ez a tervezési döntés közvetlenül a csomagútvonalon tartja az útválasztást. Ha egy relay újraindul és elveszíti a munkamenetet, a következő STUN-csomag az ufrag útválasztási tippjéből újra felépíti azt. A még nagyobb megbízhatóság érdekében egy Redis-gyorsítótár tárolja a <ügyfél IP + Port, transceiver IP + Port> leképezést, miután az útvonal létrejött, így az jóval korábban helyreállítható, még a következő STUN-csomag megérkezése előtt.

Global Relay és földrajzilag irányított jelzéskezelés

Miután a nyilvános UDP-felületet kis számú stabil címre és portra csökkentettük, ugyanezt a relaymintát globálisan is telepíthettük. A Global Relay a földrajzilag elosztott relay belépési pontjaink flottája, amelyek mind ugyanazt a csomagtovábbítási viselkedést valósítják meg.

A széles földrajzi belépési lefedettség lerövidíti az ügyfél és az OpenAI közötti első ugrást, mert egy csomag a felhasználóhoz közeli relayen keresztül léphet be a hálózatunkba, mind földrajzi, mind hálózati topológiai értelemben, ahelyett hogy először egy távoli régióig haladna át a nyilvános interneten. Gyakorlatban ez alacsonyabb késleltetést, kisebb jittert és kevesebb elkerülhető veszteségi hullámot jelent, mielőtt a forgalom elérné gerinchálózatunkat.6

A Global Relay réteg csomagokat fogad az ügyféltől, és továbbítja azokat az adóvevőklaszternek

A jelzéskezeléshez a Cloudflare földrajzi és közelségi irányítását használjuk, hogy a kezdeti HTTP- vagy WebSocket-kérés egy közeli transceiverklaszterhez érjen. A kérés környezete határozza meg a munkamenet helyét, és azt, melyik Global Relay belépési pontot hirdetjük az ügyfélnek. Az SDP-válasz megadja a Global Relay címét, míg az ufrag elegendő információt tartalmaz ahhoz, hogy a Global Relay a kijelölt klaszterhez irányítsa a médiát, a relay pedig a cél transceiverhez továbbítsa azt.

A földrajzilag irányított jelzéskezelés és a Global Relay együtt közel helyezi mind a beállítást, mind a médiát a belépési útvonalhoz, miközben a munkamenetet egyetlen transceiverhez rögzíti. Ez csökkenti a jelzéskezelés és az első ICE-kapcsolatellenőrzés köridejét, ami közvetlenül lerövidíti azt az időt, ameddig a felhasználónak várnia kell, mielőtt a beszéd elkezdődhet.

A relay implementációja és teljesítménye

A relay szolgáltatást Go nyelven írtuk, és szándékosan szűkre szabtuk az implementációt. Linuxon a kernel hálózati verem fogadja az UDP-csomagokat a gép hálózati interfészéről, és eljuttatja őket egy sockethez, ahhoz az operációsrendszer-végponthoz, amelyet egy folyamat IP:Port kötése után olvas. A relay felhasználói térben fut, így egy hagyományos Go-folyamat olvassa a csomagfejléceket erről a socketről, frissít egy kis mennyiségű folyamállapotot, és továbbítja a csomagokat anélkül, hogy végződtetné a WebRTC-t. Nem volt szükségünk kernel-bypass keretrendszerre, amely lehetővé tenné, hogy egy felhasználói térben futó folyamat közvetlenül kérdezze le a hálózati sorokat a nagyobb csomagsebesség érdekében, de operatív bonyolultságot is hozna.

Fő tervezési döntések:

  • Nincs protokollvégződtetés: A relay csak a STUN-fejléceket/ufragot elemzi; a későbbi DTLS-, RTP- és RTCP-forgalomhoz gyorsítótárazott állapotot használ, így a csomagok átlátszatlanok maradnak.
  • Efemer állapot: Egy kisméretű, rövid időtúllépésű, memóriabeli térképet tart fenn az ügyfélcím és a transceivercél között a folyamállapothoz és a megfigyelhetőséghez.
  • Horizontális skálázhatóság: Több relaypéldány fut párhuzamosan egy terheléselosztó mögött. Az állapot nem kemény WebRTC-állapot, így az újraindítások minimális forgalomkiesést és gyors folyamhelyreállást okoznak.

Hatékonysági intézkedések:

  • A SO_REUSEPORT egy Linux socketopció, amely lehetővé teszi, hogy ugyanazon a gépen több relay worker ugyanahhoz az UDP-porthoz kötődjön. A kernel ezután elosztja a bejövő csomagokat e workerek között, elkerülve az egyetlen olvasási ciklus szűk keresztmetszetét.
  • A runtime.LockOSThread minden UDP-t olvasó goroutine-t egy adott OS-szálhoz rögzít. A SO_REUSEPORT-tal együtt ez hajlamos arra, hogy ugyanazon folyamból származó csomagokat (a forrás- és cél-IP:Port plusz a protokoll) ugyanazon CPU-maghoz tartsa, javítva a gyorsítótár-localitást és csökkentve a kontextusváltást.
  • Az előre lefoglalt pufferek és a minimális másolás alacsonyan tartják az elemzési és allokációs többletet, hogy elkerüljük a Go szemétgyűjtését.

Ez az implementáció viszonylag kis relaylábnyommal kezelte globális valós idejű médiaforgalmunkat, ezért a kernel-bypass út helyett megtartottuk az egyszerűbb megoldást.

Eredmények és tanulságok

Ez az architektúra lehetővé teszi számunkra, hogy a WebRTC-médiát Kubernetesben futtassuk anélkül, hogy több ezer UDP-portot kellene kitenni. Ez azért fontos, mert egy kisebb és fix UDP-felületet könnyebb védeni és terheléselosztani, és lehetővé teszi az infrastruktúra skálázását nagy nyilvános porttartományok lefoglalása nélkül. A Kubernetes jobb infrastruktúra-támogatásával és a kisebb felületből adódó nagyobb biztonsággal ez a kialakítás az ügyfelek számára a szabványos WebRTC-viselkedést is megőrzi, és megerősíti, hogy a mi terhelésünkhöz az SFU nélküli kialakítás volt a helyes alapértelmezés. A legtöbb munkamenetünk pont-pont kapcsolatú, késleltetésérzékeny, és könnyebben skálázható, ha az inferenciaszolgáltatásoknak nem kell WebRTC-peerként viselkedniük.

A tágabb tanulság az, hogy a bonyolultságot legjobb egy vékony útválasztási rétegben elhelyezni, nem minden háttérszolgáltatásban, és nem egyedi ügyfélviselkedésben. Az útválasztási metaadatok egy protokollnatív mezőbe kódolása determinisztikus elsőcsomag-útválasztást, kis nyilvános UDP-lábnyomot és elegendő rugalmasságot adott ahhoz, hogy a belépési pontokat világszerte közel helyezzük a felhasználókhoz.

Néhány döntés különösen fontos volt:

  • Őrizzük meg a protokoll szemantikáját a peremen. Az ügyfelek továbbra is szabványos WebRTC-t használnak, ami érintetlenül hagyja a böngészős és mobilos interoperabilitást.
  • Tartsuk a nehéz munkamenet-állapotokat egy helyen. A transceiver birtokolja az ICE-t, a DTLS-t, az SRTP-t és a munkamenet életciklusát; a relay csak továbbítja a csomagokat.
  • Azokra az információkra útválasszunk, amelyek már jelen vannak a beállításban. Az ICE ufrag elsőcsomag-útválasztási kapaszkodót adott anélkül, hogy forró útvonalú lekérdezési függőséget kellett volna hozzáadnunk.
  • Az általános esetre optimalizáljunk, mielőtt kernel-bypasshoz nyúlnánk. Egy szűk Go-implementáció a SO_REUSEPORT, a szálrögzítés és az alacsony allokációjú elemzés körültekintő használatával elegendő volt a terhelésünkhöz.

A valós idejű hangalapú MI csak akkor működik, ha az infrastruktúra láthatatlanná teszi a késleltetést. Számunkra ez azt jelentette, hogy megváltoztattuk a WebRTC-telepítésünk formáját anélkül, hogy megváltoztattuk volna azt, amit az ügyfelek magától a WebRTC-től elvárnak.