24 पॉइंट द्वारा xguru 2024-07-19 | 6 टिप्पणियां | WhatsApp पर शेयर करें
  • 3 साल पहले Notion ने क्लाइंट पर डेटा cache करने के लिए SQLite database का उपयोग करके Mac और Windows के लिए Notion app की स्पीड सफलतापूर्वक बढ़ाई थी
  • इस बार वही सुधार उन उपयोगकर्ताओं तक भी पहुँचाया गया जो ब्राउज़र के माध्यम से Notion का उपयोग करते हैं
  • यह लेख WebAssembly(WASM) implementation वाले sqlite3 का उपयोग करके ब्राउज़र में Notion की performance कैसे बेहतर की गई, इसका गहन विश्लेषण है
  • SQLite का उपयोग करने से सभी आधुनिक ब्राउज़रों में page navigation time 20% बेहतर हुआ
    • खासकर उन उपयोगकर्ताओं के लिए, जिनके यहाँ internet connection जैसे बाहरी कारणों से API response time बहुत धीमा था, अंतर और भी स्पष्ट था
    • उदाहरण के लिए, Australia के उपयोगकर्ताओं के लिए page navigation time 28%, China के उपयोगकर्ताओं के लिए 31%, और India के उपयोगकर्ताओं के लिए 33% तेज़ हुआ

मुख्य तकनीक: OPFS और Web Workers

  • WASM SQLite library session के बीच डेटा बनाए रखने के लिए Origin Private File System(OPFS) नामक आधुनिक browser API का उपयोग करती है
  • OPFS केवल Web Workers में उपलब्ध है
  • Web Worker को ऐसे code के रूप में समझा जा सकता है जो browser के main thread से अलग एक अलग thread पर चलता है, जहाँ अधिकांश JavaScript चलती है
  • Notion को Webpack के साथ bundle किया जाता है, जो Web Worker load करने के लिए आसान syntax देता है
  • Notion ने Web Worker को इस तरह सेट किया कि वह OPFS का उपयोग करके SQLite database file बनाए या मौजूदा file load करे, और इसी Web Worker में मौजूदा caching code चलाया
  • Main thread और Worker के बीच message passing को आसान बनाने के लिए Comlink library का उपयोग किया गया

SharedWorker आधारित दृष्टिकोण

  • अंतिम architecture Roy Hashimoto द्वारा GitHub discussion में प्रस्तावित नए समाधान पर आधारित है
    • यह ऐसा approach है जिसमें एक समय में केवल एक tab SQLite तक पहुँचता है, लेकिन दूसरे tabs भी SQLite query चला सकते हैं
  • यह नया architecture कैसे काम करता है?
    • सरल शब्दों में, हर tab के पास अपना dedicated Web Worker होता है जो SQLite में लिख सकता है
    • लेकिन वास्तव में एक समय में केवल एक tab ही Web Worker का उपयोग कर सकता है
    • SharedWorker यह manage करता है कि “active tab” कौन-सा है
    • अगर active tab बंद हो जाए, तो SharedWorker समझ जाता है कि नया active tab चुनना है
  • SQLite query चलाने के लिए हर tab का main thread query को SharedWorker को भेजता है, और SharedWorker उसे active tab के dedicated Worker की ओर redirect करता है
  • Tabs एक साथ जितनी चाहें SQLite queries चला सकते हैं, और वे हमेशा एक ही active tab के जरिए route होती हैं
  • हर Web Worker SQLite database तक पहुँचने के लिए OPFS SyncAccessHandle Pool VFS implementation का उपयोग करता है, जो सभी प्रमुख browsers में काम करता है

सरल तरीका क्यों काम नहीं आया

  • ऊपर बताई गई architecture बनाने से पहले, Notion ने एक सरल तरीका आज़माया था जिसमें हर tab के लिए dedicated Web Worker होता और हर Web Worker SQLite database में लिखता
  • लेकिन इनमें से कोई भी तरीका ज्यों का त्यों Notion की requirements के लिए पर्याप्त नहीं था

अड़चन #1: Cross-origin isolation

  • OPFS via sqlite3_vfs का उपयोग करने के लिए site का “cross-origin isolation” state में होना ज़रूरी है
  • किसी page में cross-origin isolation जोड़ने के लिए कुछ security headers सेट करने पड़ते हैं, जो यह सीमित करते हैं कि कौन-सी scripts load हो सकती हैं
  • इन headers को सेट करना काफ़ी बड़ा काम हो सकता है
  • Notion अपनी web infrastructure की कई सुविधाएँ चलाने के लिए बहुत-सी third-party scripts पर निर्भर करता है, इसलिए पूरी cross-origin isolation हासिल करने के लिए हर vendor से नए headers सेट करने और iframe के काम करने के तरीके बदलने को कहना पड़ता — जो व्यवहारिक रूप से बहुत कठिन था
  • Testing में, Chrome और Edge browser में उपलब्ध SharedArrayBuffer के लिए Origin Trials का उपयोग करके, उपयोगकर्ताओं के एक subset को यह variant दिया गया और महत्वपूर्ण performance data मिला
  • इन Origin Trials की मदद से cross-origin isolation की requirement को अस्थायी रूप से bypass किया जा सका

अड़चन #2: Corruption समस्या

  • जब OPFS via sqlite3_vfs को कुछ उपयोगकर्ताओं तक पहुँचाया गया, तो कुछ उपयोगकर्ताओं में गंभीर bugs दिखने लगे
    • इन उपयोगकर्ताओं को page पर गलत data दिखाई देने लगा
    • उदाहरण के लिए, किसी comment का गलत teammate को assign होना, या किसी नए page के link का preview पूरी तरह किसी दूसरे page का होना
  • इन bug से प्रभावित उपयोगकर्ताओं की database files देखने पर पैटर्न मिला कि SQLite database किसी तरह corrupt हो गई थी
    • कुछ tables से rows select करने पर error आती थी, और rows को जाँचने पर data consistency की समस्याएँ मिलीं, जैसे एक ही ID वाली कई rows जिनका content अलग-अलग था
  • SQLite database उस स्थिति में कैसे पहुँची, इस पर अनुमान था कि यह concurrency समस्या के कारण हुआ
    • क्योंकि कई tabs खुले होते थे, और हर tab में SQLite database के लिए active connection वाला dedicated Web Worker होता था
    • Notion app server से update मिलने पर cache में बार-बार लिखता है, यानी tabs एक ही file में एक साथ लिख सकते थे
  • हालाँकि पहले से SQLite queries को transaction के रूप में batch करने वाला approach उपयोग में था, फिर भी काफ़ी शक था कि OPFS API की concurrency handling की कमी के कारण corruption हो रही थी
  • इसलिए corruption errors को log करना शुरू किया गया और Web Locks जोड़ने, तथा केवल focused tab को SQLite में लिखने देने जैसे कुछ अस्थायी उपाय आज़माए गए
    • इन बदलावों से corruption rate कम हुई, लेकिन production traffic पर feature को फिर से चालू करने लायक नहीं
    • फिर भी इससे यह पुष्टि हुई कि concurrency समस्या corruption में बड़ा योगदान दे रही थी
  • Notion desktop app में यह समस्या नहीं आई
    • उन platforms पर केवल एक parent process ही SQLite में लिखता है
    • App में आप जितने चाहें tabs खोल सकते हैं, लेकिन database file तक हमेशा एक ही thread पहुँचता है

अड़चन #3: विकल्प एक समय में केवल एक tab में चल सकता है

  • OPFS SyncAccessHandle Pool VFS variant का भी मूल्यांकन किया गया
    • इस variant में SharedArrayBuffer की आवश्यकता नहीं होती, इसलिए इसे Safari, Firefox और उन अन्य browsers में उपयोग किया जा सकता है जहाँ SharedArrayBuffer के लिए Origin Trial नहीं है
  • इस variant की कमी यह थी कि यह एक समय में केवल एक tab में ही चल सकता है
    • अगर किसी बाद में खुले tab में SQLite database खोलने की कोशिश की जाए, तो सीधा error आ जाता है
  • एक तरफ, इसका मतलब था कि OPFS SyncAccessHandle Pool VFS में OPFS via sqlite3_vfs वाले variant जैसी concurrency समस्याएँ नहीं थीं
    • इसे कुछ उपयोगकर्ताओं तक पहुँचाने पर corruption समस्या न दिखने से इसकी पुष्टि हुई
  • दूसरी तरफ, Notion चाहता था कि सभी उपयोगकर्ता tabs caching का लाभ लें, इसलिए इस variant को ज्यों का त्यों launch नहीं किया जा सकता था

समस्या का समाधान

  • यही तथ्य कि कोई भी variant सीधे उपयोग योग्य नहीं था, ऊपर बताए गए SharedWorker architecture को बनाने का कारण बना
  • यह architecture इन SQLite variants में से किसी एक के साथ compatible है
  • OPFS via sqlite3_vfs variant का उपयोग करने पर, एक समय में केवल एक tab लिखता है, इसलिए corruption समस्या से बचा जा सकता है
  • OPFS SyncAccessHandle Pool VFS variant का उपयोग करने पर SharedWorker की वजह से सभी tabs में caching संभव हो जाती है
  • जब यह पुष्टि हो गई कि यह architecture दोनों variants में काम करता है, metrics में performance improvement साफ़ दिख रही है, और corruption समस्या नहीं है, तब यह तय करने का समय आया कि किस variant को ship किया जाए
  • OPFS SyncAccessHandle Pool VFS चुना गया, क्योंकि इसमें cross-origin isolation की requirement नहीं थी और इस वजह से Chrome व Edge के बाहर के browsers में rollout रुकता नहीं था

Performance regression को कम करना

  • जब यह सुधार उपयोगकर्ताओं तक पहुँचाना शुरू किया गया, तब कुछ performance regressions दिखीं जिन्हें ठीक करना ज़रूरी था, जैसे load time का धीमा होना

Page load धीमा होना

  • पहली खोज यह थी कि Notion pages के बीच navigation तेज़ हुआ, लेकिन initial page load धीमा हो गया
    • Profiling से पता चला कि page load आम तौर पर data fetching से bottleneck नहीं होता
    • Notion का app boot code API call पूरा होने का इंतज़ार करते हुए दूसरे काम भी चलाता है, जैसे JS parsing और app setup, इसलिए उसे navigation जितना SQLite caching का लाभ नहीं मिलता
  • धीमापन इसलिए आया क्योंकि उपयोगकर्ताओं को WASM SQLite library download और process करनी पड़ती थी
    • इससे page load process block हो जाती थी, और बाकी page load tasks साथ में नहीं चल पाते थे
    • Library का आकार कुछ सौ kilobytes होने के कारण यह अतिरिक्त समय metrics में साफ़ दिखा
  • इसे ठीक करने के लिए library load करने के तरीके में थोड़ा बदलाव किया गया
    • WASM SQLite को पूरी तरह asynchronous तरीके से load किया गया ताकि page load block न हो
    • इसका मतलब यह था कि initial page data शायद ही कभी SQLite से load होगा
    • यह स्वीकार्य था, क्योंकि वस्तुनिष्ठ रूप से देखा गया कि initial page को SQLite से load करके मिलने वाला speedup, library download के कारण होने वाले slowdown से बड़ा नहीं था
  • बदलाव लागू करने के बाद initial page load metrics experiment के test group और control group में समान हो गईं

धीमे devices को caching का लाभ नहीं मिला

  • Metrics में एक और बात दिखी: Notion में एक page से दूसरे page पर जाने का median time तो तेज़ हुआ, लेकिन 95th percentile time धीमा हो गया
    • कुछ devices, जैसे mobile phone जिनके browser में Notion खुला था, caching का लाभ पाने के बजाय और खराब प्रदर्शन कर रहे थे
  • इस पहेली का जवाब mobile team की एक पहले की जाँच में मिला
    • जब native mobile app में यह caching लागू की गई थी, तब कुछ devices जैसे पुराने Android phones disk से बहुत धीमा पढ़ते थे
    • इसलिए यह मान लेना सही नहीं था कि disk cache से data load करना API से वही data load करने से हमेशा तेज़ होगा
  • Mobile investigation के परिणामस्वरूप, page load में पहले से कुछ ऐसा logic मौजूद था जो दो asynchronous requests(SQLite और API) को एक-दूसरे के साथ “race” करवाता था
    • Navigation click के code path में यही logic फिर से लागू किया गया
    • इससे experiment के दोनों groups के बीच navigation time का 95th percentile समान हो गया

निष्कर्ष

  • Browser में Notion के लिए SQLite performance improvements लाना अपने साथ कई चुनौतियाँ लेकर आया
  • खासकर नई technology के साथ काम करते हुए कई अनजान समस्याओं का सामना करना पड़ा, और इस प्रक्रिया में कुछ सबक मिले:
    • OPFS स्वाभाविक रूप से concurrency को सहज तरीके से handle नहीं करता। Developers को यह समझकर उसी हिसाब से design करना चाहिए
    • Web Workers और SharedWorkers (और इनके cousin Service Workers, जिनका इस लेख में उल्लेख नहीं है) की भूमिकाएँ अलग हैं, और ज़रूरत पड़ने पर इन्हें जोड़कर उपयोग करना फायदेमंद हो सकता है
    • 2024 की वसंत ऋतु तक, sophisticated web applications में cross-origin isolation को पूरी तरह लागू करना आसान नहीं है, खासकर जब third-party scripts उपयोग में हों
  • Browser में उपयोगकर्ताओं के लिए SQLite के साथ data cache करने के परिणामस्वरूप, ऊपर बताए गए navigation time में 20% सुधार देखा गया, और अन्य metrics में गिरावट नहीं दिखी
    • महत्वपूर्ण बात यह थी कि SQLite corruption से जुड़ी कोई समस्या नहीं देखी गई
    • उनका मानना है कि इस अंतिम approach की सफलता और स्थिरता का श्रेय SQLite की आधिकारिक WASM implementation पर काम करने वाली team और Roy Hashimoto को जाता है, जिन्होंने जनता के लिए एक experimental approach उपलब्ध कराया

6 टिप्पणियां

 
[यह टिप्पणी छिपाई गई है.]
 
cometkim 2024-07-19

इसीलिए जिन services को third-party के साथ collaboration करना होता है, उन्हें पहली release से ही cross-origin isolation enable करके जाना चाहिए...

 
freedomzero 2024-07-20

अरे, cometkim जी, आपसे मिलकर खुशी हुई

 
sixmen 2024-07-19

मेरे Firefox में Notion पेज खोलने पर वह फ्रीज़ हो जाता है, इसलिए इस्तेमाल ही नहीं कर पाता — क्या इसकी वजह यही हो सकती है..? (Notion ऐप ठीक से चलता है, इसलिए फिलहाल वही इस्तेमाल कर रहा हूँ)

 
hellworld 2024-07-20

शायद ऐसा ही है। Enda भी local file write को केवल Chrome और Edge में ही support करता है।

 
freedomzero 2024-07-20

मेरे साथ भी पुराने Linux लैपटॉप पर ऐसा हुआ था, लेकिन इसे incognito mode में खोलने पर काम हो गया था।