24 puan yazan xguru 2024-07-19 | 6 yorum | WhatsApp'ta paylaş
  • 3 yıl önce Notion, istemcide verileri önbelleğe almak için SQLite veritabanı kullanarak Mac ve Windows için Notion uygulamasının hızını başarıyla artırdı
  • Bu kez aynı iyileştirmeyi tarayıcı üzerinden Notion'a erişen kullanıcılara da sunabildi
  • Bu yazı, WebAssembly(WASM) tabanlı sqlite3 uygulamasını kullanarak tarayıcıda Notion performansını nasıl iyileştirdiklerinin derinlemesine bir analizini içeriyor
  • SQLite kullanıldığında tüm modern tarayıcılarda sayfa gezinme süresi %20 iyileşti
    • Özellikle internet bağlantısı gibi dış etkenler nedeniyle API yanıt süresi çok yavaş olan kullanıcılar için fark daha da belirgindi
    • Örneğin Avustralya'daki kullanıcılar için sayfa gezinme süresi %28, Çin'deki kullanıcılar için %31 ve Hindistan'daki kullanıcılar için %33 daha hızlıydı

Temel teknolojiler: OPFS ve Web Workers

  • WASM SQLite kütüphanesi, verileri oturumlar arasında korumak için Origin Private File System(OPFS) adlı modern bir tarayıcı API'si kullanıyor
  • OPFS yalnızca Web Workers içinde kullanılabiliyor
  • Web Worker, tarayıcıda JavaScript'in büyük bölümünün çalıştığı ana iş parçacığından farklı, ayrı bir iş parçacığında çalışan kod olarak düşünülebilir
  • Notion, Webpack ile birlikte paketleniyor ve Web Worker yüklemek için kullanımı kolay bir sözdizimi sağlıyor
  • OPFS kullanarak SQLite veritabanı dosyasını oluşturacak veya mevcut dosyayı yükleyecek bir Web Worker kurdular ve mevcut önbellekleme kodunu bu Web Worker üzerinde çalıştırdılar
  • Ana iş parçacığı ile Worker arasında mesaj iletimini kolayca yönetmek için Comlink kütüphanesi kullanıldı

SharedWorker tabanlı yaklaşım

  • Nihai mimari, Roy Hashimoto'nun GitHub tartışmasında sunduğu yeni çözüm temel alınarak kuruldu
    • Aynı anda yalnızca bir sekmenin SQLite'a erişmesine izin verirken diğer sekmelerin de SQLite sorguları çalıştırabilmesini sağlayan bir yaklaşım
  • Bu yeni mimari nasıl çalışıyor?
    • Kısaca, her sekmede SQLite'a yazabilen özel bir Web Worker bulunuyor
    • Ancak fiilen Web Worker'ı kullanabilen yalnızca tek bir sekme var
    • SharedWorker, "aktif sekmenin" hangisi olduğunu yönetmekten sorumlu
    • Aktif sekme kapanırsa SharedWorker yeni bir aktif sekme seçmesi gerektiğini biliyor
  • SQLite sorgusu çalıştırmak için her sekmenin ana iş parçacığı ilgili sorguyu SharedWorker'a gönderiyor, SharedWorker da bunu aktif sekmenin özel Worker'ına yönlendiriyor
  • Sekmeler istedikleri kadar eşzamanlı SQLite sorgusu çalıştırabiliyor ve bunların tümü her zaman tek bir aktif sekmeye yönlendiriliyor
  • Her Web Worker, tüm büyük tarayıcılarda çalışan OPFS SyncAccessHandle Pool VFS uygulamasını kullanarak SQLite veritabanına erişiyor

Neden daha basit yaklaşım işe yaramadı

  • Yukarıda açıklanan mimariyi kurmadan önce, her sekmeye özel bir Web Worker verip her Web Worker'ın SQLite veritabanına yazdığı daha basit bir yöntemle WASM SQLite'ı çalıştırmayı denediler
  • Ancak bunların hiçbiri Notion'ın gereksinimlerini doğrudan karşılayacak kadar yeterli değildi

Engel #1: cross-origin isolation

  • OPFS via sqlite3_vfs kullanabilmek için sitenin "cross-origin isolated" durumda olması gerekiyor
  • Sayfaya cross-origin isolation eklemek için, yüklenebilecek script'leri kısıtlayan bazı güvenlik başlıklarının ayarlanması gerekiyor
  • Bu başlıkları ayarlamak oldukça büyük bir iş olabiliyor
  • Notion, web altyapısındaki çeşitli özellikleri çalıştırmak için çok sayıda üçüncü taraf script'e bağımlı olduğundan, tam cross-origin isolation elde etmek için her sağlayıcıdan yeni başlıklar ayarlamasını ve iframe'lerin çalışma biçimini değiştirmesini istemek zorunda kalacaktı; bu da pratikte zor bir gereksinimdi
  • Testlerde, Chrome ve Edge tarayıcılarında kullanılabilen SharedArrayBuffer için Origin Trials kullanılarak bu varyantı kullanıcıların bir alt kümesine sunup önemli performans verileri elde edebildiler
  • Bu Origin Trials sayesinde cross-origin isolation gereksinimini geçici olarak aşabildiler

Engel #2: bozulma sorunu

  • OPFS via sqlite3_vfs küçük bir kullanıcı grubuna sunulduğunda, bazı kullanıcılarda ciddi hatalar görülmeye başlandı
    • Bu kullanıcılar sayfada yanlış veriler görüyordu
    • Örneğin yanlış iş arkadaşına atanmış yorumlar ya da önizlemesi tamamen başka bir sayfa olan yeni sayfa bağlantıları gibi
  • Bu hatadan etkilenen kullanıcıların veritabanı dosyalarına bakıldığında, SQLite veritabanının bir şekilde bozulduğu bir örüntü görüldü
    • Belirli tablolardaki satırlar seçildiğinde hata oluşuyor ve satırların kendisi incelendiğinde aynı ID'ye sahip birden fazla satırda farklı içerikler bulunması gibi veri tutarlılığı sorunları görülüyordu
  • SQLite veritabanının bu duruma nasıl geldiğine dair tahminleri, bunun eşzamanlılık sorunlarından kaynaklandığı yönündeydi
    • Çünkü birden fazla sekme açıktı ve her sekmede SQLite veritabanına aktif bağlantısı olan özel bir Web Worker vardı
    • Notion uygulaması sunucudan her güncelleme aldığında sık sık önbelleğe yazıyor, yani sekmeler aynı dosyaya aynı anda yazabiliyordu
  • Zaten SQLite sorgularını birlikte toplu işleyen transaction tabanlı bir yaklaşım kullanıyorlardı, ancak bozulmanın OPFS API tarafındaki yetersiz eşzamanlılık işleme nedeniyle oluştuğundan kuvvetle şüphelendiler
  • Bu yüzden bozulma hatalarını loglamaya başladılar ve Web Locks eklemek, yalnızca odaktaki sekmenin SQLite'a yazmasına izin vermek gibi bazı geçici çözümler denediler
    • Bu ayarlamalar bozulma oranını düşürdü, ancak özelliği production trafiğinde yeniden açacak kadar yeterli olmadı
    • Yine de eşzamanlılık sorununun bozulmaya önemli ölçüde katkıda bulunduğunu doğrulayabildiler
  • Notion'ın masaüstü uygulamasında bu sorun görülmedi
    • Bu platformda SQLite'a yalnızca tek bir ebeveyn süreç yazıyordu
    • Uygulamada istediğiniz kadar sekme açabiliyorsunuz ama veritabanı dosyasına her zaman yalnızca tek bir iş parçacığı erişiyordu

Engel #3: alternatif aynı anda yalnızca tek bir sekmede çalışabiliyor

  • OPFS SyncAccessHandle Pool VFS varyantı da değerlendirildi
    • Bu varyant SharedArrayBuffer gerektirmediği için Safari, Firefox ve SharedArrayBuffer için Origin Trial bulunmayan diğer tarayıcılarda da kullanılabiliyor
  • Bu varyantın dezavantajı, aynı anda yalnızca tek bir sekmede çalışabilmesi
    • Sonraki bir sekmede SQLite veritabanını açmaya çalışırsanız doğrudan hata alınıyor
  • Bir yandan bu durum, OPFS SyncAccessHandle Pool VFS'nin OPFS via sqlite3_vfs varyantındaki eşzamanlılık sorunlarına sahip olmadığı anlamına geliyor
    • Bunu, küçük bir kullanıcı grubuna sunduklarında bozulma sorunu gözlemlememeleriyle doğruladılar
  • Diğer yandan, tüm kullanıcı sekmelerinin önbelleklemeden faydalanmasını istedikleri için bu varyantı olduğu gibi yayınlayamadılar

Sorunun çözümü

  • Hiçbir varyantın doğrudan kullanılamıyor olması, yukarıda açıklanan SharedWorker mimarisini kurmalarına yol açtı
  • Bu mimari, bu SQLite varyantlarından biriyle uyumlu
  • OPFS via sqlite3_vfs varyantı kullanıldığında, aynı anda yalnızca bir sekme yazdığı için bozulma sorunu önlenebiliyor
  • OPFS SyncAccessHandle Pool VFS varyantı kullanıldığında ise SharedWorker sayesinde tüm sekmelerde önbellekleme mümkün oluyor
  • Bu mimarinin her iki varyantta da çalıştığını, metriklerde performans artışının belirgin olduğunu ve bozulma sorunu yaşanmadığını doğruladıktan sonra, hangi varyantı sunacaklarına dair son kararı verme zamanı geldi
  • Chrome ve Edge dışındaki tarayıcılara açılmasını engelleyen bir cross-origin isolation gereksinimi olmadığı için OPFS SyncAccessHandle Pool VFS'yi seçtiler

Performans gerilemelerini azaltma

  • Bu iyileştirmeyi kullanıcılara sunmaya başladıklarında, yükleme sürelerinin yavaşlaması gibi düzeltilmesi gereken bazı performans gerilemeleri keşfettiler

Sayfa yüklemesi yavaşladı

  • İlk bulgu, Notion sayfaları arasında geçişin hızlanmasına rağmen ilk sayfa yüklemesinin yavaşlamasıydı
    • Profiling sonucunda, sayfa yüklemesinde darboğazın genellikle veri getirme aşamasında olmadığını fark ettiler
    • Notion'ın uygulama başlatma kodu, API çağrısının tamamlanmasını beklerken başka işler de yaptığı için (JS parse etme, uygulama kurulumu vb.) SQLite önbelleklemesinden gezinme kadar fazla fayda görmüyordu
  • Yavaşlamanın nedeni, kullanıcının WASM SQLite kütüphanesini indirip işlemesi gerekmesiydi
    • Bu, sayfa yükleme sürecini bloke ederek diğer yükleme işlerinin aynı anda gerçekleşmesini engelliyordu
    • Bu kütüphane birkaç yüz kilobayt boyutunda olduğu için ek süre metriklerde belirgin şekilde görünüyordu
  • Bunu çözmek için kütüphaneyi yükleme biçimlerini biraz değiştirdiler
    • WASM SQLite'ı tamamen asenkron yükleyip sayfa yüklemesini bloke etmemesini sağladılar
    • Bu da ilk sayfa verisinin neredeyse hiç SQLite'tan yüklenmeyeceği anlamına geliyordu
    • Bunun sorun olmadığını düşündüler; çünkü SQLite'tan ilk sayfayı yüklemenin sağladığı hız artışının, kütüphane indirme nedeniyle oluşan yavaşlamadan nesnel olarak daha büyük olmadığını değerlendirmişlerdi
  • Değişiklikler uygulandıktan sonra, ilk sayfa yükleme metrikleri deneyin test grubu ile kontrol grubu arasında aynı hale geldi

Yavaş cihazlar önbelleklemeden fayda görmüyor

  • Metriklerde fark ettikleri başka bir durum da, Notion'da bir sayfadan diğerine geçişin medyan süresi hızlanırken 95. yüzdelik dilim süresinin yavaşlamasıydı
    • Notion açık tarayıcıya sahip cep telefonları gibi bazı cihazlar önbelleklemeden fayda görmüyor, hatta daha kötü sonuç veriyordu
  • Bu bilmeceye yanıtı, mobil ekibin daha önce yürüttüğü araştırmada buldular
    • Bu önbellekleme yerel mobil uygulamada uygulanırken, eski Android telefonlar gibi bazı cihazların diskten çok yavaş okuduğu görülmüştü
    • Dolayısıyla veriyi disk önbelleğinden yüklemenin, aynı veriyi API'den yüklemekten her zaman daha hızlı olacağını varsayamazsınız
  • Mobil araştırmanın sonucunda, sayfa yüklemesinde zaten iki asenkron isteği (SQLite ve API) birbiriyle "yarıştıran" bir mantık bulunduğu ortaya çıktı
    • Gezinme tıklamaları için kod yolunda bu mantığı yeniden uyguladılar
    • Bu da deneyin iki grubu arasında gezinme süresinin 95. yüzdelik dilimini eşitledi

Sonuç

  • Notion'ın tarayıcı sürümüne SQLite'ın performans iyileştirmelerini getirmek kendine özgü zorluklar barındırıyordu
  • Özellikle yeni teknolojilerle ilgili olarak bir dizi bilinmeyenle karşılaştılar ve bu süreçte bazı dersler çıkardılar:
    • OPFS, varsayılan olarak eşzamanlılığı zarif biçimde ele almıyor. Geliştiricilerin bunun farkında olması ve buna göre tasarım yapması gerekiyor
    • Web Workers ve SharedWorkers (ve bu yazıda değinilmeyen kuzenleri Service Workers) farklı yeteneklere sahip ve gerektiğinde bunları birleştirmek faydalı olabiliyor
    • 2024 ilkbaharı itibarıyla gelişmiş web uygulamalarında tam cross-origin isolation uygulamak kolay değil. Özellikle üçüncü taraf script'ler kullanılıyorsa daha da zor
  • Kullanıcılar için verileri tarayıcıda SQLite ile önbelleğe almanın sonucu olarak, daha önce bahsedilen %20'lik gezinme süresi iyileşmesini gördüler ve diğer metriklerde bozulma gözlemlemediler
    • Önemlisi, SQLite bozulmasından kaynaklanan herhangi bir sorun gözlemlenmedi
    • Bu nihai yaklaşımın başarısı ve kararlılığında, SQLite'ın resmi WASM uygulamasından sorumlu ekip ile deneysel yaklaşımı kamuya açan Roy Hashimoto'nun büyük payı olduğunu düşünüyorlar

6 yorum

 
[Bu yorum gizlendi.]
 
cometkim 2024-07-19

Bu yüzden üçüncü taraflarla iş birliği yapmak zorunda olan servislerin, ilk çıkıştan itibaren cross-origin isolation’ı etkinleştirerek yayına girmesi gerekiyor...

 
freedomzero 2024-07-20

Aa, cometkim, tanıştığımıza sevindim :)

 
sixmen 2024-07-19

Bende Firefox’ta Notion sayfasını açınca kilitleniyor ve kullanamıyorum; acaba sebebi bu mu?.. (Notion uygulaması sorunsuz çalıştığı için şimdilik onu kullanıyorum)

 
hellworld 2024-07-20

Muhtemelen öyledir. Enda da yerel dosya yazmayı yalnızca Chrome ve Edge'de destekliyor gibi görünüyor.

 
freedomzero 2024-07-20

Bunu eskiden eski bir Linux dizüstü bilgisayarda yaşamıştım; gizli modda açınca düzeliyordu.