
Backendin poistaminen latauspolusta
GeekBye tallentaa näyttösi ja tallentaa videon Google Driveesi. Ensimmäinen versio toimitti jokaisen tallenteen GeekByen omien palvelimien kautta matkalla sinne; yksi julkaisu myöhemmin tiedosto meni suoraan koneeltasi Driveen, ja backend alennettiin yhden osoittimen pitämiseen. Kiinnostava osa on se, miten vähän koodia 'suora, jatkettava' versio oikeasti sisältää — koska jatkettavuus tuli välityspalvelimen poistamisesta, ei sen kirjoittamisesta.
GeekBye osaa tallentaa näyttösi — video, järjestelmän ääni ja mikrofoni — ja pudottaa valmiin tallenteen automaattisesti Google Driveesi. v1.8.11:n ja v1.8.13:n välillä ominaisuuden käyttäjälle näkyvä kuvaus tuskin muuttui, mutta polku jonka video kulkee Driveen ohjattiin täysin uudelleen. Ensimmäisessä versiossa tallenteesi matkasi GeekByen palvelimien läpi. Toisessa se meni suoraan koneeltasi Driveesi, eikä GeekByen backend nähnyt siitä ainuttakaan tavua. Tuo uudelleenohjaus on koko tarina, ja tyydyttävää on se, että "parempi" versio sisältää silmiinpistävän vähemmän koodia kuin se jonka se korvasi.
Putki, päästä päähän
Tallennus tapahtuu renderöijässä. ScreenRecordingService.ts luo MediaRecorder:n kaappausvirran päälle { mimeType: 'video/webm;codecs=vp9,opus', videoBitsPerSecond: 1_000_000 }:lla ja käynnistää sen yhden sekunnin aikaviipaleella (CHUNK_INTERVAL_MS = 1000). Jokainen ondataavailable-blob lähetetään IPC:n yli pääprosessiin, jossa recording:save-chunk-käsittelijä liittää sen levyllä olevaan tiedostoon fs.promises.appendFile:lla. Kannattaa merkitä varhain, koska tätä on houkuttelevaa lukea väärin: tuo yhden sekunnin tahti on levylle-kirjoituksen rytmi, ei latauksen palakoko. Siihen mennessä kun lataus alkaa, levyllä on yksi valmis WebM-tiedosto.
Se mikä eroaa kahden julkaisun välillä on vain se mitä tuolle valmiille tiedostolle tapahtuu.
Ennen: kaikki backendin läpi (v1.8.11)
v1.8.11:n latauspolku tiedostossa CloudUploader.processRecording() on ilmeinen suunnittelu ja täysin järkevä ensimmäinen versio:
POST /api/geekbye/recordingsluodaksesi backend-tietueen tallenteen metadatalla.- Lue koko video muistiin ja POST se backendiin multipart-muodossa —
const fileBuffer = fs.readFileSync(filePath)— osuenPOST /api/geekbye/recordings/${id}/upload:iin. Backend sitten lataa tuon tiedoston Driveen ja palauttaadriveUrl:n. POST …/processlähettääksesi transkription AI-analyysiin.
Kantava yksityiskohta on vaihe 2: asiakas lataa koko tallenteen muistiin, ja jokainen sen tavu kulkee GeekByen omien palvelimien läpi matkalla Driveen. Lyhyelle klipille se on ihan hyvä. Pitkälle näyttötallenteelle se on kolme ongelmaa kerralla — muistipaine asiakkaalle readFileSync:stä, kaistanleveys- ja välityskustannus backendille tiedostosta jota se ei edes säilytä, ja yksi monoliittinen POST jonka on käynnistyttävä nollasta jos verkko nikottelee puolivälissä.
Kannattaa myös olla rehellinen siitä että tämä "ennen" ei ollut mikään koskematon alkuperäinen suunnittelu. v1.8.11:n ominaisuus litisti yhteen saman viikon sisäisen käännöksen: varhainen versio latasi Cloudflare R2:een esiallekirjoitettujen URL:ien kautta ja muunsi WebM:n MP4:ksi niputetulla ffmpeg-static:lla; se korvattiin backend-välitteisellä Drive-kululla; ja sitten ffmpeg-muunnos revittiin kokonaan pois WebM:n lataamisen hyväksi sellaisenaan (commit huomauttaa että se laski bittinopeutta ja tuotti noin nelinkertaisesti pienempiä tiedostoja — sisäinen arvio, ei vertailutesti). Joten jopa "ennen" oli jo oppinut poistamaan natiiviriippuvuuden kerran. Seuraava julkaisu poistaisi myös välityspalvelimen.
Jälkeen: suoraan Driveen (v1.8.13)
v1.8.13 kirjoittaa CloudUploader.processRecording():n uudelleen yhden virkkeen ympärille sen commitista joka sen toimitti: lataa suoraan Driveen, "keeping backend for metadata record + AI transcript analysis only." Uusi polku:
POST /api/geekbye/recordings— vain metadata (otsikko, kesto, tiedostokoko,format: 'webm'). Kommentti koodissa on tyly:// Create backend record (metadata only — no file upload).- Lataa video suoraan Google Driveen, ohittaen backend-välikäden kokonaan.
PATCH /api/geekbye/recordings/${backendRecordingId}syntyneellä{ driveUrl, driveFolderId }:llä — backend-tietueelle kerrotaan minne tiedosto päätyi.- Transkriptio menee yhä
…/process:iin analyysia varten.
Backend siirtyi olemasta tiedoston polulla siihen että sille kerrotaan tiedostosta jälkikäteen. Se pitää rivin ja osoittimen; tavut asuvat vain käyttäjän levyllä ja käyttäjän Drivessä.
Oikeaksi Drive-asiakkaaksi tuleminen
Jotta asiakas voisi puhua Driven kanssa suoraan, sen on oltava aito Google Drive API -asiakas, ja v1.8.13 lisää siihen koneiston: uuden DriveService:n (Drive-asiakas), uuden DriveAuthRepository:n jota tukee uusi drive_auth SQLite-taulu, ja googleapis-SDK:n riippuvuutena.
Valtuutus on siisti kyvykkyyden luovutus. Backendin sovelluskonfiguraatio-päätepiste välittää Google OAuth -asiakastunnukset — clientId:n ja clientSecret:n — alas sovellukseen, jotka CloudUploader lukee ja välittää driveService.initialize(...):lle. Siitä työpöytäsovellus ajaa koko OAuth-kulun itse: se pystyttää väliaikaisen http-palvelimen satunnaiseen 127.0.0.1-porttiin, avaa suostumusnäytön järjestelmäselaimessa shell.openExternal:lla, nappaa uudelleenohjauksen /callback:ssa ja vaihtaa koodin tokeneihin google-auth-library:lla. Nuo tokenit tallennetaan paikallisesti — saveDriveAuth(access_token, refresh_token, expiry_date) drive_auth:iin — drive.file-scopella, joka antaa sovelluksen hallita vain luomiaan tiedostoja. Päivitys on myös paikallinen: oauth2Client.on('tokens', …)-kuuntelija kirjoittaa päivitetyt tokenit suoraan takaisin tauluun. Backend luovuttaa kyvykkyyden kerran ja pysyy sitten silmukan ulkopuolella.
"Jatkettava"-osa, rehellisesti
Tässä on yksityiskohta josta haluan eniten olla suora, koska se on juuri sellainen asia jonka muutosloki pyöristää. Julkaisumuistiinpano sanoo "resumable uploads", ja siitä mihin käyttäjä seisoo se on totta. Mutta GeekBye ei toteuttanut jatkettavaa protokollaa. GeekByen lähdekoodissa ei ole missään palakoko-vakiota, tavusiirtymä-seurainta eikä Content-Range / 308 Resume Incomplete -käsittelyä. Grep niitä etkä saa mitään — ne asuvat googleapis-SDK:n sisällä.
Se mitä GeekByen oma koodi tekee on tämä:
const fileStats = statSync(videoFilePath)
const videoStream = createReadStream(videoFilePath)
const videoRes = await this.drive.files.create(
{
requestBody: { name: `${title}.webm`, parents: [folderId] },
media: { mimeType: 'video/webm', body: videoStream },
fields: 'id',
},
{
onUploadProgress: (evt) => {
const percent = Math.round((evt.bytesRead / fileStats.size) * 100)
onProgress(percent)
},
},
)
Kaksi valintaa tekee koko työn. Ensiksi, media.body on virta (createReadStream), ei puskuri — ja virran välittäminen on juuri se mikä saa googleapis:n neuvottelemaan jatkettavan istunnon puolestasi sen sijaan että se tekisi yhden laukauksen. Toiseksi, edistyminen luetaan SDK:n onUploadProgress-takaisinkutsusta fileStats.size:ä vasten. Siinä on koko "jatkettavan latauksen" toteutus sovellustasolla: virta sisään, edistyminen ulos. Vaikea protokolla — avaa istunto, lataa paloissa, jatka siirtymästä katkoksen jälkeen — on SDK:n työtä, ja oikea insinööripäätös oli antaa sen tehdä tuo työ sen sijaan että sen keksisi uudelleen.
Mikä kehystää koko julkaisun uudelleen. Luotettavuusparannus ei tullut latausmoottorin kirjoittamisesta. Se tuli välityspalvelimen poistamisesta joka istui sen edessä — ja aiemmin samalla viikolla ffmpeg-vaiheen poistamisesta sen edessä. Vähemmän koodia, enemmän kestävyyttä, koska kestävä asia oli hyvin kulunut kirjasto sillä hetkellä kun lopetit sen käsin syöttämisen puskurilla.
Palat jotka ovat aidosti GeekByen
Siirron luovuttaminen SDK:lle ei tarkoita ettei jäänyt mitään rakennettavaa. Kaksi palaa aitoa sovelluslogiikkaa ympäröi sitä.
Orpokansion palautus. Jokainen tallenne saa oman Drive-alikansionsa (luotu mimeType: 'application/vnd.google-apps.folder':lla), ja transkriptio laskeutuu sinne toisena tiedostona videon viereen. Jos lataus heittää, catch poistaa tuon kansion — this.drive.files.delete({ fileId: folderId }), lokitettuna nimellä Rolled back orphaned Drive folder — niin ettei epäonnistunut lataus jätä tyhjien kansioiden jälkeä Driveesi. Säiliön luominen ennen kuin tiedät operaation onnistuvan on pieni vastuu; epäonnistumispolun laittaminen siivoamaan se on korjaus.
Elinkaari singletonin ympärillä. DriveService on singleton joka pitää muistinvaraista OAuth-asiakasta, eikä tuo muistinvarainen asiakas selviä sovelluksen uudelleenkäynnistyksestä vaikka tokenit selviävät (ne ovat tietokannassa). Joten CloudUploader alustaa DriveService:n tallennetuista tokeneista uudelleen ennen jokaista latausta, mikä tarkoittaa että heti uudelleenkäynnistyksen jälkeen tehty tallenne latautuu yhä pyytämättä sinua yhdistämään uudelleen. Ja katselmointikierros muutti drive-connect-käsittelijän tulita-ja-unohda-tyyppiseksi, koska estävä yhdistämiskutsu jumitti renderöijän tilan pollausta — UI ei voinut näyttää "yhdistetään…" jos yhdistämiskutsu piti säiettä. Nämä kaksi — uudelleenalustus tallennuksesta ja estämätön yhdistäminen — ovat suoran polun todelliset reunatapaukset, eikä kumpikaan niistä huomionarvoisesti koske latauksen palottelua. Kun delegoit vaikean osan, jäljelle jäävät bugit ovat elinkaaribugeja.
Kolme asiaa jotka tämä julkaisu opetti meille
- Kysy mihin tavut virtaavat, älä vain toimiiko se. v1.8.11:n välityspalvelin toimi. Mutta jokaisen tallenteen reitittäminen omien palvelimiesi läpi maksaa sinulle kaistanleveyttä, muistia ja jatkettavuutta tiedostosta jota et säilytä. Datapolun uudelleenpiirtäminen niin että asiakas puhuu määränpään kanssa suoraan on usein koko optimointi.
- Paras jatkettava lataus on se jota et kirjoittanut. Virran välittäminen kypsälle SDK:lle osti täyden jatkettavan istunnon kahdella koodirivillä. Vaisto tehdä palottelu ja siirtymämatematiikka käsin olisi tuottanut enemmän koodia ja vähemmän luotettavuutta.
- Kun delegoit ytimen, perit elinkaaren, et algoritmeja. Suoran asiakkaan todelliset bugit olivat "alusta singleton uudelleen käynnistyksen jälkeen" ja "älä estä yhdistämiskutsua", ei tavusiirtymiä. Se on hyvä vaihtokauppa — elinkaaribugit ovat näkyviä ja paikallisia; protokollabugit eivät kumpaakaan.
Edellinen luku v1-tarinassa: syntyi ja korjattiin kahdessatoista minuutissa (v1.8.10); ja koko kaarelle, ohjelmiston toimittamisen anatomia täydellisyyteen asti.