
Backend'i Yükleme Yolundan Çıkarmak
GeekBye ekranınızı kaydeder ve videoyu Google Drive'ınıza kaydeder. İlk sürüm her kaydı oraya giderken GeekBye'in kendi sunucuları üzerinden geçirdi; bir sürüm sonra, dosya makinenizden doğrudan Drive'a gitti, ve backend tek bir işaretçiyi tutmaya indirgendi. İlginç kısım, 'doğrudan, devam ettirilebilir' sürümün aslında ne kadar az kod içerdiğidir — çünkü devam ettirilebilirlik bir proxy yazmaktan değil, bir proxy'yi silmekten geldi.
GeekBye ekranınızı kaydedebilir — video, sistem sesi ve mikrofon — ve bitmiş kaydı otomatik olarak Google Drive'ınıza bırakabilir. v1.8.11 ile v1.8.13 arasında, özelliğin kullanıcıya dönük açıklaması neredeyse hiç değişmedi, ama videonun Drive'a ulaşmak için izlediği yol tamamen yeniden yönlendirildi. İlk sürümde, kaydınız GeekBye'in sunucuları üzerinden yolculuk etti. İkincisinde, makinenizden doğrudan Drive'ınıza gitti, ve GeekBye'in backend'i onun tek bir baytını bile görmedi. O yeniden yönlendirme bütün hikâyedir, ve tatmin edici kısım şu ki "daha iyi" sürüm, yerini aldığı sürümden göze çarpacak kadar daha az kod içerir.
Baştan sona işlem hattı
Kayıt renderer'da gerçekleşir. ScreenRecordingService.ts, yakalama akışı üzerinde { mimeType: 'video/webm;codecs=vp9,opus', videoBitsPerSecond: 1_000_000 } ile bir MediaRecorder oluşturur ve onu bir saniyelik dilimle (CHUNK_INTERVAL_MS = 1000) başlatır. Her ondataavailable blob'u IPC üzerinden ana sürece gönderilir, orada recording:save-chunk handler'ı onu fs.promises.appendFile ile diskteki bir dosyaya ekler. Erkenden işaretlemeye değer, çünkü yanlış okunması cazip bir şey: o bir saniyelik tempo diske-yazma ritmidir, bir yükleme parça boyutu değil. Yükleme başladığında, diskte tek bir bitmiş WebM dosyası oturuyordur.
İki sürüm arasında farklı olan tek şey, o bitmiş dosyaya ne olduğudur.
Öncesi: her şey backend üzerinden (v1.8.11)
v1.8.11 yükleme yolu, CloudUploader.processRecording() içinde, bariz tasarımdır ve tamamen makul bir ilk sürümdür:
- Kaydın metadatasıyla bir backend kaydı oluşturmak için
POST /api/geekbye/recordings. - Tüm videoyu belleğe oku ve backend'e multipart olarak POST et —
const fileBuffer = fs.readFileSync(filePath)—POST /api/geekbye/recordings/${id}/upload'a vurarak. Backend sonra o dosyayı Drive'a yükler ve birdriveUrldöner. - Transkripti AI analizi için göndermek üzere
POST …/process.
Yük taşıyan detay 2. adımdır: istemci tüm kaydı belleğe yükler ve onun her baytı Drive'a giderken GeekBye'in kendi sunucularından geçer. Kısa bir klip için bu sorun değil. Uzun bir ekran kaydı için aynı anda üç sorundur — readFileSync'ten istemci üzerinde bellek baskısı, saklamadığı bir dosya için backend üzerinde bant genişliği ve aktarma maliyeti, ve ağ yarı yolda takılırsa sıfırdan yeniden başlamak zorunda olan tek bir monolitik POST.
Ayrıca dürüst olmakta fayda var ki bu "öncesi" el değmemiş bir orijinal tasarım değildi. v1.8.11 özelliği aynı hafta içinde bir dönüşü birbirine sıkıştırdı: erken bir kesim önceden imzalanmış URL'ler üzerinden Cloudflare R2'ye yüklüyor ve paketlenmiş bir ffmpeg-static ile WebM'i MP4'e çeviriyordu; bu, backend aracılı Drive akışıyla değiştirildi; ve sonra ffmpeg dönüşümü, WebM'i olduğu gibi yüklemek lehine tamamen söküldü (commit, bitrate'i düşürdüğünü ve kabaca dört kat daha küçük dosyalar ürettiğini belirtiyor — satır içi bir tahmin, bir benchmark değil). Yani "öncesi" bile bir kez bir yerel bağımlılığı silmeyi çoktan öğrenmişti. Bir sonraki sürüm proxy'yi de silecekti.
Sonrası: doğrudan Drive'a (v1.8.13)
v1.8.13, CloudUploader.processRecording()'ı onu gönderen commit'ten tek bir cümle etrafında yeniden yazar: doğrudan Drive'a yükle, "keeping backend for metadata record + AI transcript analysis only." Yeni yol:
POST /api/geekbye/recordings— yalnızca metadata (başlık, süre, dosya boyutu,format: 'webm'). Koddaki yorum açık sözlü:// Create backend record (metadata only — no file upload).- Videoyu doğrudan Google Drive'a yükle, backend aracısını tamamen atlayarak.
- Ortaya çıkan
{ driveUrl, driveFolderId }ilePATCH /api/geekbye/recordings/${backendRecordingId}— backend kaydına dosyanın nerede sonlandığı söylenir. - Transkript hâlâ analiz için
…/process'e gider.
Backend, dosyanın yolunun içinde olmaktan, sonradan dosya hakkında bilgilendirilmeye geçti. Bir satır ve bir işaretçi tutar; baytlar yalnızca kullanıcının diskinde ve kullanıcının Drive'ında yaşar.
Gerçek bir Drive istemcisi olmak
İstemcinin Drive ile doğrudan konuşabilmesi için, gerçek bir Google Drive API istemcisi olması gerekir, ve v1.8.13 bunun için makineyi ekler: yeni bir DriveService (Drive istemcisi), yeni bir drive_auth SQLite tablosuyla desteklenen yeni bir DriveAuthRepository, ve bağımlılık olarak googleapis SDK'sı.
Yetkilendirme temiz bir yetenek devridir. Backend'in app-config endpoint'i Google OAuth istemci kimlik bilgilerini — bir clientId ve clientSecret — uygulamaya aşağı iletir, ki bunu CloudUploader okur ve driveService.initialize(...)'a geçirir. Oradan masaüstü uygulaması tüm OAuth akışını kendisi çalıştırır: rastgele bir 127.0.0.1 portunda geçici bir http sunucusu kurar, sistem tarayıcısında onay ekranını shell.openExternal ile açar, yönlendirmeyi bir /callback'te yakalar, ve kodu google-auth-library ile tokenlerle takas eder. O tokenler yerel olarak kaydedilir — drive_auth'a saveDriveAuth(access_token, refresh_token, expiry_date) — uygulamanın yalnızca oluşturduğu dosyaları yönetmesine izin veren drive.file scope'uyla. Yenileme de yereldir: bir oauth2Client.on('tokens', …) dinleyicisi yenilenen tokenleri doğrudan tabloya geri yazar. Backend yeteneği bir kez devreder ve sonra döngünün dışında kalır.
"Devam ettirilebilir" kısmı, dürüstçe
İşte hakkında en çok net olmak istediğim detay, çünkü tam da bir changelog'un yuvarladığı türden bir şey. Sürüm notu "resumable uploads," der, ve bu kullanıcının durduğu yerden doğrudur. Ama GeekBye devam ettirilebilir protokolü uygulamadı. GeekBye'in kaynağının hiçbir yerinde parça-boyutu sabiti, bayt-offset izleyici, veya Content-Range / 308 Resume Incomplete işlemesi yoktur. Onlar için grep yap, hiçbir şey bulamazsın — onlar googleapis SDK'sının içinde yaşar.
GeekBye'in kendi kodunun yaptığı şey şudur:
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)
},
},
)
İki tercih bütün işi yapar. Önce, media.body bir akıştır (createReadStream), bir tampon değil — ve bir akış geçirmek tam da googleapis'in, tek seferde yapmak yerine, senin adına devam ettirilebilir bir oturum müzakere etmesini sağlayan şeydir. İkinci olarak, ilerleme SDK'nın onUploadProgress callback'inden fileStats.size'a karşı okunur. Uygulama düzeyindeki bütün "resumable upload" uygulaması budur: akış içeri, ilerleme dışarı. Zor protokol — bir oturum aç, parçalar hâlinde yükle, bir kopmadan sonra bir offset'ten devam et — SDK'nın işidir, ve doğru mühendislik kararı onu yeniden icat etmek yerine o işi ona bırakmaktı.
Ki bu bütün sürümü yeniden çerçeveler. Güvenilirlik iyileştirmesi bir yükleme motoru yazmaktan gelmedi. Önünde duran proxy'yi silmekten geldi — ve, aynı hafta daha önce, onun önündeki ffmpeg adımını silmekten. Daha az kod, daha çok dayanıklılık, çünkü dayanıklı olan şey, ona elle bir tampon beslemeyi bıraktığın anda iyi yıpranmış bir kütüphaneydi.
Gerçekten GeekBye'e ait olan parçalar
Aktarımı SDK'ya teslim etmek, inşa edilecek hiçbir şey kalmadığı anlamına gelmez. İki parça gerçek uygulama mantığı onu kuşatır.
Bir yetim-klasör geri alması. Her kayıt kendi Drive alt klasörünü alır (mimeType: 'application/vnd.google-apps.folder' ile oluşturulur), ve transkript oraya videonun yanında ikinci bir dosya olarak iner. Yükleme bir hata fırlatırsa, catch o klasörü siler — this.drive.files.delete({ fileId: folderId }), Rolled back orphaned Drive folder olarak loglanır — böylece başarısız bir yükleme Drive'ında boş klasörlerden bir iz bırakmaz. İşlemin başarılı olacağını bilmeden bir konteyner oluşturmak küçük bir yükümlülüktür; başarısızlık yolunun onu temizlemesini sağlamak çözümdür.
Bir singleton etrafında yaşam döngüsü. DriveService bellek içi bir OAuth istemcisi tutan bir singleton'dır, ve o bellek içi istemci, tokenler öyle yapsa da (veritabanındalar) bir uygulama yeniden başlatmasından sağ çıkmaz. Bu yüzden CloudUploader, DriveService'i saklanan tokenlerden her yüklemeden önce yeniden başlatır, ki bu yeniden başlatmanın hemen ardından yapılan bir kaydın seni yeniden bağlanmaya sormadan hâlâ yüklendiği anlamına gelir. Ve inceleme geçişi drive-connect handler'ını ateşle-ve-unut hâline getirdi, çünkü bloklayan bir bağlanma çağrısı renderer'ın durum yoklamasını takıyordu — bağlanma çağrısı iş parçacığını tutuyorsa UI "connecting…" gösteremiyordu. Bu ikisi — depolamadan-yeniden-başlatma ve bloklamayan bağlanma — doğrudan yolun gerçek uç durumlarıdır, ve dikkat çekici biçimde ikisi de yükleme parçalamasıyla ilgili değildir. Zor kısmı devrettiğinde, elinde kalan buglar yaşam döngüsü buglarıdır.
Bu sürümün bize öğrettiği üç şey
- Sadece çalışıp çalışmadığını değil, baytların nereye aktığını sor. v1.8.11 proxy'si çalışıyordu. Ama her kaydı kendi sunucularının üzerinden yönlendirmek, saklamadığın bir dosya için sana bant genişliği, bellek ve yeniden başlatılabilirlik maliyeti çıkarır. İstemcinin hedefle doğrudan konuşması için veri yolunu yeniden çizmek çoğu zaman bütün optimizasyondur.
- En iyi devam ettirilebilir yükleme, yazmadığındır. Olgun bir SDK'ya bir akış geçirmek, iki satır kod karşılığında tam bir devam ettirilebilir oturum satın aldı. Parçalamayı ve offset matematiğini elle yapma içgüdüsü daha çok kod ve daha az güvenilirlik üretirdi.
- Çekirdeği devrettiğinde, algoritmaları değil, yaşam döngüsünü miras alırsın. Doğrudan istemcinin gerçek bugları "yeniden başlatmadan sonra singleton'ı yeniden başlat" ve "bağlanma çağrısını bloklama"ydı, bayt offset'leri değil. Bu iyi bir takas — yaşam döngüsü bugları görünür ve yereldir; protokol bugları hiçbiri değildir.
v1 hikâyesinin bir önceki bölümü için, on iki dakikada doğdu ve düzeltildi (v1.8.10); ve tüm yay için, yazılımı kusursuzluğa kadar sevk etmenin anatomisi.