Steven
Steven6 min lukuaika

Hiljaisuus oli kantava

GeekBye v1:n kaksi viimeistä julkaisua kertovat samasta epämukavasta totuudesta: reaaliaikainen transkriptio oikean verkon yli ei ole häviötöntä, ja rehellinen liike on lakata teeskentelemästä että se on. v1.8.20 säilytti jokaisesta äänipalasta kopion levyllä ennen kuin pudotti sen uudelleenyhdistämisen aikana, ja alkoi merkitä transkription aukot ääneen. v1.9.0 lakkasi lähettämästä hiljaisuutta kaistanleveyden säästämiseksi — ja huomasi, että hiljaisuus oli juuri se signaali, jolla transkriboija tiesi lauseen päättyneen. Kaksi julkaisua asioiden pois heittämisen hinnasta.

Ohjelmistokehitys
Audio
Luotettavuus
GeekBye-julkaisut
Hiljaisuus oli kantava

GeekBye v1:n kaksi viimeistä julkaisua — v1.8.20 ja v1.9.0, ne aivan ennen version 2 virstanpylvästä — jakavat teeman joka käy ilmeiseksi vasta kun luet ne yhdessä. Molemmat käsittelevät asioita joita sovellus hiljaa heitti pois, ja molemmat päätyvät samaan johtopäätökseen: asia jonka pudotat tehokkuuden vuoksi teki enemmän työtä kuin luulit. Yksi julkaisu pudotti ääntä verkon uudelleenyhdistämisten aikana. Toinen pudotti hiljaisuutta kaistanleveyden säästämiseksi. Molemmissa tapauksissa pudotettu asia osoittautui kantavaksi.

Reaaliaikainen transkriptio ei ole häviötöntä, ja v1.8.20 myöntää sen

Tässä on yksi tosiasia puheen-tekstiksi-virtauksesta WebSocketin yli, josta on helppo katsoa pois: kun yhteys katkeaa ja yhdistyy uudelleen — tai kun sovellus vaihtaa yhdeltä transkriptiopalvelulta toiselle, Deepgramista ElevenLabsiin — asiakas pudottaa tarkoituksella tuon katkoksen aikana napatut äänipalat. Sen on pakko. Tuoreesti yhdistetty tarjoaja joka saa yhtäkkiä jonon vanhentunutta ääntä tukehtuu, tai mikä pahempaa, lukitsee äänenaktiivisuustunnistimensa väärään asiaan. Joten asiakas heittää nuo palat pois.

Ongelma ei ole pudottamisessa. Ongelma on siinä, mitä pudottaminen ennen tarkoitti: nuo sanat eivät yksinkertaisesti koskaan ilmestyneet transkriptioon, eikä mikään kertonut sinulle että ne puuttuivat. Tallennus näytti täydelliseltä. Ei se ollut.

v1.8.20 hyökkää sitä vastaan kahdella rintamalla, eikä kumpikaan niistä ole "tee niin että uudelleenyhdistämiset eivät koskaan menetä ääntä" — koska et voi, et reaaliajassa. Sen sijaan se tekee menetyksestä palautettavan ja rehellisen.

Palautettava puolisko on kestävä PCM-kopiointi. Pääprosessin nappauskäsittelijässä jokainen napattu pala kirjoitetaan nyt levylle tiedostoon ennen kuin se saavuttaa uudelleenyhdistämisen ja armonajan pudotusportit:

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

Mekanismi on miellyttävän vaatimaton: teeChunkToDisk avaa WriteStream:n, varaa etukäteen 44-tavuisen WAV-otsakkeen, liittää raa'at 16-bittiset mononäytteet niiden saapuessa, ja pysäytyksessä paikkaa otsakkeen lopullisilla kooilla (fs.openSync(..., 'r+'), kirjoittaa 44 tavua siirtymällä 0). Tulos on toistettava WAV täydellisestä äänestä — mukaan lukien osat joita reaaliaikainen transkriptio ei koskaan nähnyt. Kun tallennus päättyy, se lähettää session-audio-saved -tapahtuman tiedostopolun ja tavumäärän kanssa.

Tässä on tärkeää kaksi rehellisyyshuomiota, koska tätä on helppo liioitella. Ensinnäkin: kopiointi ei täytä aukkoa näissä julkaisuissa. Se säilyttää raakaäänen varten myöhemmän sovitusajon — eräajona tapahtuvan uudelleentranskription joka sovittaa tiedoston live-transkriptioon — mikä on koukku jonka nämä julkaisut laskevat, ei ominaisuus jonka ne toimittavat. Jatkocommit jopa annotoi session-audio-saved:n TODO:lla joka osoittaa sovitusongelmiin, jottei kukaan luulisi sitä kuolleeksi koodiksi. Toiseksi: tämä maksaa levyä. Tiimi arvioi noin 86 Mt per 30 minuutin tallennus, joten sama julkaisu lisää StartupCleanupService:n joka karsii tallennetut sessiot jotka ovat vanhempia kuin 7 päivää. Turvaverkko joka kasvaa rajatta on vuoto; siivous on se mikä tekee kopioinnista toimitettavan.

Rehellinen puolisko on vieläkin yksinkertaisempi. Ensimmäisellä uudelleenyhdistämisyrityksellä asiakas kirjoittaa oikean, näkyvän merkinnän transkriptioon:

[reconnecting — audio may be missing]

Tuo yksittäinen rivi on koko julkaisun filosofia pienoiskoossa. Sovellus ei voi taata että se kuuli kaiken, joten se lakkaa vihjaamasta että kuuli. Aukko jonka näet on aukko jonka voit kiertää; aukko jota et näe on bugiraportti joka odottaa tapahtumistaan.

v1.9.0: hiljaisuus jonka optimoit pois oli se signaali

v1.9.0 on viimeinen v1-julkaisu, ja se on kohta jossa backend luovutti joukon live-transkription vastuita asiakkaalle — hiljaisuuden pudottaminen, jokaisen transkriptiorivin liittäminen mikrofoniin tai järjestelmän ääneen paikalliselta äänenaktiivisuuden aikajanalta, uudelleenyhdistämissilmukan omistaminen, tallennusrajojen valvominen. Suurin osa siitä on pohjatyötä version 2 arkkitehtuurille ja on kerrottu muualla. Mutta yksi säie siinä on paras pieni tarina kummassakaan julkaisussa, koska se on korjaus jonka piti kumota vahinko jonka aiheutti optimointi joka oli täysin oikea erikseen tarkasteltuna.

Se alkaa selvästi hyvällä muutoksella: lakkaa lähettämästä hiljaisuutta. Asiakas alkoi suodattaa pois palat joiden vallitseva lähde oli hiljaisuus ennen niiden lähettämistä soketin yli — vähemmän kaistanleveyttä, vähemmän hyödytöntä ääntä backendin käsiteltäväksi, ja se kunnioitti eksplisiittistä backendin asiakassopimusta joka sanoi "älä lähetä hiljaisuutta". Jokainen syy tehdä se oli pätevä.

Ja se rikkoi lausuman viimeistelyn täysin. Commit joka korjaa sen esittää syyketjun epätavallisen suoraan:

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.

Tämä on platoninen muoto vuotavasta abstraktiosta joka puree takaisin. Transkriboijan äänenaktiivisuustunnistimella ei ole erillistä "puhuja on valmis" -syötettä — se päättelee lauseen lopun kuulemalla noin sekunnin hiljaisuutta äänessä. Hiljaisuus ei ollut kohinaa johdolla; se oli viesti. Suodattamalla sen pois kaistanleveyden säästämiseksi asiakas vahingossa lakkasi lähettämästä sitä ainoaa signaalia joka kertoi transkriboijalle että lause oli loppunut. Joten jokainen osittainen transkriptio vain jatkoi kasvamistaan, häntä häipyen ...:ksi joka ei koskaan ratkennut.

Voisit korjata tämän laittamalla hiljaisuuden takaisin — mutta silloin olet kumonnut optimoinnin. Todellinen korjaus on parempi: lähetä hiljaisuuden merkitys lähettämättä hiljaisuutta. Asiakas seuraa nyt paikallisesti kuinka kauan ääni on ollut hiljaa, ja kun se ylittää SILENCE_COMMIT_THRESHOLD_MS = 1000:n — tarkoituksella sovitettu backendin oman VAD:n hiljaisuuskynnykseen — se laukaisee eksplisiittisen commit-signaalin kaistan ulkopuolelta: sendMixedChunk huomaa jatkuvan hiljaisuuden, lähettää realtime-audio:commit:n IPC:n yli, pääprosessi välittää { type: 'commit' }:n soketissa, ja backend käskee ElevenLabsia viimeistelemään osittaisen tuloksen. Kaksi pientä tilapalaa, silenceStartedAt ja silenceCommitFired, estävät sitä laukeamasta toistuvasti. Kaistanleveysvoitto säilyy; viimeistely toimii jälleen; loppu ... ratkeaa.

Tähän julkaisuun liittyy myös pieni, rehellinen alaviite: rajaton uudelleenyhdistämislogiikka jonka v1.9.0 esitteli rakennettiin ensin rendereriin WsReconnectController:na, sitten poistettiin saman julkaisun sisällä kun todellinen toteutus laskeutui pääprosessin käsittelijään johon se kuului. Uudelleenyhdistämistarina itsessään — rajaton perääntyminen, tarjoajia vuorotellen joka viidennen epäonnistumisen jälkeen — on kerrottu omassa luvussaan; mitä v1.9.0 lisää on se, että täällä tuo arkkitehtuuri syntyi ja missä asiakas ensimmäisen kerran otti sen omakseen.

Kolme asiaa jotka nämä julkaisut opettivat

  1. Kun et voi estää menetystä, tee siitä palautettava ja tee siitä näkyvä. v1.8.20 ei estä uudelleenyhdistämisiä pudottamasta ääntä — se ei ole fyysisesti mahdollista reaaliajassa. Se säilyttää raakaäänestä kopion myöhempää varten, ja se merkitsee aukon transkriptioon. Kumpikaan ei ole hohdokas; yhdessä ne muuttavat hiljaisen tiedon menetyksen tunnetuksi, rajatuksi, palautettavaksi tilaksi.
  2. Turvaverkko tarvitsee omat rajansa. Kestävä kopiointi täyttäisi hiljaa levyn jos se jätettäisiin rauhaan — korjaus josta tulee hidastettu bugi. Samassa julkaisussa toimitettu 7 päivän siivous ei ole jälkiajatus; se on se mikä tekee kopioinnista ylipäätään turvallisen toimittaa. Jos lisäät mekanismin joka kasautuu, lisää mekanismi joka karsii sen samalla henkäyksellä.
  3. Asia jonka pudotat tehokkuuden vuoksi voi olla signaali. Terävin opetus kummassakaan julkaisussa: hiljaisuuden suodattaminen pois oli oikein jokaisella akselilla jonka normaalisti tarkistaisit — kaistanleveys, kuorma, sopimuksen noudattaminen — ja se rikkoi silti tuotteen, koska alavirran järjestelmä luki hiljaisuuteen merkityksen. Kun poistat dataa tehokkuuden vuoksi, kysy mikä päätteli jotakin siitä datasta. Joskus joudut lähettämään merkityksen eksplisiittisesti juuri siksi että lakkasit lähettämästä sitä asiaa josta se ennen pääteltiin.

Ja siinä on version 1 tarinan loppu — viimeinen kovennus ennen uudelleenkirjoitusta. Edellistä lukua varten — kolme verbiä, jotka pitävät Web Audion elossa (v1.8.16 + v2.0.2); sitä varten, mihin tämä kaikki oli menossa — mitä versio 2 oikeasti vaatii: 206 committia (v2.0.0); ja koko kaarelle — ohjelmiston toimittamisen anatomia täydellisyyteen asti.

Aiheeseen liittyvät artikkelit

Kolme verbiä, jotka pitävät Web Audion elossa
Steven
Steven7 min lukuaika

Kolme verbiä, jotka pitävät Web Audion elossa

Kaksi GeekBye-julkaisua, kahden kuukauden välein ja kahdessa eri tiedostossa, opettivat ääni-koodillemme saman opetuksen vastakkaisista päistä: lakkaa kohtelemasta selaimen AudioContextia kertakäyttöisenä. Yksi julkaisu oppi kutsumaan resume() kontekstille, jonka macOS oli hiljaa keskeyttänyt kesken tallennuksen; toinen oppi kutsumaan suspend() eikä close(), niin että peräkkäiset istunnot lakkaavat törmäämästä Chromiumin noin-kuuden-kontekstin kattoon. Resume, suspend, close — siinä on koko juoni.

Ohjelmistokehitys
Audio
Desktop
Katkennut yhteys ei saisi kaataa koko sovellusta — mutta meidän kaatoi
Steven
Steven5 min lukuaika

Katkennut yhteys ei saisi kaataa koko sovellusta — mutta meidän kaatoi

Kun taustapalvelimemme katkesi kesken kokouksen, se ei vain keskeyttänyt litterointia — se kaatoi koko sovelluksen. Syynä oli yksi käsittelemätön tapahtuma, ja korjaus oli kymmenen riviä. Tämä on se julkaisurypäs, joka sai GeekByen pysymään kirjautuneena ja yhteydessä niiden asioiden läpi, jotka ennen tappoivat sen.

Ohjelmistokehitys
Luotettavuus
Electron
Päivä, jona sovelluksemme teki DDoS-hyökkäyksen itseään vastaan
Steven
Steven4 min lukuaika

Päivä, jona sovelluksemme teki DDoS-hyökkäyksen itseään vastaan

Odottavien latausten kertymä, joka vapautettiin kaikki kerralla käynnistyksessä, muutti jokaisen GeekBye-asiakkaan pieneksi palvelunestohyökkäykseksi (DoS) omia palvelimiamme vastaan. Korjaus — ja yhteyden elävyystikkaat, jotka se pakotti meidät rakentamaan — on yksi hyödyllisimmistä asioista, jotka v2 meille opetti.

Luotettavuus
Verkot
Ohjelmistokehitys