- Linear इश्यू मैनेजमेंट का काम browser के अंदर के database और local-first sync के साथ संभालता है, इसलिए इश्यू अपडेट कुछ मिलीसेकंड के भीतर UI में दिख जाते हैं
- UI जिस वास्तविक database को पढ़ता है, वह IndexedDB में होता है; बदलाव पहले local में लागू होते हैं, फिर async तरीके से server को भेजे जाते हैं, और WebSocket के ज़रिए delta दोबारा वितरित किए जाते हैं
- पहली लोडिंग के लिए कम JavaScript·CSS ट्रांसफर, आक्रामक code splitting, modulepreload, service worker precache, और inline app shell जैसी loading strategy network wait को कम करती है
- sync engine IndexedDB के data को MobX object pool में hydrate करता है, बदलावों को transaction queue में रखता है, और field-level observable state के आधार on केवल ज़रूरी cell को फिर से render करता है
- तेज़ महसूस होने वाली स्पीड keyboard-केंद्रित input, global command palette, GPU-friendly animation, और छोटे transition time तक को मिलाकर बनी system design का नतीजा है
browser के अंदर का database
- पारंपरिक CRUD web app में user के click के बाद HTTP request, server के database lookup, response receive, और browser repaint की प्रक्रिया से गुजरना पड़ता है, जिसके कारण सैकड़ों मिलीसेकंड तक spinner·skeleton·रुका हुआ UI दिखाई देता है
- Linear UI द्वारा पढ़े जाने वाले वास्तविक database को browser के IndexedDB में रखता है, बदलाव पहले local में लागू करता है, फिर उन्हें async तरीके से server को भेजता है, और server WebSocket के ज़रिए दूसरे clients को delta broadcast करता है
- तेज़ web app में सबसे बड़ा bottleneck network होता है, और client व server के बीच data transfer में सैकड़ों मिलीसेकंड का खर्च आता है
- Linear का मूल flow network request को user के लिए अदृश्य बनाना और जहाँ तक संभव हो loading state को खत्म करना है
// Linear
issue.title = "Faster app launch";
issue.save();
issue.title = "Faster app launch" in-memory data store को अपडेट करता है, और Linear के मामले में यह MobX observable का उपयोग करता है
issue.save(); sync engine द्वारा batch process के लिए queue में रखा जाने वाला transaction बनाता है, जिसे बाद में server पर flush किया जाता है
- UI local memory changes के आधार पर synchronously फिर से render होता है, और data sync background में चलता है, इसलिए spinner की ज़रूरत नहीं पड़ती
- Tuomas ने 2024 की एक conference में कहा था कि Linear में उन्होंने सबसे पहला code sync engine लिखा था, और इसे startup में आम नहीं होने वाला approach बताया
- ज़्यादातर apps को Linear की तरह अपना sync engine बनाने की ज़रूरत नहीं होती; केवल TanStack Query और SWR के optimistic update से भी काफी हद तक वैसी ही महसूस होने वाली स्पीड पाई जा सकती है
- optimistic request बेकार spinner हटाने, तुरंत state update करने, background validation, और ज़रूरत पड़ने पर rollback के ज़रिए बड़ा सुधार देती है
- UI responsiveness network latency पर निर्भर नहीं होनी चाहिए, और user जो speed महसूस करता है, वह server response speed से ज़्यादा interface responsiveness से तय होती है
-
Linear के stack की एक झलक
- Linear React, TypeScript, MobX, Postgres, और CDN जैसे सरल stack पर बना है
- frontend में React और
react-dom, MobX, TypeScript, Rolldown-Vite और plugin-react-oxc, ProseMirror और y-prosemirror, Radix UI primitives, Emotion और StyleX, Comlink, idb, graphql-request, Sentry, Inter Variable का उपयोग होता है
- backend में Node.js और TypeScript, Cloud SQL पर PostgreSQL, Memorystore Redis, turbopuffer, GCP का Kubernetes, और Cloudflare Workers इस्तेमाल होते हैं
- desktop client Electron-आधारित है, जबकि mobile के लिए iOS में Swift और Kotlin के साथ अलग से पूरा reimplementation किया गया है
- marketing site में Next.js, styled-components, और inline SVG sprite का उपयोग होता है
- Linear client-side rendering को बनाए रखता है, और यह दिखाता है कि सही architecture और design के साथ CSR भी तुरंत responsive महसूस हो सकता है
- पूरे app को client-side रखने से server·client विभाजन,
window access उपलब्ध है या नहीं, और cache header setting जैसी जटिलताएँ कम होती हैं, जिससे एक सरल mental model मिलता है
पहली लोडिंग को तुरंत महसूस कराना
- प्रोडक्टिविटी टूल्स में असली काम शुरू करने तक लगने वाला समय एक अहम डिटेल है
- client-side app की शुरुआती लोडिंग
index.html request, JavaScript और CSS request, authentication प्रोसेसिंग, और app दिखाने के लिए API request के क्रम में धीमी हो सकती है
-
Linear का bundler flow: Parcel, Rollup, Vite, Rolldown
- तुरंत महसूस होने वाली speed runtime से पहले, यानी build time से शुरू होती है, और तेज़ लोड के लिए भेजे जाने वाले JavaScript और CSS की मात्रा घटाना महत्वपूर्ण है
- Linear ने अपनी build pipeline को Parcel → Rollup → Vite → Rolldown क्रम में फिर से लिखा, और हर migration का लक्ष्य JavaScript·CSS की मात्रा कम करना और developer experience बेहतर बनाना था
- Linear ब्लॉग के अनुसार सुधार के आँकड़े
- ट्रांसफर होने वाला code 50% कम
- compression के बाद size 30% कम
- cold cache page load 10~30% बेहतर
- Safari में active-issues view का Time-to-first-paint 59% कम
- memory usage 70~80% कम
- सुधार का बड़ा हिस्सा केवल modern browser को target करने के फैसले, बेहतर dead-code elimination, और aggressive code splitting के संयोजन से आया
- legacy support हटाने से polyfill, ES5 transpile, और
nomodule fallback हटाने जैसे बड़े फायदे मिले
- optimization के बाद भी Linear लगभग 21MB minified JavaScript भेजता है, लेकिन इसे सैकड़ों route-level chunk में आक्रामक रूप से बाँटकर ज़रूरत पड़ने पर ही लाता है
- मुख्य बात किसी खास bundler का चुनाव नहीं, बल्कि legacy browser हटाना, native ESM पर जाना, और aggressive code splitting है
- इन चरणों के जुड़ने से Linear की first-load JavaScript लगभग आधी रह गई, और build time एक छोटे single-digit अंतर से नहीं बल्कि पूरे एक order of magnitude से घटा
-
शुरुआती लोड के बाद preload
- JavaScript को छोटे chunk में बाँटने पर waterfall load की समस्या आती है, जहाँ हर chunk दूसरे chunk को import करता है
- Linear ने ऐसा सेटअप बनाया कि JavaScript चलने से पहले browser पूरी सूची देख सके और request को parallel में शुरू कर दे, ताकि entry script जब पहले
import तक पहुँचे तब तक संबंधित chunk पहले से cache में हों
<head> में modulepreload और entry script के crossorigin value को मैच करके browser preload और import को अलग resource न माने और cached fetch को reuse करे
- cold load timeline sequential waterfall से बदलकर एक single parallel batch बन जाती है, यानी network काम अभी भी रहता है लेकिन सब एक साथ होता है
- user के पहली बार login page पर पहुँचने पर यह काम background में किया जाता है, और कुछ सेकंड बाद पूरा app cache में रखकर तुरंत दिया जा सकता है
-
और तेज़ speed और offline क्षमता के लिए service worker
- जिन view के route-level chunk user ने अभी तक नहीं देखे, उन्हें service worker background में cache करता है
- service worker के पास source में embedded precache manifest होता है, जिसमें लगभग 1,200 hashed asset शामिल हैं जो route chunk, icon और font को कवर करते हैं
- login screen पर पहुँचने के कुछ सेकंड के भीतर पूरा app cache में चला जाता है
- इसके बाद navigation पूरी तरह network को bypass कर देती है, और service worker HTTP cache से गुज़रे बिना अपने cache से सीधे response देता है
- local-first sync engine और IndexedDB में पहले से stored user data के साथ मिलकर Linear offline में भी इस्तेमाल किया जा सकता है
- issue पढ़ना, नया issue बनाना, title और description edit करना, और status बदलना supported है
- सभी actions local transaction store में queue होते हैं, और connection वापस आते ही flush हो जाते हैं
modulepreload वह तंत्र है जो अभी ज़रूरी चीज़ों को parallel में लाता है ताकि browser serial import chain में अटका न रहे
- service worker वह तंत्र है जो आगे ज़रूरी होने वाली चीज़ों को पहले से तैयार रखता है
- Linear की fast-loading strategy है जितना संभव हो उतना code हटाना, उसे छोटे हिस्सों में बाँटना, और background precache करना; लक्ष्य है network request को तेज़ बनाना या उन्हें पूरी तरह हटाना
-
Vendor bundle संरचना
- Linear के इस्तेमाल में आने वाला हर package अलग chunk में बँटा होता है और स्वतंत्र रूप से cache होता है
- पारंपरिक
vendor.js में अगर एक dependency भी update हो जाए तो पूरी dependency graph cache invalidate हो जाती है
- Linear का chunk splitting एक single बड़ी file की जगह granular vendor caching बनाता है, इसलिए किसी खास dependency के update पर केवल वही chunk invalidate होता है और बाकी cache में बने रहते हैं
-
बड़े font file की loading
- गलत font loading थोड़ी देर के लिए invisible text, असली font swap होने पर layout shift, और preload mismatch की वजह से duplicate fetch पैदा कर सकती है
- Linear
<head> में Inter Variable font को preload करता है, और static.linear.app के लिए preconnect करता है
<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 एक ही woff2 में 100~900 पूरे weight axis को संभाल लेता है, जिससे हर weight के लिए अलग request हट जाती है
font-display: swap fallback stack को तुरंत render करता है, और Inter लोड होने के बाद उसे replace करता है
- preload tag का
crossorigin="anonymous" एक अहम setting है, जो बाद में CSS द्वारा उसी font को refer करने पर browser को cached resource reuse करने देती है
- अगर
crossorigin न हो तो preload और CSS reference का CORS mode अलग हो जाता है, जिससे browser font को फिर से fetch करता है
-
inline app shell
- Linear
<head> के अंदर loading state draw करने भर का CSS inline डालता है, ताकि external stylesheet request के बिना app shell दिख सके
- inline JavaScript शुरुआती experience के लिए ज़रूरी branching तुरंत कर देता है
- Electron और Linear user agent detect करके
electron class जोड़ता है
- अगर
localStorage.ApplicationStore न हो तो logged-out class जोड़ता है
localStorage.splashScreenConfig से sidebar background, sidebar width, dark mode जैसे shell token restore करता है
- अगर user ने desktop app में link खोलने की setting चुनी हो तो sidebar width को
8px पर adjust करता है
- पहला JavaScript bundle network से आने से पहले ही loading screen का theme, size और position login status के हिसाब से सेट हो चुके होते हैं
- user के URL डालकर Enter दबाते ही app तैयार लगने का सबसे तेज़ तरीका है शुरुआती
index.html response में app shell भेज देना
-
पहले render, बाद में authentication
- सामान्य authentication flow HTML fetch, bundle load, session verify, user fetch, workspace fetch, render के क्रम में चलता है, और user को कुछ दिखने तक 1~3 सेकंड लग सकते हैं
- Linear authentication को भी change processing की तरह संभालता है, यानी normal path मानकर background में verify करता है
- ज़्यादातर CRUD app असली session को HttpOnly cookie में रखते हैं, और frontend को startup के दौरान login state पता चल सके इसके लिए JavaScript से पढ़ी जा सकने वाली अलग cookie या
/me request जोड़ते हैं
- Linear का inline boot script parallel authentication signal की जगह सिर्फ
localStorage.ApplicationStore की मौजूदगी देखता है
if (localStorage.getItem("ApplicationStore") === null) {
document.documentElement.classList.add("logged-out");
}
ApplicationStore मौजूद होने का मतलब है कि user ने इस browser में पहले Linear इस्तेमाल किया है, और workspace data पहले से IndexedDB में है
- अगर value न हो तो render करने के लिए data नहीं है, इसलिए shell
logged-out layout में switch हो जाता है और login flow आगे बढ़ता है
- असली session token cookie में होता है, और bundle session state का पहले से अनुमान नहीं लगाता
- अगर WebSocket handshake, sync delta, या HTTP call में से किसी को expired session के साथ 401 मिलता है, तो client login पर redirect करता है
- पूरा pattern local data पर भरोसा करके तुरंत render करना, server को correctness का source मानना, और दोनों को asynchronous तरीके से reconcile करना है
सिंक इंजन
- Linear की गति की शुरुआत इस फैसले से होती है कि सर्वर को UI का source of truth नहीं, बल्कि sync target माना जाए
- गति किसी एक चीज़ का नतीजा नहीं है, बल्कि तीन अक्षों के साथ काम करने का परिणाम है
-
1. डेटा पहले से मौजूद है
- ऐप बूट होने पर सर्वर से workspace लाने के बजाय, IndexedDB से in-memory MobX object pool में hydrate किया जाता है
- UI की सभी queries पहले object pool की ओर जाती हैं, और क्योंकि issue पहले से ही user device पर मौजूद होते हैं, इसलिए “loading issues” जैसी कोई state नहीं होती
- स्केल बढ़ने के दौरान Linear ने sync engine के डेटा को JavaScript bundle जैसी ही सोच के साथ chunk किया
- सबसे भारी दो tables, Issue और Comment, एक साथ fetch नहीं किए जाते, बल्कि ज़रूरत पड़ने पर lazy-hydrate होते हैं
- यह तरीका data-level code splitting है, जिससे startup cost workspace के size के बजाय workspace की structure का अनुसरण करती है
- 10,000 issues वाला workspace भी 100 issues वाले workspace की तुलना में लगभग उतनी ही गति से बूट होता है
- project में जाने पर issues पहले से मौजूद होते हैं, और assignee से filter करने पर index पहले से बना होता है
-
2. बदलाव नेटवर्क का इंतज़ार नहीं करते
- issue state बदलने पर लगभग एक साथ तीन चीज़ें होती हैं
- MobX observable update के ज़रिए UI में बदलाव दिखता है
- IndexedDB की durable transaction queue में बदलाव रिकॉर्ड होता है
- server transmission queue में बदलाव जोड़ा जाता है
- इस समय तक network का अभी इस्तेमाल नहीं हुआ होता
- user को अपना बदलाव देखने के लिए इंतज़ार नहीं करना पड़ता, और retry, rollback, तथा reload across durability सब background में संभाले जाते हैं
- अगर server reject करता है तो observable वापस revert हो जाता है और थोड़ी देर का flicker होता है, लेकिन ज़्यादातर गलत बदलाव transaction बनने से पहले ही पकड़ लिए जाते हैं
- Linear का flow local change से शुरू होता है और server को permission चरण नहीं, बल्कि confirmation चरण की तरह मानता है
-
3. एक delta, एक cell
- जब server user के बदलाव या किसी और के बदलाव को confirm करता है, तो क्या बदला है यह दिखाने वाला एक छोटा JSON envelope client को वापस आता है
- client संबंधित MobX observable में value लिखकर बदलाव लागू करता है
- Linear में हर model property अलग-अलग observable है, और उन properties को पढ़ने वाले सभी components को
observer() में wrap किया जाता है
- MobX को ठीक-ठीक पता चल सकता है कि कौन-सा component किस field पर निर्भर है
- एक issue के एक field में बदलाव सिर्फ उन्हीं components को फिर से render करता है जो उस field को पढ़ते हैं, parent list या पूरे sidebar को फिर से render नहीं करता
- 50 issues के updates का मतलब पूरी list re-render नहीं, बल्कि 50 cells का re-render है
- 10 लोग एक साथ edit कर रहे व्यस्त workspace में भी updates receive करने की लागत स्क्रीन पर मौजूद कुल items के हिसाब से नहीं, बल्कि वास्तव में बदले हुए items के हिसाब से बढ़ती है
-
ये तीनों साथ क्यों काम करते हैं
- अगर सिर्फ local database हो और optimistic write न हो, तो save करते समय फिर भी spinner दिखेगा
- अगर सिर्फ optimistic write हो और granular observables न हों, तो हर update पर झटके या रुकावटें आएँगी
- अगर सिर्फ granular observables हों और local database न हो, तो initial load पर फिर भी इंतज़ार करना पड़ेगा
- Linear की गति किसी एक layer की property नहीं, बल्कि पूरे system की property है
- bundler और loader shell first paint को तेज़ महसूस कराते हैं, और sync engine उपयोग शुरू होने के बाद भी उस तेज़ एहसास को बनाए रखता है
गति के लिए डिज़ाइन
- गति एक engineering समस्या भी है और एक design समस्या भी
- अगर सबसे तेज़ action path में mouse, तीन menus और clicks चाहिए हों, तो user अंदरूनी engine की गति से अलग उस प्रक्रिया की लागत चुकाएगा
- Linear की गति का एक और अक्ष यह है कि उसने keyboard को navigation और task completion के मुख्य tool के रूप में शामिल किया है
- हर सामान्य काम के लिए shortcut है, command palette एक key input से खुलती है, और right-click menu custom बनाया गया है
-
हर action के लिए shortcut है
- single character focused issue को edit करता है, दो-अक्षरों के संयोजन navigation के लिए काम आते हैं, और modifier global actions के लिए इस्तेमाल होते हैं
- Linear के शुरुआती चरण से ही shortcuts एक foundational element थे, और sync engine का एक हिस्सा इस तरह डिज़ाइन किया गया था कि कोई भी action कभी भी किया जा सके
- UI के हर हिस्से में shortcuts दिखाई देते हैं, और सबसे ज़्यादा इस्तेमाल होने वाले shortcuts single character हैं
- beginners को बाहर न करने के लिए हर action mouse से भी किया जा सकता है
-
Command palette हमेशा एक key input की दूरी पर है
⌘ k एक command palette खोलता है जिसमें Linear के लगभग सभी actions खोजे जा सकते हैं
- search targets में issues, projects, labels, state changes, navigation, issue creation, settings, theme toggle आदि शामिल हैं
- command palette server नहीं, बल्कि local MobX object pool को search करती है, इसलिए यह बहुत तेज़ है
- पूरी app एक ही pane से access की जा सकती है, और navigation, issue creation, state changes सब search के ज़रिए किए जा सकते हैं
- command palette मौजूदा working context के हिसाब से खुद को ढालती है, और हर view के core actions तथा shortcuts सिखाने के तरीके से काम करती है
- तेज़ app के लिए बेहतरीन engineering और design दोनों चाहिए; engineering speed किसी एक interaction को तेज़ बनाती है, जबकि design speed interaction तक पहुँचने का रास्ता छोटा करती है
- दिन भर इस्तेमाल होने वाले tool में shortcut और 2-second mouse path का अंतर हर action में जमा होता जाता है
एनीमेशन
- खराब एनीमेशन शुरुआती लोड, अपडेट और database query optimization से बचाए गए मिलीसेकंड को आख़िरी चरण में फिर बर्बाद कर सकते हैं
- 500ms का
height animation जैसी चीज़ें यूज़र को इंतज़ार न कराना चाहने की कोशिश को बेअसर कर सकती हैं
-
animate करने लायक properties बहुत कम हैं
- ब्राउज़र में property change की लागत rendering pipeline में उसकी स्थिति के अनुसार तीन स्तरों में बंटती है
- composited properties
transform और opacity काम को GPU पर भेजती हैं और main thread से स्वतंत्र रूप से चलती हैं
- paint-triggering properties
color, background-color, border-color, fill layout को छोड़ देती हैं, लेकिन pixel redraw कराती हैं
- layout-triggering properties
width, height, top, left, margin, padding बाद के सभी elements की स्थिति फिर से calculate कराती हैं, इसलिए इन्हें animation target नहीं होना चाहिए
/* Linear 방식 */
.row:hover {
background-color: var(--color-bg-hover);
transition: background-color 0.12s;
}
.icon-arrow {
transform: translateX(0);
transition: transform 0.15s;
}
margin-left को animate करने पर hovered row के नीचे की हर row का layout पूरे 200ms transition के दौरान हर frame में फिर से calculate होता है
- लंबी issue list में यही फ़र्क smooth स्क्रीन और jank के बीच सीमा तय करता है
- Linear में ज़्यादातर animation properties
transform और opacity जैसी composited properties हैं, और कभी-कभी background-color व border-color का उपयोग होता है
-
संयम रखना जानना ज़रूरी है
- रोज़ इस्तेमाल होने वाले tools में marketing site पर अच्छे लगने वाले animation काम में बाधा बन सकते हैं
- गलत जगह पर थोड़ी-सी hover delay भी ऐसी चीज़ बन सकती है जिसे यूज़र तुरंत नोटिस करे
- Linear के कई animation origin को संदर्भित करते हैं, इसलिए उनकी गति प्रभावी लगती है
- status popover, status pill से scale out होता है, और agent panel toggle से slide in होता है
- यह motion सजावटी fade नहीं है, बल्कि यह बताता है कि नया element कहाँ से आया
-
duration छोटा और तुरंत महसूस होने वाला रखें
--speed-highlightFadeIn: 0s;
--speed-highlightFadeOut: .15s;
--speed-quickTransition: .1s;
--speed-regularTransition: .25s;
--speed-slowTransition: .35s;
- कई design systems default duration को ज़रूरत से ज़्यादा लंबा रखते हैं
- Material का standard duration 200ms है, और iOS spring लगभग 350ms के क़रीब है
- Linear के default values उद्योग की आम प्रथा से छोटे हैं
- Linear enter और exit के लिए asymmetric timing इस्तेमाल करता है
- hover highlight, popover और agent panel बुलाए जाने पर तुरंत दिखते हैं, और बंद होने पर 150ms तक fade out होते हैं
- agent window तुरंत दिखती है और macOS की तरह fade out होती है
Linear के तेज़ होने का तरीका
- Linear का performance किसी एक secret या एक ही तकनीक का नतीजा नहीं, बल्कि सैकड़ों सही फ़ैसलों के जमा असर का परिणाम है
- इस approach का बड़ा हिस्सा सीधा-सादा है, और Next, TanStack या किसी flashy framework के बिना भी यूज़र के अनुकूल architecture को शुरू में तय कर उसे बनाए रखने का परिणाम है
- सर्वर UI का source of truth नहीं, बल्कि sync target की तरह काम करता है
- database ब्राउज़र के अंदर है, और बदलाव पहले local पर लागू होते हैं, फिर background में reconcile किए जाते हैं
- first load में कम code को ज़्यादा हिस्सों में भेजा जाता है, और service worker यूज़र के login page पर रहने के दौरान बाकी को precache कर देता है
- authentication local state के आधार पर happy path मानकर चलती है और बाद में verify करती है
- sync engine IndexedDB से per-property MobX observables में hydrate करता है, इसलिए 50 issues का update पूरी list rerender करने के बजाय 50 cells rerender करके संभाला जाता है
- input model keyboard-first है, और हर सामान्य काम के लिए shortcut और global command palette मौजूद है
- animation GPU-friendly properties तक सीमित रहते हैं, और layout-triggering properties को animate नहीं किया जाता
- असली कठिनाई implementation से ज़्यादा इस बात में है कि codebase के mature होने, scale होने और नई constraints से टकराने के दौरान वर्षों तक detail quality पर ध्यान केंद्रित रखा जाए
1 टिप्पणियां
Hacker News की राय
अगर आप अपने application में ऐसा अनुभव जोड़ना चाहते हैं, तो Zero(https://zero.rocicorp.dev/) को देखना अच्छा रहेगा
लाइव डेमो: https://gigabugs.rocicorp.dev/
alternatives की सूची यहाँ भी है: https://zero.rocicorp.dev/docs/when-to-use#alternatives
अगर इसकी अंदरूनी कार्यप्रणाली जाननी हो, तो Replicache design document भी देखने लायक है: https://doc.replicache.dev/concepts/how-it-works
Replicache, Zero का पूर्ववर्ती है, और core protocol अब भी लगभग उसी तरह काम करता है
client पर data sync करके रखने से मिलने वाले स्पष्ट performance फायदे के अलावा, React code कितना सरल हो जाता है, यह भी चौंकाने वाला था. sync engine होने पर client state का बड़ा हिस्सा गायब हो जाता है, और component code के अधिकांश हिस्से को synchronous तरीके से सोचा जा सकता है
dedicated team बनाए बिना जो हासिल किया जा सकता है, उनमें शायद यह सबसे नज़दीकी विकल्प है
मैं हमेशा सुनता था कि Linear तेज़ है, लेकिन इसे रोज़ इस्तेमाल करने पर मेरा उत्साह ठंडा पड़ गया. search काफ़ी धीमा है, UI अक्सर भारी-सा लगता है, और देखने में अच्छा होने के बावजूद “Pulse” छोटे scale पर भी शोर की बाढ़ जैसा है
ज़रूरी चीज़ें ढूँढना मुश्किल हो जाता है, इसलिए आख़िरकार मैं सब कुछ bookmark में डाल देता हूँ. शुरुआती Trello project tracking experience के मामले में बहुत बेहतर था
पिछले साल किसी ने Linear sync engine को reverse engineer करके GitHub पर डाला था और साथ में बढ़िया व्याख्या भी लिखी थी
https://github.com/wzhudev/reverse-linear-sync-engine/blob/m...
ऐसे local-first sync web apps वाकई दिलचस्प और उपयोगी हो सकते हैं, लेकिन मुझे लगता है कि बुनियादी premise कुछ हद तक गलत है
premise कुछ ऐसा है: “Linear में issue अपडेट करने में बस कुछ milliseconds लगते हैं. पारंपरिक CRUD app में वही काम लगभग 300ms लेता है”, “client और server के बीच आने-जाने वाला हर data सैकड़ों milliseconds की लागत लाता है”
light speed की सीमा के कारण HTTP client और server के बीच round-trip time बढ़ने की समस्या को तो हल नहीं किया जा सकता, लेकिन backend को user के क़रीब रखकर तेज़ बनाया जा सकता है
उदाहरण के लिए, ऐसा web app backend चलाना जो अधिकांश users के लिए लगभग 10ms round-trip time के भीतर हो, और backend भी लगभग 10ms में response render कर दे, पूरी तरह संभव है. यानी पारंपरिक CRUD app में भी वही काम 300ms नहीं बल्कि करीब 30ms में किया जा सकता है
हो सकता है Linear को backend पर वाजिब कारणों से ज़्यादा समय लगता हो और उसे frontend की मदद चाहिए, लेकिन इसे सामान्य नियम नहीं बनाया जा सकता. JavaScript के हर टुकड़े की अपनी लागत भी होती है
us-west-1 60ms दूर है, eu-centra-1 100ms दूर है, और एशिया 200ms दूर है. और यह भी data center के बीच का traffic है; असली public internet पर घरेलू कनेक्शन तक latency इससे कहीं बदतर होती है
database को ठीक-ठीक एक region में होना पड़ता है. उसे जहाँ भी रखें, पृथ्वी के अधिकांश users उससे 100ms से ज़्यादा दूर होंगे
endpoint कहाँ है, इससे फ़र्क नहीं पड़ता, क्योंकि data पढ़ने-लिखने के लिए endpoint को database से बात करनी ही होगी. जैसे ही आप data को user के पास replicate करने की कोशिश करते हैं, आप अंततः local-first sync database के मालिक बन जाते हैं
चाहे आप इसे ख़ुद बनाएँ या off-the-shelf इस्तेमाल करें, उस replicated database में client-side sync जैसी सारी समस्याएँ होंगी, और काफ़ी network latency फिर भी बनी रहेगी. physics को हराया नहीं जा सकता, इसलिए ज़्यादातर users के लिए या तो 0.25-second commit देना होगा या eventual consistency, यानी sync, चुनना होगा
बेशक, global CDN edge network जैसी जगहों पर “intermediate backend” रखा जा सकता है, लेकिन उस बिंदु पर आप client पर “intermediate backend” रखने वाले इस तरीके जैसी ही complexity cost चुकाने लगते हैं
सबसे खराब स्थिति में background worker update failure message emit कर सकता है, जिसे UI thread लेकर दिखा दे. success path फिर भी बिजली की तरह तेज़ रहता है
अंतिम संगति वाला डेटाबेस लिखना मुश्किल है, और Linear के use case के लिए यह ठीक हो सकता है, लेकिन यह न जान पाना कि मेरा update server, यानी टीम तक पहुँचा या नहीं, एक समस्या है
पहले जिन दूसरे projects में मैं शामिल था, उनमें sync delay ने अनगिनत समस्याएँ पैदा की थीं, इसलिए मैं हमेशा synchronous solution चुनता हूँ। चमकदार features सिर्फ़ तभी निकालूँगा जब वे सच में ज़रूरी हों, और उसकी बजाय server को बेहद तेज़ optimize करके user को network latency सहने दूँगा
हमारी company में Linear इस्तेमाल होता है। मैं शायद minority में हूँ, लेकिन user experience सच में बहुत कठिन है। इसे तेज़ कहना भी मुश्किल है
पेज खुद तकनीकी रूप से ठीक-ठाक जल्दी load हो जाता है, लेकिन लगभग आधे समय आप सिर्फ़ पेज पर numbers बदलते हुए देखते रहते हैं, बिना इस बात के किसी visual indication के कि data loading अभी भी चल रही है
हालत इतनी खराब है कि Linear में मैं सिर्फ़ एक वाक्य की description के साथ issue बनाता हूँ, और बाकी details भरने के लिए GitHub चला जाता हूँ। Linear बस यही एक काम अच्छी और तेज़ी से करता है
अफ़सोस की बात है, लेकिन company को ज़िंदा रहना है और ऊपर के market में जाना है, तो उसके पास लगभग यही रास्ता है
मैं इसे “तेज़” नहीं कहूँगा। जब शुरू में load होने में ही 30 सेकंड लगते हों, तब issue update का 300ms से “कुछ” milliseconds तक जाना ज़्यादा मायने नहीं रखता
Jira से बेहतर है, लेकिन यह बहुत ही नीचे का benchmark है
बढ़िया है। शायद मैं अपने बन रहे browser game और engine में भी ऐसा कुछ डाल सकूँ, ताकि पहली load के बाद loading state को पूरी तरह खत्म किया जा सके। मेरा setup server-less, पूरी तरह client-side static asset structure है
मैं इस game की performance को लेकर काफ़ी जुनूनी रहा हूँ। पिछले weekend से पहले तक M1 MacBook Pro पर 128 concurrent players को simulate करते हुए, viewport के अंदर pathfinding, भारी strategy logic और rendering सब कुछ संभालते हुए 120fps बनाए रखना मुश्किल पड़ रहा था, और बहुत कभी-कभी frame drop के साथ frame time लगभग 4ms था
weekend के दौरान मैंने performance पर ज़ोरदार काम किया, और अब मैं 2048 concurrent players को sub-millisecond frame time पर simulate कर सकता हूँ। यह rendering, सारी logic और procedural generation समेत है
और 11.2x CPU throttling लगाकर low-end mobile device जैसा environment simulate करने पर भी 256~512 concurrent players पर लगभग 5ms frame time के साथ स्थिर 60fps मिल रहा है। अभी मुख्य bottleneck थोड़ा logic संबंधी issue है और low-end devices पर startup/boot time है जिसे बेहतर करना है, और लगता है कि Linear से कुछ सीखने को मिल सकता है
मुझे हमेशा लगा कि Linear असल में काफ़ी धीमा है। कुछ हफ़्तों तक tab खुला रहने पर यह CPU 100% तक चला जाता था
दिलचस्प है। सच कहूँ तो मैंने कभी Linear को “तेज़” नहीं माना। ज़्यादातर web apps की तरह इसमें भी latency महसूस होती थी, लेकिन JIRA से तुलना करें तो यह निश्चित ही बिजली की रफ़्तार है
Linear खुद शानदार है, और JIRA torture के बाद तो यह सच में ताज़गी जैसा लगता है। optimistic routes और “तेज़ी” की बात करनी हो तो शायद पहले Gmail की बात करनी चाहिए
speed का जवाब है prefetching। मूल रूप से initialization के समय client database डाउनलोड कर लेना और उसके साथ cache invalidation strategy रखना
इसी paradigm के data synchronization पहलू को करने के लिए मैंने starfx बनाया: https://starfx.bower.sh/learn#data-loading-strategy-stale-wh...