Steven
Steven6 min čítania

Vymazali sme 5 000 riadkov audio kódu — a prepisy sa začali zobrazovať dvakrát

GeekBye v1.6 vytrhol dva zariadeniové Swift prepisovače a nahradil ich jedným zjednoteným pipeline — čistý úbytok cez 5 000 riadkov, spravený, kým ľudia práve na aplikácii sedeli uprostred meetingu. Potom sa prepisy začali objavovať dvakrát, pretože chyba sa presťahovala do jedinej vrstvy, ktorá si stále myslela, že existujú dva enginy.

Inžinierstvo
Prepis
Architektúra
Vydania GeekBye
Vymazali sme 5 000 riadkov audio kódu — a prepisy sa začali zobrazovať dvakrát

Existuje jeden osobitný druh hrôzy pri refaktoringu kódu, ktorý beží práve teraz, pre ľudí, ktorí sú uprostred meetingu. Nemôžete funkciu vypnúť kvôli údržbe — niekto sa na ňu spolieha, že prepíše rozhovor, ktorý sa nedá zopakovať. GeekBye v1.6 spravil presne toto s najkritickejšou cestou v aplikácii: s audio pipeline. Toto je príbeh o tom, čo sme vymazali, čo sa pokazilo, a o dvoch lekciách o spoľahlivosti, ktoré z toho vyplynuli.

Dva enginy, jeden z nich v Swift

Pred v1.6 GeekBye prevod reči na text bežal na zariadení v Swift binárkach. Boli dve: jedna obaľovala Apple Speech framework a väčšia (cez 1,500 riadkov), ktorá zachytávala duálny zvuk — váš mikrofón plus systémový zvuk — cez ScreenCaptureKit a otvárala si vlastný WebSocket priamo do nášho backendu, aby streamovala do Deepgram. Dva prepisovače, dve cesty zachytávania, dva spôsoby komunikácie so sieťou, všetko v kompilovanom jazyku, čo znamenalo, že každá zmena vyžadovala rekompiláciu binárky.

v1.6.0 to všetko zbalil do jedného pipeline. Jediný zjednocujúci commit vymazal Apple Speech prepisovač (~1,145 riadkov), Deepgram Swift zachytávač (~1,523 riadkov), oba TypeScript mosty, starý orchestrátor a duálne audio IPC handlery — čistý úbytok cez 5,200 riadkov. Na ich miesto: renderer zachytáva zvuk cez štandardné Web APIs, posiela ho ako PCM cez IPC a Electron hlavný proces vlastní jedinú WebSocket cestu do backendu, pričom poskytovateľa vyberá konfigurácia backendu. Jedna cesta zachytávania, jeden vlastník socketu, jedno miesto na uvažovanie.

Vymazať päťtisíc riadkov je skvelý pocit. Asi na jeden deň.

Chyba sa presťahovala do vrstvy, ktorá si stále myslela, že sú dve

Potom sa prepisy začali zobrazovať dvakrát. Jediná vyslovená veta sa uložila ako dva identické záznamy — niekedy horšie.

Hlavná príčina je najpoučnejšia vec v celom tomto vydaní, pretože je to všeobecný zákon zjednotenia. Poskytovateľov sme zjednotili v hlavnom procese — jeden socket, jeden handler. No React vrstva to nezachytila: stále montovala dva event hooky špecifické pre poskytovateľa naraz, jeden postavený pre Deepgram a jeden pre ElevenLabs. Oba počúvali. Oba ukladali. Každý prepis uložil ktorýkoľvek hook, ktorý bol nažive — a oba boli.

A pod tým bolo druhé, záludnejšie zdvojenie: ElevenLabs vysiela každý finalizovaný riadok dvakrát — raz ako committed event a znova ako committed_with_timestamps. Takže aj s jedným hookom mohla jedna veta doraziť ako dva eventy.

Oprava (v línii v1.6) je úhľadná dvojdielna, ktorá presne mapuje na dve príčiny: každý event hook zablokovať za enabled príznakom, ktorý riadi konfigurácia aktívneho poskytovateľa z backendu, takže vždy je nažive iba jeden poslucháč; a pridať deduplikáciu textu podľa zdroja (zapamätať si posledný uložený riadok pre mikrofón a pre systémový zvuk), aby sa spolklo dvojité vysielanie ElevenLabs.

Lekcia je väčšia než táto chyba: keď zjednotíte dva backendy za jedno rozhranie, duplikácia nezmizne — presťahuje sa do vrstvy, ktorá stále predpokladá, že sú dve. Zbalili sme inštalatérstvo v hlavnom procese a únik sa vynoril o dve vrstvy vyššie, v prepojení eventov UI. Zjednotenie pipeline nie je hotové, keď je zjednotený producent; je hotové, keď každý konzument prestal veriť v starý tvar.

Prvé opätovné pripojenie: „vzdaj sa po piatich pokusoch"

v1.6.0 priniesol aj úplne prvé WebSocket automatické opätovné pripojenie GeekBye s exponenciálnym backoffom — a stojí za to ho ukázať práve preto, že je to primitívny predok nepriestrelnej verzie, ktorú prevádzkujeme dnes.

Originál bol ohraničený. Päť pokusov, oneskorenia 1s, 2s, 4s, 8s, 16s — a potom sa vzdal, vyslal fatálnu chybu: „Spojenie stratené — reštartujte prosím prepis." Vtedy to pôsobilo zodpovedne: neopakuj donekonečna, povedz používateľovi úprimne. V praxi to bola UX chyba v prestrojení. Šestnásťsekundový sieťový výpadok — prepnutie Wi-Fi, opätovné pripojenie VPN, tunel — je prechodný, no ohraničené opakovanie ho berie ako terminálny. Používateľ neurobil nič zlé a dostal pokyn reštartovať živý meeting.

To je presne to zlyhanie, ktoré súčasný dizajn odstránil. Dnes sa GeekBye opätovne pripája s neohraničeným backoffom a dokonca cyklí poskytovateľov, a zastaví sa iba z naozaj fatálneho dôvodu — autentifikácia, kvóta, fakturácia. Celá cesta od „vzdaj sa po piatich" k „nikdy sa nevzdávaj pri prechodnej chybe" je vyrozprávaná v prečo váš AI zapisovateľ prestane fungovať pri zlom Wi-Fi. v1.6 je tam, kde sa tá cesta začala, s naivnou verziou, ktorá musela existovať ako prvá.

Zastarané spojenia vás strašia, pokiaľ nemajú mená

Ešte jedna lekcia o spoľahlivosti pristála vo v1.6.3 a opravila jemnú triedu chýb, s ktorou sa každý, kto robí opätovné pripájanie cez stavový backend, nakoniec stretne.

Keď sa opätovne pripojíte — alebo prepnete jazyk prepisu uprostred relácie, čo sa opätovne pripája pod kapotou — staré spojenie nie vždy zomrie potichu. Jeho posledné chrapoty (CONNECTION_LOST, disconnected) môžu doraziť po tom, ako je nové spojenie už zdravé, a strhnúť dokonalú náhradu. Oprava dáva každému pokusu o spojenie identitu — session ID na jedno spojenie vyrazené na WebSocket URL — plus krátke okno milosti po opätovnom pripojení, počas ktorého sa zastarané správy o odpojení z predchádzajúceho pokusu ignorujú. Pred zatvorením socketu tiež posiela explicitnú stop správu, aby backend mohol starú reláciu čisto zbaliť.

Princíp: akákoľvek logika opätovného pripájania cez stavový backend potrebuje označiť každý pokus identitou a zaobchádzať s oneskorenými správami z predchádzajúcich pokusov ako so šumom — inak úspešné opätovné pripojenie zabije umierajúci dych svojho predchodcu.

Tri veci, ktoré nás v1.6 naučil

  1. Zjednotenie premiestňuje chyby; nemaže ich. Zbaľte dva enginy do jedného a preverte každého konzumenta, ktorý si stále myslí, že sú dva. Ten náš bolo UI, montujúce dva event hooky potom, ako sa inštalatérstvo už zlúčilo.
  2. Vaše prvé opätovné pripojenie bude ohraničené, a ohraničené je pre živé streamy nesprávne. „Vzdaj sa po N pokusoch" mení prechodný výpadok na terminálne zlyhanie, z ktorého je používateľ obviňovaný. Odlíšte fatálne (autentifikácia, kvóta) od prechodného (spadnutý socket) hneď od úplne prvej verzie.
  3. Opätovné pripojenia potrebujú identitu. Označte každý pokus; ignorujte oneskorené správy z mŕtvych pokusov. Spojenie bez mena môže byť zavraždené vlastným duchom.

Toto je druhá kapitola príbehu o spoľahlivosti, ktorý sa stáva GeekBye v2. Pre prvú si pozrite ságu automatickej aktualizácie, ktorá si vyžiadala šesť vydaní za štyri dni (v1.5.x); pre miesto, kam práca na opätovnom pripájaní nakoniec dorazila, prečo váš AI zapisovateľ prestane fungovať pri zlom Wi-Fi; a pre celý oblúk anatómiu dodávania softvéru do dokonalosti.