- 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 yüzden üçüncü taraflarla iş birliği yapmak zorunda olan servislerin, ilk çıkıştan itibaren cross-origin isolation’ı etkinleştirerek yayına girmesi gerekiyor...
Aa, cometkim, tanıştığımıza sevindim :)
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)
Muhtemelen öyledir. Enda da yerel dosya yazmayı yalnızca Chrome ve Edge'de destekliyor gibi görünüyor.
Bunu eskiden eski bir Linux dizüstü bilgisayarda yaşamıştım; gizli modda açınca düzeliyordu.