Steven
Steven7 perc olvasás

A csend teherhordó volt

A GeekBye v1 utolsó két kiadása ugyanarról a kényelmetlen igazságról szól: a valós idejű átírás egy valódi hálózaton nem veszteségmentes, és az őszinte lépés az, hogy abbahagyod az úgy tenni, mintha az lenne. A v1.8.20 minden audio chunk másolatát a lemezen tartotta, mielőtt egy újracsatlakozás során eldobta volna, és elkezdte hangosan megjelölni a réseket az átiratban. A v1.9.0 abbahagyta a csend küldését, hogy sávszélességet spóroljon — és felfedezte, hogy a csend pontosan az a jel volt, amelyet az átíró használt annak megállapítására, hogy egy mondat véget ért. Két kiadás a dolgok eldobásának költségéről.

Mérnöki munka
Audio
Megbízhatóság
GeekBye kiadások
A csend teherhordó volt

A GeekBye v1 utolsó két kiadása — a v1.8.20 és a v1.9.0, azok, amelyek közvetlenül a 2-es verzió mérföldkő előtt vannak — olyan témát osztanak meg, amely csak akkor válik nyilvánvalóvá, ha együtt olvasod őket. Mindkettő olyan dolgokról szól, amelyeket az alkalmazás csendben eldobott, és mindkettő ugyanarra a következtetésre jut: amit eldobsz, hogy hatékony légy, több munkát végzett, mint gondoltad. Az egyik kiadás hangot dobott el a hálózati újracsatlakozások során. A másik csendet dobott el, hogy sávszélességet spóroljon. Mindkét esetben az eldobott dolog teherhordónak bizonyult.

A valós idejű átírás nem veszteségmentes, és a v1.8.20 ezt beismeri

Íme egy tény a WebSocketen keresztüli streaming beszéd-szöveg átírásról, amelyről könnyű elfordítani a tekintetet: amikor a kapcsolat megszakad és újracsatlakozik — vagy amikor az alkalmazás átvált az egyik átírási szolgáltatóról a másikra, Deepgramról ElevenLabsre — a kliens szándékosan eldobja az adott rés alatt befogott audio chunkokat. Muszáj neki. Egy frissen csatlakozott szolgáltató, amely hirtelen egy halomnyi elavult hangot kap, megfullad, vagy ami rosszabb, a rossz dologra rögzíti a hangaktivitás-érzékelőjét. Így a kliens eldobja azokat a chunkokat.

A probléma nem az eldobás. A probléma az, hogy mit jelentett az eldobás: azok a szavak egyszerűen soha nem jelentek meg az átiratban, és semmi sem mondta meg, hogy hiányoznak. A felvétel teljesnek tűnt. Nem volt az.

A v1.8.20 két fronton támadja ezt, és egyik sem az, hogy „tedd az újracsatlakozásokat sohasem hangvesztővé" — mert nem tudod, valós időben nem. Ehelyett a veszteséget helyreállíthatóvá és őszintévé teszi.

A helyreállítható fele egy tartós PCM tee. A fő folyamat befogó handlerjében minden befogott chunk most egy lemezen lévő fájlba íródik mielőtt elérné az újracsatlakozás és a türelmi időszak eldobó kapuit:

every captured chunk is written to <userData>/sessions/session-<ts>.wav before realtime drop gates so reconnect-lost audio is preserved for a future batch-STT reconciliation pass

A mechanizmus kellemesen alacsony technológiájú: a teeChunkToDisk megnyit egy WriteStream-et, előre lefoglal egy 44 bájtos WAV fejlécet, hozzáfűzi a nyers 16 bites mono mintákat, ahogy megérkeznek, és leálláskor a végleges méretekkel javítja a fejlécet (fs.openSync(..., 'r+'), 44 bájt írása a 0 eltolásnál). Az eredmény a teljes hang lejátszható WAV-ja — beleértve azokat a részeket, amelyeket a valós idejű átirat soha nem látott. Amikor a felvétel véget ér, kibocsát egy session-audio-saved eseményt a fájl útvonalával és a bájtszámmal.

Két őszinteségi megjegyzés számít itt, mert könnyű túlállítani ezt. Először: a tee nem tölti ki a rést ezekben a kiadásokban. Megőrzi a nyers hangot egy későbbi összeegyeztető menethez — egy kötegelt újraátírás, amely összeegyezteti a fájlt az élő átirattal — ami egy horog, amelyet ezek a kiadások leraknak, nem pedig egy funkció, amelyet szállítanak. A követő commit még annotálja is a session-audio-saved-et egy TODO-val, amely az összeegyeztetési problémákra mutat, hogy senki se tévessze össze halott kóddal. Másodszor: ez lemezt fogyaszt. A csapat nagyjából 86 MB-ot becsült 30 perces felvételenként, így ugyanaz a kiadás hozzáad egy StartupCleanupService-t, amely kitakarítja a 7 napnál régebbi mentett munkameneteket. Egy határtalanul növekvő biztonsági háló egy szivárgás; a takarítás az, ami a tee-t egyáltalán szállíthatóvá teszi.

Az őszinte fele még egyszerűbb. Az első újracsatlakozási kísérletnél a kliens egy valódi, látható bejegyzést ír az átiratba:

[reconnecting — audio may be missing]

Az az egyetlen sor a kiadás egész filozófiája kicsiben. Az alkalmazás nem tudja garantálni, hogy mindent hallott, ezért abbahagyja annak sugallását, hogy hallotta. Egy rés, amelyet látsz, egy rés, amelyet meg tudsz kerülni; egy rés, amelyet nem látsz, egy hibajelentés, amely arra vár, hogy megtörténjen.

v1.9.0: a csend, amelyet optimalizálással eltüntettél, volt a jel

A v1.9.0 az utolsó v1 kiadás, és ez az, ahol a backend átadott a kliensnek egy sor élő-átirat felelősséget — csend eldobása, minden átirat-sor mikrofonhoz vagy rendszerhanghoz rendelése egy helyi hangaktivitás-idővonalról, az újracsatlakozási hurok birtoklása, felvételi korlátok érvényesítése. Ennek nagy része a 2-es verzió architektúrájának alapozása, és máshol el lett mesélve. De egy szál benne a legjobb kis történet bármelyik kiadásban, mert egy javítás, amelynek vissza kellett vonnia egy elszigetelten teljesen helyes optimalizáció kárát.

Egy nyilvánvalóan jó változással kezdődik: hagyd abba a csend küldését. A kliens elkezdte kiszűrni azokat a chunkokat, amelyek domináns forrása csend volt, mielőtt elküldte volna őket a socketen — kevesebb sávszélesség, kevesebb haszontalan hang a backendnek feldolgozni, és tiszteletben tartott egy explicit backend kliens-szerződést, amely azt mondta, „ne küldj csendet". Minden ok, hogy megtedd, megalapozott volt.

És teljesen elrontotta a megnyilatkozások véglegesítését. A commit, amely javítja, szokatlan őszinteséggel fekteti le az oksági láncot:

After the client began filtering silence chunks … ElevenLabs Scribe's VAD-based commit strategy no longer fires — EL needs ~1s of silence in the audio stream to commit a partial, and that silence never reaches it. Result: partials accumulate forever, UI shows trailing "...", utterances never finalize.

Ez egy szivárgó absztrakció platóni formája, amely visszaharap. Az átíró hangaktivitás-érzékelőjének nincs külön „a beszélő végzett" bemenete — kikövetkezteti egy mondat végét abból, hogy körülbelül egy másodperc csendet hall a hangban. A csend nem zaj volt a vonalon; egy üzenet volt. Azzal, hogy kiszűrte a sávszélesség spórolása érdekében, a kliens véletlenül abbahagyta annak az egyetlen jelnek a küldését, amely megmondta az átírónak, hogy egy mondat véget ért. Így minden részleges átirat egyszerűen tovább nőtt, a farka egy ...-ba halványult el, amely soha nem oldódott fel.

Megjavíthatnád ezt azzal, hogy visszateszed a csendet — de akkor visszavontad az optimalizációt. A tényleges javítás jobb: küldd el a csend jelentését anélkül, hogy elküldenéd a csendet. A kliens most helyben követi, mennyi ideje csendes a hang, és amikor ez átlépi a SILENCE_COMMIT_THRESHOLD_MS = 1000-et — szándékosan a backend saját VAD csendküszöbéhez illesztve — egy explicit commit jelet tüzel sávon kívül: a sendMixedChunk észreveszi a tartós csendet, küld egy realtime-audio:commit-ot IPC-n át, a fő folyamat továbbít egy { type: 'commit' }-ot a socketen, és a backend megmondja az ElevenLabsnek, hogy véglegesítse a részlegeset. Két apró állapotdarab, a silenceStartedAt és a silenceCommitFired, megakadályozza, hogy ismételten tüzeljen. A sávszélesség-győzelem túléli; a véglegesítés újra működik; a végén lévő ... feloldódik.

Van egy kis, őszinte lábjegyzet is ehhez a kiadáshoz: a határtalan újracsatlakozási logika, amelyet a v1.9.0 bevezetett, először a rendererben épült meg egy WsReconnectController-ként, majd ugyanazon a kiadáson belül törölve lett, amint a valódi implementáció leszállt a fő folyamat handlerjébe, ahová tartozott. Maga az újracsatlakozás története — határtalan backoff, szolgáltatók váltogatása minden öt hiba után — saját fejezetében van elmesélve; amit a v1.9.0 hozzáad, az az, hogy itt született meg az az architektúra, és ahol a kliens először vette át a tulajdonjogát.

Három dolog, amit ezek a kiadások megtanítottak

  1. Amikor nem tudod megelőzni a veszteséget, tedd helyreállíthatóvá és tedd láthatóvá. A v1.8.20 nem akadályozza meg, hogy az újracsatlakozások hangot dobjanak el — ez fizikailag nem lehetséges valós időben. Megőriz egy másolatot a nyers hangról későbbre, és megcímkézi a rést az átiratban. Egyik sem látványos; együtt a csendes adatvesztést egy ismert, korlátozott, helyreállítható állapottá alakítják.
  2. Egy biztonsági hálónak megvannak a saját korlátai. A tartós tee csendben megtöltené a lemezt, ha magára hagynád — egy javítás, amely lassított felvételű buggá válik. Az ugyanabban a kiadásban szállított 7 napos takarítás nem utólagos gondolat; ez az, ami a tee-t egyáltalán biztonságosan szállíthatóvá teszi. Ha hozzáadsz egy mechanizmust, amely felhalmoz, add hozzá ugyanabban a lélegzetben a mechanizmust, amely kitakarítja.
  3. Amit eldobsz, hogy hatékony légy, lehet, hogy egy jel. Az egyetlen legélesebb lecke bármelyik kiadásban: a csend kiszűrése minden tengelyen helyes volt, amelyet normálisan ellenőriznél — sávszélesség, terhelés, szerződési megfelelés — és mégis elrontotta a terméket, mert egy lejjebbi rendszer jelentést olvasott a csendbe. Amikor adatokat távolítasz el a hatékonyság érdekében, kérdezd meg, mi következtetett ki valamit azokból az adatokból. Néha explicit módon kell elküldened a jelentést, éppen azért, mert abbahagytad annak a dolognak a küldését, amelyből korábban kikövetkeztették.

És ezzel véget ér a verzió-1 története — az utolsó megerősítés az újraírás előtt. Az előző fejezetért, a három ige, amely életben tartja a Web Audiót (v1.8.16 + v2.0.2); azért, hogy hová tartott mindez, mit igényel valójában egy 2-es verzió: 206 commit (v2.0.0); a teljes ívért pedig a szoftver tökéletesre szállításának anatómiája.

Kapcsolódó cikkek

A három ige, amely életben tartja a Web Audiót
Steven
Steven8 perc olvasás

A három ige, amely életben tartja a Web Audiót

Két GeekBye pontkiadás, két hónap eltéréssel és két különböző fájlban, ugyanarra a leckére tanította meg az audio kódunkat ellentétes végekről: ne kezeld a böngésző AudioContext-jét eldobhatóként. Az egyik kiadás megtanult resume()-t hívni egy contexten, amelyet a macOS csendben suspendelt a felvétel közepén; a másik megtanult suspend()-et hívni close() helyett, hogy az egymást követő munkamenetek ne csapódjanak bele a Chromium nagyjából hat contextes plafonjába. Resume, suspend, close — ez az egész cselekmény.

Mérnöki munka
Audio
Desktop
Egy megszakadt kapcsolatnak nem szabadna összeomlasztania az egész alkalmazást — de a miénk ezt tette
Steven
Steven6 perc olvasás

Egy megszakadt kapcsolatnak nem szabadna összeomlasztania az egész alkalmazást — de a miénk ezt tette

Amikor a backendünk értekezlet közben elesett, nem csak a leiratozást állította le — összeomlasztotta az egész alkalmazást. Az ok egyetlen kezeletlen esemény volt, a javítás pedig tíz sor. Ez az a kiadási csokor, amely elérte, hogy a GeekBye bejelentkezve és kapcsolódva maradjon azokon a dolgokon át, amelyek korábban megölték.

Mérnöki munka
Megbízhatóság
Electron
A biztonsági ellenőrzés, amitől lehetetlen lett kilépni az appból
Steven
Steven6 perc olvasás

A biztonsági ellenőrzés, amitől lehetetlen lett kilépni az appból

Az automatikus frissítés volt a legnehezebb funkció, amit valaha kiadtunk — négy nap alatt hat kiadás kellett, hogy megakadályozzuk az app tönkretételét. A legrosszabb hibát mi magunk okoztuk, miközben óvatosak próbáltunk lenni: egy 500 ezredmásodperces „biztonsági" ellenőrzés egy sikertelen frissítést olyan folyamattá változtatott, amelyből szó szerint nem lehetett kilépni.

Mérnöki munka
Electron
Megbízhatóság