- Linear, sorun yönetimi işlerini tarayıcı içi veritabanı ve local-first senkronizasyonla işleyen, böylece sorun güncellemelerini birkaç milisaniye içinde UI'a yansıtan bir üretkenlik aracı
- UI'ın okuduğu gerçek veritabanı IndexedDB içinde bulunur; değişiklikler önce lokale uygulanır, ardından sunucuya eşzamansız gönderilir ve delta'lar WebSocket ile yeniden dağıtılır
- İlk yükleme, az miktarda JavaScript ve CSS aktarımı, agresif kod bölme, modulepreload, servis worker precache ve satır içi app shell ile ağ bekleme süresini azaltan bir yükleme stratejisi kullanır
- Senkronizasyon motoru, IndexedDB verisini MobX nesne havuzuna hydrate eder, değişiklikleri transaction kuyruğunda saklar ve alan düzeyinde gözlemlenebilir durumla yalnızca gereken hücreleri yeniden render eder
- Hızlı hissedilen performans, klavye odaklı giriş, küresel komut paleti, GPU dostu animasyonlar ve kısa geçiş sürelerinin birleştiği bir sistem tasarımı sonucudur
Tarayıcı içindeki veritabanı
- Geleneksel CRUD web uygulamaları, kullanıcının tıklamasından sonra HTTP isteği, sunucuda veritabanı sorgusu, yanıtın alınması ve tarayıcının yeniden boyaması aşamalarından geçer; bu sırada yüzlerce milisaniye boyunca spinner, skeleton ve donmuş UI ortaya çıkar
- Linear, UI'ın okuduğu gerçek veritabanını tarayıcının IndexedDB'sinde tutar; değişiklikleri önce lokale uygular, ardından sunucuya eşzamansız gönderir ve sunucu delta'ları WebSocket ile diğer istemcilere yayınlar
- Hızlı web uygulamalarındaki en büyük darboğaz ağdır; istemci ile sunucu arasındaki veri aktarımı yüzlerce milisaniyelik bir maliyet yaratır
- Linear'in temel akışı, ağ isteklerini kullanıcı için görünmez hale getirmek ve mümkün olduğunca yükleme durumlarını ortadan kaldırmak üzerine kuruludur
// Linear
issue.title = "Faster app launch";
issue.save();
issue.title = "Faster app launch", bellek içi veri deposunu günceller; Linear bu noktada MobX observable kullanır
issue.save();, senkronizasyon motorunun toplu işleyip sunucuya flush edeceği bir transaction'ı kuyruğa ekler
- UI, yerel bellek değişikliklerine göre eşzamanlı olarak yeniden render edilir; veri senkronizasyonu arka planda sürdüğü için spinner gerekmez
- Tuomas, 2024'teki bir konferansta Linear'da ilk yazdığı kodun senkronizasyon motoru olduğunu söyledi ve bunun bir startup için alışılmadık bir yaklaşım olduğunu belirtti
- Çoğu uygulamanın, Linear gibi kendi senkronizasyon motorunu yapmasına gerek yoktur; yalnızca TanStack Query ve SWR'nin optimistic update yaklaşımıyla bile buna oldukça yakın bir hissedilen hız elde edilebilir
- Optimistic request'ler, gereksiz spinner'ları kaldırma, durumu anında güncelleme, arka planda doğrulama ve gerekirse rollback yapma yoluyla büyük iyileşme sağlar
- UI tepkiselliği ağ gecikmesine bağlı olmamalıdır; kullanıcının algıladığı hız, sunucu yanıt süresinden çok arayüzün tepki hızınca belirlenir
-
Linear'in stack'ine kısa bir bakış
- Linear, React, TypeScript, MobX, Postgres ve CDN gibi sade bir stack üzerine kuruludur
- Frontend tarafında React ve
react-dom, MobX, TypeScript, Rolldown-Vite ve plugin-react-oxc, ProseMirror ve y-prosemirror, Radix UI primitives, Emotion ve StyleX, Comlink, idb, graphql-request, Sentry, Inter Variable kullanılır
- Backend tarafında Node.js ve TypeScript, Cloud SQL üzerinde PostgreSQL, Memorystore Redis, turbopuffer, GCP Kubernetes ve Cloudflare Workers kullanılır
- Masaüstü istemcisi Electron tabanlıdır; mobil taraf ise iOS için Swift ve Kotlin ile ayrıdan baştan uygulanmıştır
- Pazarlama sitesi Next.js, styled-components ve satır içi SVG sprite kullanır
- Linear, client-side rendering yaklaşımını korur ve doğru mimari ile tasarım olduğunda CSR'nin de anlık hissedilebileceğini gösteren bir örnektir
- Uygulamanın tamamını client-side tutmak, sunucu-istemci ayrımı,
window erişiminin mümkün olup olmaması ve cache header ayarı gibi karmaşıklıkları azaltan daha basit bir zihinsel model sağlar
İlk yüklemeyi anında hissettirmek
- Üretkenlik araçlarında gerçek işe başlamaya kadar geçen süre önemli bir ayrıntıdır
- İstemci tarafı uygulamalarında ilk yükleme,
index.html isteği, JavaScript ve CSS istekleri, kimlik doğrulama işlemi ve uygulamayı göstermek için API istekleri sırası nedeniyle yavaşlayabilir
-
Linear'ın bundler akışı: Parcel, Rollup, Vite, Rolldown
- Anında hissedilen hız, çalışma zamanından önce yani build aşamasında başlar; hızlı yükleme için gönderilen JavaScript ve CSS miktarını azaltmak önemlidir
- Linear, build pipeline'ını Parcel → Rollup → Vite → Rolldown sırasıyla yeniden yazdı; her geçişin hedefi JavaScript·CSS miktarını azaltmak ve geliştirici deneyimini iyileştirmekti
- Linear bloguna göre iyileştirme rakamları
- Aktarılan kodda %50 azalma
- Sıkıştırma sonrası boyutta %30 azalma
- Cold cache sayfa yüklemesinde %10~30 iyileşme
- Safari'de active-issues görünümünün Time-to-first-paint süresinde %59 azalma
- Bellek kullanımında %70~80 azalma
- İyileşmenin önemli bir bölümü, yalnızca modern tarayıcıları hedefleme kararı, daha iyi dead-code elimination ve agresif code splitting birleşiminden kaynaklandı
- Legacy desteğinin bırakılması, polyfill'lerin, ES5 transpile işleminin ve
nomodule fallback'inin kaldırılması gibi büyük faydalar sağladı
- Linear, optimizasyondan sonra bile yaklaşık 21MB minified JavaScript gönderiyor; ancak bunu yüzlerce route seviyeli chunk'a agresif biçimde bölerek gerektiğinde yüklüyor
- Asıl nokta belirli bir bundler seçimi değil; legacy tarayıcıların kaldırılması, native ESM'e geçiş ve agresif code splitting
- Bu adımlar üst üste birikince Linear'ın ilk yüklemedeki JavaScript'i kabaca yarıya indi ve build süreleri tek haneli değil, büyüklük mertebesinde azaldı
-
İlk yüklemeden sonra preload
- JavaScript'i küçük chunk'lara bölmek, her chunk'ın başka chunk'ları import ettiği waterfall yükleme sorununu doğurabilir
- Linear, JavaScript çalışmadan önce tarayıcının tam listeyi görüp istekleri paralel başlatmasını sağlayarak, entry script ilk
import satırına geldiğinde ilgili chunk'ların zaten cache'te olmasını sağlıyor
<head> içindeki modulepreload ile entry script'in crossorigin değerini eşleştirerek, tarayıcının preload ve import'u ayrı kaynaklar gibi görmeyip cache'teki fetch'i yeniden kullanmasını sağlıyor
- Cold load zaman çizelgesi, sıralı waterfall'dan tek bir paralel toplu yüklemeye dönüşüyor; ağ işlemleri hâlâ var ama hepsi bir kerede yapılıyor
- Kullanıcı giriş sayfasına ilk ulaştığında bu işlemler arka planda yürütülüyor ve birkaç saniye sonra tüm uygulama cache'e alınarak anında sunulabilir hâle geliyor
-
Daha yüksek hız ve çevrimdışı özellikler için service worker
- Kullanıcının henüz ziyaret etmediği görünümlerin route seviyeli chunk'ları service worker tarafından arka planda cache'leniyor
- Service worker, kaynağa gömülü bir precache manifest'e sahip ve yaklaşık 1.200 hash'li asset; route chunk'larını, ikonları ve fontları kapsıyor
- Giriş ekranına ulaştıktan birkaç saniye sonra tüm uygulama cache'e girmiş oluyor
- Sonraki gezinmeler ağ katmanını tamamen atlıyor ve service worker, HTTP cache'ten geçmeden kendi cache'inden doğrudan yanıt veriyor
- Yerel öncelikli sync engine ve IndexedDB'de zaten saklanan kullanıcı verileriyle birleşince Linear çevrimdışıyken de kullanılabiliyor
- Issue okuma, yeni issue oluşturma, başlık ve açıklama düzenleme, durum değiştirme destekleniyor
- Tüm işlemler yerel transaction deposunda kuyruğa alınıyor ve bağlantı geri geldiğinde flush ediliyor
modulepreload, şu anda gerekenleri paralel getirerek tarayıcının seri import chain içinde takılmasını önleyen mekanizma
- Service worker ise bir sonraki ihtiyaç duyulacak şeyleri hazırlayan mekanizma
- Linear'ın hızlı yükleme adımları; mümkün olduğunca çok kodu kaldırmak, küçük parçalara bölmek ve arka planda precache yapmak üzerine kurulu; amaç ağ isteklerini ya hızlandırmak ya da tamamen ortadan kaldırmak
-
Vendor bundle yapısı
- Linear'ın kullandığı her paket ayrı bir chunk'a bölünüyor ve bağımsız olarak cache'leniyor
- Geleneksel
vendor.js, yalnızca tek bir bağımlılık güncellense bile tüm bağımlılık grafiğinin cache'ini geçersiz kılar
- Linear'ın chunk bölme yaklaşımı, tek bir büyük dosya yerine ayrıntılı vendor cache'leme oluşturuyor; belirli bir bağımlılık güncellendiğinde yalnızca ilgili chunk geçersiz oluyor, geri kalanı cache'te kalıyor
-
Büyük font dosyalarının yüklenmesi
- Hatalı font yükleme, metnin kısa süre görünmemesine, gerçek font değiştiğinde layout shift'e ve preload uyumsuzluğu nedeniyle yinelenen fetch'lere yol açabilir
- Linear,
<head> içinde Inter Variable fontunu preload ediyor ve static.linear.app için preconnect kullanıyor
<link rel="preload"
href="https://static.linear.app/fonts/InterVariable.woff2?v=4.1"
as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preconnect" href="https://static.linear.app" crossorigin>
- Variable font, 100~900 arasındaki tüm weight eksenini tek bir woff2 ile karşılayarak her weight için ayrı istek ihtiyacını ortadan kaldırıyor
font-display: swap, fallback stack'i hemen render ediyor ve Inter yüklendikten sonra değiştiriyor
- Preload etiketindeki
crossorigin="anonymous", CSS daha sonra aynı fonta başvurduğunda tarayıcının cache'lenmiş kaynağı yeniden kullanmasını sağlayan kritik ayardır
crossorigin yoksa preload ile CSS referansının CORS modu farklı olur ve tarayıcı fontu yeniden getirmek zorunda kalır
-
Inline app shell
- Linear,
<head> içine yükleme durumunu çizmeye yetecek kadar CSS'i inline ekleyerek harici stylesheet isteği olmadan app shell'i gösteriyor
- Inline JavaScript, ilk deneyim için gerekli dallanmaları anında gerçekleştiriyor
- Electron ve Linear user agent'ını algılayıp
electron sınıfını ekliyor
localStorage.ApplicationStore yoksa logged-out sınıfını ekliyor
localStorage.splashScreenConfig içinden sidebar arka planını, sidebar genişliğini ve dark mode gibi shell token'larını geri yüklüyor
- Kullanıcı masaüstü uygulamasında bağlantıları açacak şekilde ayar yaptıysa sidebar genişliğini
8px olarak ayarlıyor
- İlk JavaScript bundle'ı ağ üzerinden gelmeden önce yükleme ekranı; tema, boyut ve konum açısından kullanıcının giriş durumuna göre zaten ayarlanmış oluyor
- Kullanıcının URL'yi yazıp Enter'a bastığı anda uygulamanın hazırmış gibi hissettirilmesinin en hızlı yolu, ilk
index.html yanıtında app shell'i göndermektir
-
Önce render, sonra kimlik doğrulama
- Tipik kimlik doğrulama akışı HTML fetch, bundle yükleme, session doğrulama, kullanıcı fetch'i, workspace fetch'i ve render sırasıyla ilerler; kullanıcı bir şey görmeden önce 1~3 saniye geçebilir
- Linear, kimlik doğrulamayı da değişiklik işleme mantığıyla ele alıyor; normal yolu varsayıp doğrulamayı arka planda yapıyor
- Çoğu CRUD uygulaması gerçek session'ı HttpOnly cookie içinde tutar ve frontend'in başlangıçta giriş yapılıp yapılmadığını anlayabilmesi için JavaScript tarafından okunabilen ayrı bir cookie veya
/me isteği ekler
- Linear'ın inline boot script'i, paralel bir kimlik doğrulama sinyali yerine yalnızca
localStorage.ApplicationStore var mı diye kontrol ediyor
if (localStorage.getItem("ApplicationStore") === null) {
document.documentElement.classList.add("logged-out");
}
ApplicationStore varsa, kullanıcı bu tarayıcıda daha önce Linear kullanmış ve workspace verileri zaten IndexedDB'ye yazılmış demektir
- Bu değer yoksa render edilecek veri de yoktur; bu yüzden shell, logged-out yerleşimine geçer ve giriş akışı devam eder
- Gerçek session token'ı cookie içindedir ve bundle session durumunu önceden tahmin etmez
- WebSocket handshake, sync delta veya HTTP çağrılarından biri süresi dolmuş session nedeniyle 401 alırsa istemci giriş sayfasına yönlendirir
- Genel desen; anında render için yerel verilere güvenmek, doğruluğun kaynağı olarak sunucuyu kullanmak ve ikisini asenkron biçimde uzlaştırmaktır
Senkronizasyon motoru
- Linear’ın hızı, sunucuyu UI’nin
source of truthu değil bir senkronizasyon hedefi olarak görme kararından başlıyor
- Hız, tek bir unsurun değil, birbiriyle kenetlenen üç eksenin sonucu
-
1. Veri zaten mevcut
- Uygulama açılırken sunucudan workspace çekmek yerine, IndexedDB’den bellek içi MobX nesne havuzuna hydrate ediliyor
- UI’deki tüm sorgular önce nesne havuzuna yöneliyor ve issue’lar zaten kullanıcının cihazında olduğu için “loading issues” durumu yok
- Linear, ölçeklenme sürecinde senkronizasyon motoru verisini JavaScript bundle’ına benzer bir mantıkla parçalara ayırıyor
- En ağır iki tablo olan Issue ve Comment, tek seferde getirilmiyor; gerektiğinde lazy-hydrate ediliyor
- Bu yaklaşım veri düzeyinde code splitting anlamına geliyor ve başlangıç maliyetinin workspace boyutunu değil workspace yapısını takip etmesini sağlıyor
- 10.000 issue içeren bir workspace bile, 100 issue’luk bir workspace ile neredeyse aynı hızda açılıyor
- Bir projeye girdiğinizde issue’lar zaten hazır oluyor; assignee’ye göre filtrelediğinizde ise indeks çoktan oluşturulmuş oluyor
-
2. Değişiklikler ağı beklemiyor
- Bir issue’nun durumunu değiştirdiğinizde neredeyse aynı anda üç şey gerçekleşiyor
- MobX observable güncellenerek değişiklik UI’ye yansıtılıyor
- Değişiklik, IndexedDB’deki kalıcı transaction kuyruğuna kaydediliyor
- Değişiklik, sunucuya gönderim kuyruğuna ekleniyor
- Bu noktada ağ henüz hiç kullanılmamış oluyor
- Kullanıcı kendi değişikliğini görmek için beklemiyor; retry, rollback ve reload across durability işlemlerinin tamamı arka planda ele alınıyor
- Sunucu reddederse observable eski haline dönüyor ve kısa bir flicker oluşuyor; ancak hatalı değişikliklerin çoğu transaction oluşturulmadan önce yakalanıyor
- Linear’ın akışı yerel değişiklikten başlıyor ve sunucuyu bir izin aşaması değil, bir doğrulama aşaması olarak ele alıyor
-
3. Tek delta, tek hücre
- Sunucu kullanıcının değişikliğini ya da başkasının değişikliğini doğruladığında, neyin değiştiğini gösteren küçük bir JSON envelope istemciye geri dönüyor
- İstemci, ilgili MobX observable’a değer yazarak değişikliği uyguluyor
- Linear’daki tüm model özellikleri ayrı ayrı observable ve bu özellikleri okuyan tüm bileşenler
observer() ile sarılıyor
- MobX, hangi bileşenin hangi alana bağımlı olduğunu tam olarak biliyor
- Tek bir issue’nun tek bir alanındaki değişiklik, yalnızca o alanı okuyan bileşenleri yeniden render ediyor; üst listeyi ya da kenar çubuğunun tamamını yeniden render etmiyor
- 50 issue güncellemesi, tüm listenin yeniden render edilmesi değil 50 hücrenin yeniden render edilmesi anlamına geliyor
- 10 kişinin aynı anda düzenleme yaptığı yoğun bir workspace’te bile güncelleme alma maliyeti, ekrandaki toplam öğe sayısına göre değil gerçekten değişen öğe sayısına göre artıyor
-
Üçünün birlikte çalışmasının nedeni
- Yalnızca yerel veritabanı olup optimistic write yoksa, kaydetme sırasında hâlâ spinner görülür
- Yalnızca optimistic write olup ince taneli observable’lar yoksa, her güncellemede takılma yaşanır
- Yalnızca ince taneli observable’lar olup yerel veritabanı yoksa, ilk yüklemede hâlâ bekleme olur
- Linear’ın hızı tek bir katmanın özelliği değil, tüm sistemin özelliğidir
- Bundler ve loader shell, ilk paint’in hızlı hissedilmesini sağlar; senkronizasyon motoru ise kullanmaya başladıktan sonra da bu hızlı hissi korur
Hız için tasarım
- Hız hem bir mühendislik problemi hem de bir tasarım problemidir
- En hızlı eylem yolu fare, üç menü ve tıklama gerektiriyorsa, kullanıcı iç motorun hızından bağımsız olarak bu adımların maliyetini öder
- Linear’ın hızındaki bir diğer eksen, klavyeyi gezinme ve iş tamamlama için ana araç olarak entegre etmiş olmasıdır
- Tüm yaygın işlemler için bir shortcut vardır, command palette tek tuş vuruşuyla açılır ve right-click menu özel olarak oluşturulmuştur
-
Her aksiyonun bir shortcut’ı var
- Tek bir karakter odaklanılmış issue’yu düzenler, iki harfli kombinasyonlar gezinme için kullanılır ve modifier’lar global davranışlar için kullanılır
- Linear’ın ilk dönemlerinden beri shortcut’lar temel bir yapı taşıydı; senkronizasyon motoru da herhangi bir aksiyonun her an yapılabilmesini sağlayacak şekilde tasarlanmıştı
- Shortcut’lar UI’nin her yerinde görünür ve en sık kullanılan shortcut’lar tek karakterlidir
- Yeni başlayanları dışlamamak için tüm işlemler fareyle de yapılabilir
-
Command palette her zaman tek tuş uzağında
⌘ k, Linear’daki neredeyse tüm aksiyonları arayabileceğiniz command palette’i açar
- Arama kapsamına issue’lar, projeler, label’lar, durum değiştirme, gezinme, issue oluşturma, ayarlar ve tema değiştirme gibi öğeler girer
- Command palette, sunucuyu değil yerel MobX nesne havuzunu aradığı için çok hızlıdır
- Tüm uygulamaya tek bir pane içinden erişilebilir; gezinme, issue oluşturma ve durum değiştirme işlemlerinin tümü arama üzerinden yapılır
- Command palette mevcut çalışma bağlamına uyum sağlar ve her view’daki temel aksiyonları ve shortcut’ları öğretir
- Hızlı bir uygulama için hem mükemmel mühendislik hem de iyi tasarım gerekir; mühendislik hızı tek bir etkileşimi hızlandırırken tasarım hızı etkileşime giden yolu kısaltır
- Gün boyu kullanılan bir araçta, shortcut ile 2 saniyelik bir fare rotası arasındaki fark her aksiyonda birikir
Animasyon
- Kötü animasyonlar, ilk yükleme, güncellemeler ve veritabanı sorgusu optimizasyonlarıyla kazanılan milisaniyeleri son aşamada yeniden boşa harcayabilir
- 500 ms süren bir
height animasyonu gibi unsurlar, kullanıcının beklememesini sağlama çabasını boşa çıkarabilir
-
Animasyon uygulanması gereken özellik sayısı azdır
- Tarayıcıdaki property değişiklikleri, rendering pipeline içindeki konumlarına göre üç katmanlı bir maliyete sahiptir
- composited property olan
transform ve opacity, işi GPU'ya devreder ve main thread'den bağımsız çalışır
- paint-triggering property olan
color, background-color, border-color, fill, layout'u atlar ama piksellerin yeniden çizilmesine neden olur
- layout-triggering property olan
width, height, top, left, margin, padding, kendilerinden sonraki tüm öğelerin konumunun yeniden hesaplanmasına yol açar ve animasyon hedefi olmamalıdır
/* Linear yaklaşımı */
.row:hover {
background-color: var(--color-bg-hover);
transition: background-color 0.12s;
}
.icon-arrow {
transform: translateX(0);
transition: transform 0.15s;
}
margin-left animasyonu yapılırsa, hover uygulanan satırın altındaki tüm satırların layout'u, transition'ın tamamı olan 200 ms boyunca her frame'de yeniden hesaplanır
- Uzun issue listelerinde bu fark, akıcı bir ekran ile jank arasındaki ayrımı belirler
- Linear'ın animasyon özellikleri çoğunlukla
transform ve opacity gibi composited property'lerdir; zaman zaman da background-color ve border-color kullanılır
-
Ne zaman ölçülü davranmak gerektiğini bilmek
- Her gün kullanılan araçlarda, pazarlama sitelerinde hoş görünen animasyonlar çalışmayı engelleyebilir
- Yanlış yerdeki küçük bir hover gecikmesi bile kullanıcının fark edeceği bir noktaya dönüşebilir
- Linear'daki birçok animasyon, origin'e referans verdiği için etkili çalışır
- status popover, status pill'den scale out olur; agent panel ise toggle'dan slide in yapar
- Bu hareketler dekoratif fade'ler değil, yeni öğenin nereden geldiğini anlatan mekânsal bir işlev görür
-
Süreleri kısa ve anlık tutmak
--speed-highlightFadeIn: 0s;
--speed-highlightFadeOut: .15s;
--speed-quickTransition: .1s;
--speed-regularTransition: .25s;
--speed-slowTransition: .35s;
- Birçok design system, varsayılan duration'ı gerekenden uzun tutar
- Material'ın standard duration'ı 200 ms'dir ve iOS spring, 350 ms'ye daha yakındır
- Linear'ın varsayılanları, sektör pratiğine göre daha kısa tarafta kalır
- Linear, enter ve exit için asimetrik timing kullanır
- hover highlight, popover ve agent panel çağrıldığında anında görünür, kapanırken ise 150 ms boyunca fade out olur
- agent window anında görünür ve macOS'e benzer şekilde fade out olur
Linear'ın hızlı olma biçimi
- Linear'ın performansı tek bir sır ya da tek bir teknoloji değil, doğru verilmiş yüzlerce kararın birikimli sonucudur
- Yaklaşımın büyük kısmı basittir; Next, TanStack veya gösterişli framework'lerden ziyade, kullanıcıya uygun mimariyi erken belirleyip korumanın sonucudur
- Sunucu, UI'nin source of truth'u değil, sync target'ı olarak çalışır
- Veritabanı tarayıcının içindedir ve değişiklikler önce yerelde uygulanır, ardından arka planda uzlaştırılır
- İlk yüklemede daha az kod, daha fazla parçaya bölünerek gönderilir; service worker ise kullanıcı giriş sayfasındayken geri kalanı precache eder
- Kimlik doğrulama, yerel durumu temel alarak normal akışı varsayar ve doğrulamayı sonra yapar
- Senkronizasyon motoru, IndexedDB'den property başına MobX observable olarak hydrate ettiği için 50 issue güncellemesi, listenin tamamını yeniden render etmek yerine 50 hücrenin yeniden render edilmesiyle işlenir
- Girdi modeli keyboard-first'tür; tüm yaygın işlemler için shortcut ve global command palette bulunur
- Animasyonlar GPU dostu özelliklerde kalır, layout-triggering property'lere animasyon uygulanmaz
- Zor olan kısım, uygulamanın kendisinden çok; kod tabanı olgunlaşıp büyürken ve yeni kısıtlarla karşılaşırken yıllar boyunca ayrıntı kalitesine odaklanma tutumudur
1 yorum
Hacker News görüşleri
Uygulamanıza böyle bir deneyim katmak istiyorsanız Zero(https://zero.rocicorp.dev/)'ya bakmak iyi olabilir
Canlı demo: https://gigabugs.rocicorp.dev/
Alternatiflerin listesi de burada: https://zero.rocicorp.dev/docs/when-to-use#alternatives
İç işleyişi merak ediyorsanız Replicache tasarım dokümanına da bakmaya değer: https://doc.replicache.dev/concepts/how-it-works
Replicache, Zero'nun öncülü ve çekirdek protokol hâlâ aynı şekilde çalışıyor
Veriyi istemciye senkronize tutmanın getirdiği belirgin performans avantajlarının yanı sıra, React kodunun ne kadar basitleştiği de beni şaşırttı. Bir senkronizasyon motoru olduğunda istemci durumunun büyük kısmı ortadan kalkıyor ve bileşen kodunun çoğunu senkron şekilde düşünebiliyorsunuz
Bunun için özel bir ekip kurmadan elde edilebilecek seçenekler arasında muhtemelen en yakını bu
Linear'ın hızlı olduğunu hep duydum ama her gün gerçekten kullanınca hevesim kaçtı. Arama epey yavaş ve arayüz çoğu zaman hantallaşıyor; görünüşü güzel olsa da “Pulse” küçük ölçekte bile bir gürültü seli gibi
İhtiyacım olan şeyi bulmak zor olduğu için sonunda her şeyi favorilere ekliyorum. İlk dönem Trello, proje takip deneyimi açısından açık ara en iyisiydi
Geçen yıl biri Linear senkronizasyon motorunu tersine mühendislikle çözüp GitHub'a koymuş ve yanına da güzel bir açıklama eklemişti
https://github.com/wzhudev/reverse-linear-sync-engine/blob/m...
Bu tür local-first senkronizasyonlu web uygulamaları gerçekten ilginç ve faydalı olabilir, ama temel varsayımın biraz hatalı olduğunu düşünüyorum
Varsayım şu tarzda: “Linear'da bir issue'yu güncellemek için birkaç milisaniye yeterli. Geleneksel bir CRUD uygulamasında aynı iş yaklaşık 300ms sürer”, “istemci ile sunucu arasında gidip gelen her veri yüzlerce milisaniyelik bir bedele mal olur”
HTTP istemcisi ile sunucu arasındaki gidiş-dönüş süresinin ışık hızı nedeniyle büyümesi sorununu çözemeyiz, ama backend'i kullanıcıya yakın tutup hızlı hâle getirebiliriz
Örneğin çoğu kullanıcı için yaklaşık 10ms gidiş-dönüş süresi içinde olan bir web uygulaması backend'i işletmek ve backend'in de yaklaşık 10ms içinde yanıtı üretmesini sağlamak gayet mümkün. Yani geleneksel bir CRUD uygulamasında da aynı işi 300ms değil yaklaşık 30ms seviyesine indirebilirsiniz
Elbette Linear'ın backend tarafında meşru nedenlerle daha fazla zamana ihtiyaç duyup frontend desteğine başvurması gerekebilir, ama bunu genelleştiremeyiz. Her bir JavaScript parçasının da kendine ait bir maliyeti var
us-west-1 60ms, eu-centra-1 100ms, Asya ise 200ms uzakta. Üstelik bunlar veri merkezleri arası trafik; gerçek halka açık internette ev bağlantılarına kadar olan gecikme çok daha kötü
Veritabanı tam olarak tek bir bölgede olmak zorunda. Nereye koyarsanız koyun, dünyadaki kullanıcıların çoğu oraya 100ms'den daha uzakta olacak
Endpoint'in nerede olduğunun önemi yok, çünkü veri okuyup yazmak için yine veritabanıyla konuşmak zorunda. Veriyi kullanıcıya yakın yerlere çoğaltmaya başladığınız anda, sonunda zaten bir local-first senkronizasyon veritabanına sahip oluyorsunuz
Bunu kendiniz yapsanız da hazır bir çözüm kullansanız da, çoğaltılmış bu veritabanı istemci tarafı senkronizasyonla aynı sorunların hepsini taşır ve yine de kayda değer ağ gecikmesi kalır. Fiziği aşamazsınız; çoğu kullanıcı için ya 0,25 saniyelik commit süresini kabul edersiniz ya da eventual consistency, yani senkronizasyonu seçersiniz
Elbette dünya çapındaki CDN edge ağı gibi yerlere bir “ara backend” koyabilirsiniz, ama o noktada bu yaklaşımda olduğu gibi “ara backend”i istemciye koymanın getirdiği karmaşıklık maliyetini ödemiş olursunuz
En kötü durumda arka plan worker'ı güncelleme başarısız mesajı üretir, UI thread'i de bunu alıp gösterir. Başarı yolu ise yıldırım gibi hızlı kalmaya devam eder
Nihai tutarlılığa sahip bir veritabanı yazmak zordur ve Linear’ın kullanım senaryosu için uygun olabilir, ancak güncellememin sunucuya, yani ekibe ulaşıp ulaşmadığını bilmemek sorun yaratır
Geçmişte yer aldığım diğer projelerde senkronizasyon gecikmesi sayısız probleme yol açtığı için her zaman eşzamanlı çözümü tercih ederim. Gösterişli özellikleri ancak gerçekten gerektiğinde devreye alır, onun yerine sunucuyu inanılmaz hızlı olacak şekilde optimize edip kullanıcıların ağ gecikmesini tolere etmesine izin veririm
Şirkette Linear kullanıyoruz. Azınlıkta olduğumu biliyorum ama kullanıcı deneyimi gerçekten zorlayıcı. Hızlı demek de pek mümkün değil
Sayfanın kendisi teknik olarak fena olmayan bir hızda yükleniyor, ama zamanın yaklaşık yarısında sayfadaki sayıların, verinin hâlâ yüklenmekte olduğuna dair görsel bir işaret olmadan değiştiğini görüyorsunuz
O kadar kötü ki Linear’da sadece tek cümlelik bir açıklamayla issue açıyor, ayrıntıları doldurmak için GitHub’a geçiyorum. Linear’ın iyi ve hızlı yaptığı şey tam olarak bu kadar
Ne yazık ki şirketin ayakta kalması ve üst pazara çıkması için pratikte başka yolu yok
“Hızlı” kelimesini kullanmam. Zaten yüklenmesi 30 saniye sürüyorsa, issue güncellemesinin 300ms’den “birkaç” milisaniyeye inmesi çok da önemli değil
Jira’dan daha iyi ama bu da çok düşük bir çıta
Harika. Geliştirmekte olduğum tarayıcı oyunu ve oyun motoruna da benzer bir şey ekleyip ilk yüklemeden sonra yükleme durumunu tamamen ortadan kaldırabilirim belki. Benimki sunucusuz, tamamen istemci taraflı statik varlık yapısı
Bu oyunun performansına takıntılıyım. Geçen hafta sonundan önce M1 MacBook Pro’da 128 eşzamanlı oyuncuyu simüle ederken yol bulma, ağır strateji mantığı ve render işlemlerinin hepsini viewport içinde yapıp 120fps’i korumakta zorlanıyordum; çok nadiren kare düşüşü oluyordu ve kare süresi yaklaşık 4ms idi
Hafta sonu boyunca yoğun performans çalışması yaptım ve artık 2048 eşzamanlı oyuncuyu milisaniyenin altında kare süresiyle simüle edebiliyorum. Buna render, tüm mantık ve prosedürel üretim de dahil
Ayrıca 11,2 kat CPU throttling uygulayarak düşük donanımlı mobil cihaz seviyesini simüle ettiğimde bile 256–512 eşzamanlı oyuncuda yaklaşık 5ms kare süresiyle stabil 60fps alıyorum. Şu anda ana darboğaz biraz mantık problemi ve düşük donanımlı cihazlarda iyileştirilmesi gereken başlangıç/önyükleme süresi; burada Linear’dan öğrenilecek şeyler olabilir
Linear’ın aslında epey yavaş olduğunu hissettim. Bir sekmeyi bir süre açık bırakırsam CPU’yu %100 kullandığı haftalar oldu
İlginç. Dürüst olmak gerekirse Linear için hiç “hızlı” diye düşünmedim. Çoğu web uygulaması gibi gecikmeli hissettiriyordu ama JIRA ile karşılaştırınca elbette ışık hızında
Linear’ın kendisi harika ve JIRA işkencesinden sonra gerçekten ferahlatıcı. İyimser yönlendirme ve “hızlılık” hakkında konuşacaksak önce Gmail’den bahsetmemiz gerekmez mi diye düşünüyorum
Hızın cevabı önceden yükleme. Temelde istemci veritabanını başlangıçta indirip bir önbellek geçersiz kılma stratejisi uyguluyorsunuz
Bu paradigmanın veri senkronizasyonu tarafını gerçekleştirmek için starfx’i yaptım: https://starfx.bower.sh/learn#data-loading-strategy-stale-wh...