ऑफ़-मेन-थ्रेड आर्किटेक्चर, आपके ऐप्लिकेशन की विश्वसनीयता और उपयोगकर्ता अनुभव को काफ़ी बेहतर बना सकता है.
पिछले 20 सालों में, वेब में काफ़ी बदलाव आया है. पहले इसमें कुछ स्टाइल और इमेज वाले स्टैटिक दस्तावेज़ होते थे. अब इसमें जटिल और डाइनैमिक ऐप्लिकेशन होते हैं. हालांकि, एक चीज़ में ज़्यादा बदलाव नहीं हुआ है: हमारे पास हर ब्राउज़र टैब के लिए सिर्फ़ एक थ्रेड (कुछ अपवादों के साथ) है. इससे हमारी साइटों को रेंडर करने और JavaScript को चलाने का काम किया जाता है.
इस वजह से, मुख्य थ्रेड पर बहुत ज़्यादा काम का बोझ बढ़ गया है. वेब ऐप्लिकेशन के ज़्यादा जटिल होने पर, मुख्य थ्रेड की वजह से परफ़ॉर्मेंस में रुकावट आती है. इससे भी ज़्यादा खराब बात यह है कि किसी उपयोगकर्ता के लिए, मुख्य थ्रेड पर कोड चलाने में लगने वाले समय का पूरी तरह से अनुमान नहीं लगाया जा सकता. इसकी वजह यह है कि डिवाइस की क्षमताओं का परफ़ॉर्मेंस पर बहुत ज़्यादा असर पड़ता है. यह अनिश्चितता तब और बढ़ जाएगी, जब उपयोगकर्ता अलग-अलग तरह के डिवाइसों से वेब को ऐक्सेस करेंगे. इनमें, कम सुविधाओं वाले फ़ीचर फ़ोन से लेकर ज़्यादा पावर और ज़्यादा रीफ़्रेश रेट वाली फ़्लैगशिप मशीनें शामिल हैं.
अगर हमें बेहतर वेब ऐप्लिकेशन को Core Web Vitals जैसे परफ़ॉर्मेंस के दिशा-निर्देशों को भरोसेमंद तरीके से पूरा करना है, तो हमें अपने कोड को मुख्य थ्रेड (ओएमटी) से बाहर लागू करने के तरीके चाहिए. Core Web Vitals, इंसानी सोच और मनोविज्ञान के बारे में अनुभव के आधार पर मिले डेटा पर आधारित है.
वेब वर्कर क्यों?
JavaScript, डिफ़ॉल्ट रूप से एक सिंगल-थ्रेडेड भाषा है. यह मुख्य थ्रेड पर टास्क चलाती है. हालांकि, वेब वर्कर, मुख्य थ्रेड से बचने का एक तरीका उपलब्ध कराते हैं. इसके लिए, वे डेवलपर को अलग-अलग थ्रेड बनाने की अनुमति देते हैं, ताकि मुख्य थ्रेड से बाहर के काम को मैनेज किया जा सके. वेब वर्कर का दायरा सीमित होता है और वे डीओएम को सीधे ऐक्सेस नहीं कर सकते. हालांकि, अगर कोई ऐसा काम करना है जो मुख्य थ्रेड को ओवरलोड कर सकता है, तो वेब वर्कर काफ़ी फ़ायदेमंद हो सकते हैं.
Core Web Vitals के मामले में, मुख्य थ्रेड से अलग काम करना फ़ायदेमंद हो सकता है. खास तौर पर, मुख्य थ्रेड से वेब वर्कर को काम सौंपने से, मुख्य थ्रेड के लिए कॉन्टेंट की कमी को कम किया जा सकता है. इससे पेज के पेज के रिस्पॉन्स में लगने वाला समय (आईएनपी) मेट्रिक को बेहतर बनाया जा सकता है. जब मुख्य थ्रेड के पास प्रोसेस करने के लिए कम काम होता है, तो वह उपयोगकर्ता के इंटरैक्शन का जवाब ज़्यादा तेज़ी से दे सकता है.
मुख्य थ्रेड पर कम काम होने से, सबसे बड़े एलिमेंट को रेंडर करने में लगने वाले समय (एलसीपी) को कम करने में मदद मिलती है. ऐसा खास तौर पर, स्टार्टअप के दौरान होता है. एलसीपी एलिमेंट को रेंडर करने के लिए, मुख्य थ्रेड के समय की ज़रूरत होती है. यह समय, टेक्स्ट या इमेज को रेंडर करने के लिए ज़रूरी होता है. ये दोनों ही एलसीपी एलिमेंट के सामान्य और अक्सर इस्तेमाल होने वाले उदाहरण हैं. मुख्य थ्रेड के काम को कम करके, यह पक्का किया जा सकता है कि आपके पेज के एलसीपी एलिमेंट को रेंडर होने में ज़्यादा समय न लगे. ऐसा इसलिए, क्योंकि मुख्य थ्रेड के काम को कम करने से, वेब वर्कर को ज़्यादा समय लेने वाले काम को हैंडल करने का मौका मिलता है.
वेब वर्कर के साथ थ्रेडिंग
अन्य प्लैटफ़ॉर्म पर, आम तौर पर एक साथ कई काम किए जा सकते हैं. इसके लिए, आपको थ्रेड को एक फ़ंक्शन देना होता है. यह फ़ंक्शन, आपके प्रोग्राम के बाकी हिस्सों के साथ-साथ चलता है. दोनों थ्रेड से एक ही वैरिएबल को ऐक्सेस किया जा सकता है. साथ ही, शेयर किए गए इन संसाधनों के ऐक्सेस को म्यूटेक्स और सेमाफ़ोर के साथ सिंक किया जा सकता है, ताकि रेस कंडिशन से बचा जा सके.
JavaScript में, हमें वेब वर्कर से काफ़ी हद तक मिलती-जुलती सुविधाएं मिल सकती हैं. ये सुविधाएं 2007 से उपलब्ध हैं और 2012 से सभी मुख्य ब्राउज़र पर काम करती हैं. वेब वर्कर, मुख्य थ्रेड के साथ-साथ चलते हैं. हालांकि, ओएस थ्रेडिंग की तरह, वे वैरिएबल शेयर नहीं कर सकते.
वेब वर्कर बनाने के लिए, वर्कर कंस्ट्रक्टर को एक फ़ाइल पास करें. इससे वह फ़ाइल एक अलग थ्रेड में चलने लगती है:
const worker = new Worker("./worker.js");
postMessage एपीआई का इस्तेमाल करके, वेब वर्कर को मैसेज भेजें. postMessage कॉल में मैसेज की वैल्यू को पैरामीटर के तौर पर पास करें. इसके बाद, वर्कर में मैसेज इवेंट लिसनर जोड़ें:
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
// ...
});
मुख्य थ्रेड पर वापस मैसेज भेजने के लिए, वेब वर्कर में उसी postMessage एपीआई का इस्तेमाल करें. साथ ही, मुख्य थ्रेड पर एक इवेंट लिसनर सेट अप करें:
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
console.log(event.data);
});
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
postMessage(a + b);
});
हालांकि, इस रणनीति की अपनी सीमाएं हैं. पहले, वेब वर्कर का इस्तेमाल मुख्य रूप से किसी एक भारी काम को मुख्य थ्रेड से हटाने के लिए किया जाता था. एक ही वेब वर्कर से कई कार्रवाइयां मैनेज करने की कोशिश करने पर, चीज़ें जल्दी ही मुश्किल हो जाती हैं: आपको मैसेज में पैरामीटर के साथ-साथ कार्रवाई को भी कोड में बदलना पड़ता है. साथ ही, अनुरोधों के जवाबों को मैच करने के लिए, बुककीपिंग करनी पड़ती है. इस जटिलता की वजह से, वेब वर्कर का इस्तेमाल ज़्यादा नहीं किया जाता.
हालांकि, अगर हम मुख्य थ्रेड और वेब वर्कर के बीच कम्यूनिकेशन की कुछ समस्याओं को दूर कर सकें, तो यह मॉडल कई मामलों में काफ़ी मददगार साबित हो सकता है. खुशकिस्मती से, एक ऐसी लाइब्रेरी है जो यही काम करती है!
Comlink: वेब वर्कर के लिए काम को आसान बनाना
Comlink एक लाइब्रेरी है. इसका मकसद, आपको postMessage की जानकारी के बारे में सोचे बिना वेब वर्कर का इस्तेमाल करने की सुविधा देना है. Comlink की मदद से, वेब वर्कर और मुख्य थ्रेड के बीच वैरिएबल शेयर किए जा सकते हैं. यह सुविधा, थ्रेडिंग की सुविधा देने वाली अन्य प्रोग्रामिंग लैंग्वेज की तरह ही काम करती है.
Comlink को सेट अप करने के लिए, इसे वेब वर्कर में इंपोर्ट करें और फ़ंक्शन का एक सेट तय करें, ताकि उन्हें मुख्य थ्रेड में दिखाया जा सके. इसके बाद, मुख्य थ्रेड पर Comlink को इंपोर्ट करें, वर्कर को रैप करें, और एक्सपोज़ किए गए फ़ंक्शन ऐक्सेस करें:
worker.js
import {expose} from 'comlink';
const api = {
someMethod() {
// ...
}
}
expose(api);
main.js
import {wrap} from 'comlink';
const worker = new Worker('./worker.js');
const api = wrap(worker);
मुख्य थ्रेड पर api वैरिएबल, वेब वर्कर में मौजूद वैरिएबल की तरह ही काम करता है. हालांकि, हर फ़ंक्शन वैल्यू के बजाय वैल्यू के लिए प्रॉमिस दिखाता है.
आपको किस कोड को वेब वर्कर में ले जाना चाहिए?
वेब वर्कर के पास डीओएम और कई एपीआई का ऐक्सेस नहीं होता. जैसे, WebUSB, WebRTC या Web Audio. इसलिए, अपने ऐप्लिकेशन के उन हिस्सों को वर्कर में नहीं डाला जा सकता जो इस तरह के ऐक्सेस पर निर्भर करते हैं. हालांकि, कोड के हर छोटे हिस्से को वर्कर थ्रेड में ले जाने से, मुख्य थ्रेड में उन चीज़ों के लिए ज़्यादा जगह मिलती है जो वहां होनी चाहिए—जैसे कि यूज़र इंटरफ़ेस को अपडेट करना.
वेब डेवलपर के लिए एक समस्या यह है कि ज़्यादातर वेब ऐप्लिकेशन, ऐप्लिकेशन में मौजूद हर चीज़ को व्यवस्थित करने के लिए, Vue या React जैसे यूज़र इंटरफ़ेस (यूआई) फ़्रेमवर्क पर निर्भर करते हैं. हर चीज़ फ़्रेमवर्क का एक कॉम्पोनेंट होती है. इसलिए, वह डीओएम से जुड़ी होती है. इस वजह से, ओएमटी आर्किटेक्चर पर माइग्रेट करना मुश्किल हो सकता है.
हालांकि, अगर हम किसी ऐसे मॉडल पर शिफ़्ट करते हैं जिसमें यूज़र इंटरफ़ेस (यूआई) से जुड़ी समस्याओं को अन्य समस्याओं से अलग रखा जाता है, जैसे कि स्टेट मैनेजमेंट, तो वेब वर्कर, फ़्रेमवर्क पर आधारित ऐप्लिकेशन के साथ भी काफ़ी मददगार हो सकते हैं. PROXX के साथ भी यही तरीका अपनाया गया है.
PROXX: ओएमटी की केस स्टडी
Google Chrome की टीम ने PROXX को Minesweeper के क्लोन के तौर पर डेवलप किया है. यह प्रोग्रेसिव वेब ऐप्लिकेशन की ज़रूरी शर्तों को पूरा करता है. जैसे, ऑफ़लाइन काम करना और उपयोगकर्ताओं को दिलचस्प अनुभव देना. हालांकि, गेम के शुरुआती वर्शन, फ़ीचर फ़ोन जैसे सीमित क्षमता वाले डिवाइसों पर ठीक से काम नहीं कर रहे थे. इससे टीम को पता चला कि मुख्य थ्रेड की वजह से गेम की परफ़ॉर्मेंस पर असर पड़ रहा है.
टीम ने गेम के विज़ुअल स्टेट को उसके लॉजिक से अलग करने के लिए, वेब वर्कर का इस्तेमाल करने का फ़ैसला किया:
- मुख्य थ्रेड, ऐनिमेशन और ट्रांज़िशन को रेंडर करता है.
- वेब वर्कर, गेम लॉजिक को हैंडल करता है. यह पूरी तरह से कंप्यूटेशनल होता है.
ओएमटी की वजह से, PROXX के फ़ीचर फ़ोन की परफ़ॉर्मेंस पर दिलचस्प असर पड़ा. बिना ओएमटी वाले वर्शन में, यूज़र इंटरफ़ेस (यूआई) के साथ इंटरैक्ट करने के बाद, वह छह सेकंड के लिए फ़्रीज़ हो जाता है. कोई सुझाव नहीं दिया गया है और उपयोगकर्ता को कुछ और करने से पहले, पूरे छह सेकंड तक इंतज़ार करना होगा.
हालांकि, ओएमटी वर्शन में गेम को यूज़र इंटरफ़ेस (यूआई) अपडेट पूरा करने में 12 सेकंड लगते हैं. हालांकि, ऐसा लग सकता है कि इससे परफ़ॉर्मेंस में गिरावट आई है, लेकिन असल में इससे उपयोगकर्ता को ज़्यादा सुझाव मिलते हैं. धीमी रेंडरिंग की समस्या इसलिए होती है, क्योंकि ऐप्लिकेशन, नॉन-ओएमटी वर्शन की तुलना में ज़्यादा फ़्रेम भेज रहा है. नॉन-ओएमटी वर्शन, कोई फ़्रेम नहीं भेजता. इसलिए, उपयोगकर्ता को पता होता है कि कुछ हो रहा है और वह यूज़र इंटरफ़ेस (यूआई) अपडेट होने के दौरान भी गेम खेलना जारी रख सकता है. इससे गेम खेलने का अनुभव काफ़ी बेहतर हो जाता है.
यह एक सोच-समझकर किया गया समझौता है: हम कम सुविधाओं वाले डिवाइसों के उपयोगकर्ताओं को बेहतर अनुभव देते हैं. साथ ही, बेहतर सुविधाओं वाले डिवाइसों के उपयोगकर्ताओं को भी कोई नुकसान नहीं पहुंचाते.
ओएमटी आर्किटेक्चर के असर
PROXX के उदाहरण से पता चलता है कि ओएमटी की मदद से, आपका ऐप्लिकेशन ज़्यादा डिवाइसों पर भरोसेमंद तरीके से काम करता है. हालांकि, इससे आपके ऐप्लिकेशन की स्पीड नहीं बढ़ती:
- इससे सिर्फ़ मुख्य थ्रेड का काम कम होता है, न कि कुल काम.
- वेब वर्कर और मुख्य थ्रेड के बीच ज़्यादा बातचीत होने की वजह से, कभी-कभी प्रोसेस थोड़ी धीमी हो सकती है.
फ़ायदों और नुकसानों को ध्यान में रखें
JavaScript के चलने के दौरान, मुख्य थ्रेड स्क्रोल करने जैसे उपयोगकर्ता इंटरैक्शन को प्रोसेस करने के लिए उपलब्ध रहता है. इसलिए, इंतज़ार का कुल समय थोड़ा ज़्यादा होने के बावजूद, ड्रॉप किए गए फ़्रेम कम होते हैं. उपयोगकर्ता को थोड़ा इंतज़ार कराना, फ़्रेम ड्रॉप करने से बेहतर है. ऐसा इसलिए, क्योंकि ड्रॉप किए गए फ़्रेम के लिए गड़बड़ी का मार्जिन कम होता है: फ़्रेम ड्रॉप होने में मिलीसेकंड लगते हैं, जबकि उपयोगकर्ता को इंतज़ार का समय महसूस होने से पहले, आपके पास सैकड़ों मिलीसेकंड होते हैं.
अलग-अलग डिवाइसों पर परफ़ॉर्मेंस का अनुमान नहीं लगाया जा सकता. इसलिए, ओएमटी आर्किटेक्चर का मकसद, जोखिम को कम करना है. इससे आपके ऐप्लिकेशन को रनटाइम की अलग-अलग स्थितियों में ज़्यादा बेहतर बनाया जा सकता है. इसका मकसद, पैरललाइज़ेशन की परफ़ॉर्मेंस को बेहतर बनाना नहीं है. हालांकि, इससे स्पीड में थोड़ी कमी आ सकती है, लेकिन इससे मिलने वाली सुरक्षा और बेहतर उपयोगकर्ता अनुभव के सामने यह कमी बहुत छोटी है.
टूल के बारे में जानकारी
वेब वर्कर अभी तक मुख्यधारा में नहीं हैं. इसलिए, webpack और Rollup जैसे ज़्यादातर मॉड्यूल टूल, उन्हें डिफ़ॉल्ट रूप से इस्तेमाल नहीं करते. (हालांकि, Parcel में यह सुविधा है!) खुशकिस्मती से, वेब वर्कर को webpack और Rollup के साथ काम करने लायक बनाने के लिए प्लग इन मौजूद हैं:
- webpack के लिए worker-plugin
- Rollup के लिए rollup-plugin-off-main-thread
खास जानकारी
हम यह पक्का करना चाहते हैं कि हमारे ऐप्लिकेशन ज़्यादा से ज़्यादा भरोसेमंद और ऐक्सेस करने में आसान हों. खास तौर पर, दुनिया भर में तेज़ी से फैल रहे बाज़ार में, हमें कम स्टोरेज वाले डिवाइसों के लिए सहायता उपलब्ध करानी होगी. ऐसा इसलिए, क्योंकि दुनिया भर में ज़्यादातर लोग ऐसे डिवाइसों से ही वेब ऐक्सेस कर रहे हैं. ओएमटी, ऐसे डिवाइसों पर परफ़ॉर्मेंस को बेहतर बनाने का एक बेहतरीन तरीका है. इससे हाई-एंड डिवाइसों के उपयोगकर्ताओं पर कोई बुरा असर नहीं पड़ता.
इसके अलावा, ओएमटी के अन्य फ़ायदे भी हैं:
- यह JavaScript को लागू करने की लागत को एक अलग थ्रेड में ले जाता है.
- यह पार्सिंग की लागत को कम करता है. इसका मतलब है कि यूज़र इंटरफ़ेस (यूआई) तेज़ी से बूट हो सकता है. इससे साइट का पहला एलिमेंट लोड होने में लगने वाला समय या टाइम टू इंटरैक्टिव कम हो सकता है. इससे आपका Lighthouse स्कोर बढ़ सकता है.
वेब वर्कर से डरने की ज़रूरत नहीं है. Comlink जैसे टूल, वर्कर के काम को आसान बना रहे हैं. साथ ही, उन्हें कई तरह के वेब ऐप्लिकेशन के लिए एक बेहतर विकल्प बना रहे हैं.