ऑफ़-मेन-थ्रेड आर्किटेक्चर से, आपके ऐप्लिकेशन की विश्वसनीयता और उपयोगकर्ता अनुभव को बेहतर बनाया जा सकता है.
पिछले 20 सालों में, वेब में काफ़ी बदलाव हुए हैं. अब यह कुछ स्टाइल और इमेज वाले स्टैटिक दस्तावेज़ों से बदलकर, जटिल और डाइनैमिक ऐप्लिकेशन में बदल गया है. हालांकि, एक चीज़ में ज़्यादा बदलाव नहीं हुआ है: हमारे पास हर ब्राउज़र टैब के लिए सिर्फ़ एक थ्रेड होती है. कुछ मामलों में, हमारी साइटों को रेंडर करने और JavaScript चलाने के लिए, एक से ज़्यादा थ्रेड का इस्तेमाल किया जाता है.
इस वजह से, मुख्य थ्रेड पर बहुत ज़्यादा काम का बोझ बढ़ गया है. वेब ऐप्लिकेशन के ज़्यादा कॉम्प्लेक्स होने पर, मुख्य थ्रेड परफ़ॉर्मेंस के लिए एक अहम बॉटलनेक बन जाती है. इससे भी ज़्यादा मुश्किल बात यह है कि किसी उपयोगकर्ता के लिए मुख्य थ्रेड पर कोड चलाने में लगने वाले समय का अंदाज़ा लगाना लगभग नामुमकिन है. ऐसा इसलिए, क्योंकि डिवाइस की क्षमताओं का परफ़ॉर्मेंस पर बहुत ज़्यादा असर पड़ता है. उपयोगकर्ता अलग-अलग तरह के डिवाइसों से वेब ऐक्सेस करते हैं. इनमें कम सुविधाओं वाले फ़ीचर फ़ोन से लेकर, ज़्यादा पावर और ज़्यादा रिफ़्रेश रेट वाले फ़्लैगशिप डिवाइस शामिल हैं. इसलिए, यह अनुमान लगाना और भी मुश्किल हो जाएगा कि उपयोगकर्ता किस डिवाइस से वेब ऐक्सेस कर रहा है.
अगर हमें यह पक्का करना है कि हमारे वेब ऐप्लिकेशन, परफ़ॉर्मेंस से जुड़े दिशा-निर्देशों का पालन कर रहे हैं, तो हमें अपने कोड को मुख्य थ्रेड (ओएमटी) से बाहर चलाने के तरीके चाहिए. जैसे, वेबसाइट कैसा परफ़ॉर्म कर रही है इसके बारे में जानकारी देने वाली रिपोर्ट. यह रिपोर्ट, लोगों की सोच और मनोविज्ञान के बारे में अनुभवजन्य डेटा पर आधारित होती है.
वेब वर्कर क्यों इस्तेमाल करें?
JavaScript डिफ़ॉल्ट रूप से, सिंगल-थ्रेड वाली भाषा है. यह टास्क को मुख्य थ्रेड पर चलाती है. हालांकि, वेब वर्कर मुख्य थ्रेड से अलग थ्रेड बनाने की सुविधा देते हैं. इससे डेवलपर, मुख्य थ्रेड से अलग थ्रेड में काम कर पाते हैं. वेब वर्कर का दायरा सीमित होता है और ये सीधे तौर पर DOM को ऐक्सेस नहीं करते. हालांकि, अगर कोई ऐसा काम है जिसे पूरा करने में ज़्यादा समय लगता है और जिसकी वजह से मुख्य थ्रेड पर ज़्यादा लोड पड़ता है, तो वेब वर्कर बहुत फ़ायदेमंद हो सकते हैं.
वेबसाइट की परफ़ॉर्मेंस की अहम जानकारी के लिए, मुख्य थ्रेड से अलग काम करने से फ़ायदा मिल सकता है. खास तौर पर, मुख्य थ्रेड से वेब वर्कर को काम सौंपने से, मुख्य थ्रेड के लिए विवाद कम हो सकता है. इससे पेज के पेज के रिस्पॉन्स में लगने वाले समय (आईएनपी) की रिस्पॉन्सिवनेस मेट्रिक बेहतर हो सकती है. जब मुख्य थ्रेड को प्रोसेस करने के लिए कम काम करना पड़ता है, तो वह उपयोगकर्ता के इंटरैक्शन का जवाब ज़्यादा तेज़ी से दे सकती है.
मुख्य थ्रेड पर कम काम करने से, खास तौर पर स्टार्टअप के दौरान, लंबे टास्क कम हो जाते हैं. इससे सबसे बड़े कॉन्टेंटफ़ुल पेंट (एलसीपी) को भी फ़ायदा मिलता है. एलसीपी एलिमेंट को रेंडर करने के लिए, मुख्य थ्रेड के समय की ज़रूरत होती है. यह समय, टेक्स्ट या इमेज को रेंडर करने के लिए होता है. ये दोनों ही एलसीपी के सामान्य एलिमेंट हैं. मुख्य थ्रेड के काम को कम करके, यह पक्का किया जा सकता है कि आपके पेज के एलसीपी एलिमेंट को ऐसे काम से ब्लॉक न किया जाए जिसे वेब वर्कर हैंडल कर सकता है.
वेब वर्कर के साथ थ्रेडिंग
आम तौर पर, अन्य प्लैटफ़ॉर्म पर एक साथ कई काम किए जा सकते हैं. इसके लिए, आपको किसी थ्रेड को एक फ़ंक्शन देना होता है, जो आपके प्रोग्राम के बाकी हिस्सों के साथ-साथ चलता है. दोनों थ्रेड से एक ही वैरिएबल को ऐक्सेस किया जा सकता है. साथ ही, शेयर किए गए इन संसाधनों को ऐक्सेस करने की प्रोसेस को म्यूटेक्स और सेमाफ़ोर के साथ सिंक किया जा सकता है, ताकि रेस कंडीशन को रोका जा सके.
JavaScript में, हमें वेब वर्कर से मिलती-जुलती सुविधा मिल सकती है. ये 2007 से उपलब्ध हैं और 2012 से सभी मुख्य ब्राउज़र पर काम करते हैं. वेब वर्कर, मुख्य थ्रेड के साथ-साथ काम करते हैं. हालांकि, ओएस थ्रेडिंग के उलट, वे वैरिएबल शेयर नहीं कर सकते.
वेब वर्कर बनाने के लिए, वर्कर कंस्ट्रक्टर को कोई फ़ाइल पास करें. इससे वह फ़ाइल अलग थ्रेड में चलने लगती है:
const worker = new Worker("./worker.js");
postMessage API का इस्तेमाल करके, वेब वर्कर को मैसेज भेजें. मैसेज की वैल्यू को 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: making web workers less work
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 वैरिएबल, वेब वर्कर में मौजूद वैरिएबल की तरह ही काम करता है. हालांकि, हर फ़ंक्शन वैल्यू के बजाय, वैल्यू के लिए प्रॉमिस दिखाता है.
आपको वेब वर्कर में कौन सा कोड ट्रांसफ़र करना चाहिए?
वेब वर्कर के पास DOM और कई एपीआई का ऐक्सेस नहीं होता. जैसे, WebUSB, WebRTC या Web Audio. इसलिए, अपने ऐप्लिकेशन के उन हिस्सों को वर्कर में नहीं रखा जा सकता जो इस तरह के ऐक्सेस पर निर्भर होते हैं. हालांकि, कोड के हर छोटे हिस्से को वर्कर में ले जाने से, मुख्य थ्रेड पर ज़्यादा जगह मिल जाती है. इस जगह का इस्तेमाल उन कामों के लिए किया जा सकता है जो ज़रूरी हैं. जैसे, यूज़र इंटरफ़ेस को अपडेट करना.
वेब डेवलपर के लिए एक समस्या यह है कि ज़्यादातर वेब ऐप्लिकेशन, ऐप्लिकेशन में हर चीज़ को व्यवस्थित करने के लिए Vue या React जैसे यूज़र इंटरफ़ेस (यूआई) फ़्रेमवर्क पर निर्भर करते हैं. हर चीज़ फ़्रेमवर्क का एक कॉम्पोनेंट होती है. इसलिए, यह स्वाभाविक रूप से DOM से जुड़ी होती है. इससे ओएमटी आर्किटेक्चर पर माइग्रेट करना मुश्किल हो जाएगा.
हालांकि, अगर हम ऐसे मॉडल पर स्विच करते हैं जिसमें यूज़र इंटरफ़ेस (यूआई) से जुड़ी समस्याओं को अन्य समस्याओं से अलग किया जाता है, तो वेब वर्कर फ़्रेमवर्क पर आधारित ऐप्लिकेशन के लिए भी काफ़ी मददगार हो सकते हैं. जैसे, स्टेट मैनेजमेंट. PROXX को इसी तरीके से बनाया गया है.
PROXX: ओएमटी की केस स्टडी
Google Chrome की टीम ने PROXX को Minesweeper के क्लोन के तौर पर बनाया है. यह प्रोग्रेसिव वेब ऐप्लिकेशन की ज़रूरी शर्तों को पूरा करता है. जैसे, ऑफ़लाइन काम करना और उपयोगकर्ता को बेहतर अनुभव देना. दुर्भाग्य से, गेम के शुरुआती वर्शन, फ़ीचर फ़ोन जैसे सीमित संसाधनों वाले डिवाइसों पर ठीक से काम नहीं करते थे. इससे टीम को यह पता चला कि मुख्य थ्रेड एक बॉटलनेक है.
टीम ने वेब वर्कर का इस्तेमाल करके, गेम की विज़ुअल स्थिति को उसके लॉजिक से अलग करने का फ़ैसला किया:
- मुख्य थ्रेड, ऐनिमेशन और ट्रांज़िशन को रेंडर करने का काम करता है.
- वेब वर्कर, गेम लॉजिक को मैनेज करता है. यह पूरी तरह से कंप्यूटेशनल होता है.
OMT की वजह से, PROXX के फ़ीचर फ़ोन वर्शन की परफ़ॉर्मेंस पर काफ़ी असर पड़ा. ओएमटी के अलावा अन्य वर्शन में, उपयोगकर्ता के इंटरैक्ट करने के बाद यूज़र इंटरफ़ेस (यूआई) छह सेकंड के लिए फ़्रीज़ हो जाता है. इसमें कोई फ़ीडबैक नहीं मिलता. साथ ही, उपयोगकर्ता को छह सेकंड तक इंतज़ार करना पड़ता है. इसके बाद ही, वह कोई दूसरी कार्रवाई कर पाता है.
हालांकि, ओएमटी वर्शन में यूज़र इंटरफ़ेस (यूआई) को अपडेट होने में बारह सेकंड लगते हैं. हालांकि, इससे परफ़ॉर्मेंस में गिरावट आती है, लेकिन इससे उपयोगकर्ता को ज़्यादा फ़ीडबैक मिलता है. ऐसा इसलिए होता है, क्योंकि ऐप्लिकेशन, नॉन-ओएमटी वर्शन की तुलना में ज़्यादा फ़्रेम शिप कर रहा है. नॉन-ओएमटी वर्शन कोई भी फ़्रेम शिप नहीं कर रहा है. इसलिए, उपयोगकर्ता को पता चलता है कि कुछ हो रहा है. साथ ही, यूज़र इंटरफ़ेस (यूआई) अपडेट होने पर, वह गेम खेलना जारी रख सकता है. इससे गेम का अनुभव काफ़ी बेहतर हो जाता है.
यह एक सोच-समझकर किया गया समझौता है: हम कम क्षमता वाले डिवाइसों के उपयोगकर्ताओं को ऐसा अनुभव देते हैं जो बेहतर लगता है. साथ ही, हम ज़्यादा क्षमता वाले डिवाइसों के उपयोगकर्ताओं पर कोई असर नहीं डालते.
ओएमटी आर्किटेक्चर के निहितार्थ
PROXX के उदाहरण से पता चलता है कि OMT की मदद से, आपका ऐप्लिकेशन ज़्यादा डिवाइसों पर भरोसेमंद तरीके से काम करता है. हालांकि, इससे ऐप्लिकेशन की परफ़ॉर्मेंस बेहतर नहीं होती:
- आपने सिर्फ़ मुख्य थ्रेड से काम को दूसरी थ्रेड पर ट्रांसफ़र किया है, काम को कम नहीं किया है.
- वेब वर्कर और मुख्य थ्रेड के बीच ज़्यादा कम्यूनिकेशन होने की वजह से, कभी-कभी चीज़ें थोड़ी धीमी हो सकती हैं.
अटैचमेंट में मिलने वाले फ़ायदों के बारे में जानें
JavaScript के चलने के दौरान मुख्य थ्रेड, स्क्रोल करने जैसे उपयोगकर्ता इंटरैक्शन को प्रोसेस करने के लिए उपलब्ध होती है. इसलिए, फ़्रेम ड्रॉप होने की संख्या कम होती है. हालांकि, इंतज़ार करने का कुल समय थोड़ा ज़्यादा हो सकता है. फ़्रेम छोड़ने के बजाय, उपयोगकर्ता को थोड़ा इंतज़ार कराना बेहतर होता है. ऐसा इसलिए, क्योंकि छोड़े गए फ़्रेम के लिए गड़बड़ी की मार्जिन कम होती है: फ़्रेम छोड़ने में मिलीसेकंड लगते हैं, जबकि उपयोगकर्ता को इंतज़ार का समय महसूस होने से पहले आपके पास सैकड़ों मिलीसेकंड होते हैं.
अलग-अलग डिवाइसों पर परफ़ॉर्मेंस अलग-अलग हो सकती है. इसलिए, ओएमटी आर्किटेक्चर का मकसद जोखिम को कम करना है. इसका मतलब है कि रनटाइम की अलग-अलग स्थितियों में आपके ऐप्लिकेशन को ज़्यादा मज़बूत बनाना. इसका मकसद, पैरलल प्रोसेसिंग से मिलने वाले परफ़ॉर्मेंस फ़ायदों को हासिल करना नहीं है. तेज़ रफ़्तार के मुकाबले, बेहतर UX और ज़्यादा भरोसेमंद होना ज़्यादा ज़रूरी है.
टूलिंग के बारे में जानकारी
वेब वर्कर अभी तक मुख्यधारा में नहीं आए हैं. इसलिए, webpack और Rollup जैसे ज़्यादातर मॉड्यूल टूल, इन्हें बॉक्स से बाहर सपोर्ट नहीं करते हैं. (हालांकि, Parcel की सुविधा उपलब्ध है!) अच्छी बात यह है कि वेब वर्कर को webpack और Rollup के साथ काम करने के लिए, प्लगिन उपलब्ध हैं:
- webpack के लिए worker-plugin
- Rollup के लिए rollup-plugin-off-main-thread
संक्षेप में
हमारा मकसद है कि हमारे ऐप्लिकेशन ज़्यादा से ज़्यादा भरोसेमंद और आसानी से ऐक्सेस किए जा सकें. खास तौर पर, ऐसे समय में जब दुनिया भर में इंटरनेट का इस्तेमाल बढ़ रहा है. इसलिए, हमें सीमित संसाधनों वाले डिवाइसों के लिए भी ऐप्लिकेशन बनाने होंगे. दुनिया भर में ज़्यादातर लोग, वेब को ऐक्सेस करने के लिए इन्हीं डिवाइसों का इस्तेमाल करते हैं. ओएमटी, इस तरह के डिवाइसों पर परफ़ॉर्मेंस बढ़ाने का एक बेहतर तरीका है. इससे हाई-एंड डिवाइसों का इस्तेमाल करने वाले लोगों पर बुरा असर नहीं पड़ता.
इसके अलावा, ओएमटी के ये फ़ायदे भी हैं:
- यह JavaScript को लागू करने की लागत को अलग थ्रेड में ले जाता है.
- इससे पार्सिंग की लागत कम हो जाती है. इसका मतलब है कि यूज़र इंटरफ़ेस (यूआई) तेज़ी से बूट हो सकता है. इससे फ़र्स्ट कॉन्टेंटफ़ुल पेंट या इंटरैक्टिव होने में लगने वाला समय कम हो सकता है. इससे Lighthouse स्कोर बढ़ सकता है.
वेब वर्कर का इस्तेमाल करना मुश्किल नहीं है. Comlink जैसे टूल, वर्कर के काम को आसान बना रहे हैं. साथ ही, उन्हें कई तरह के वेब ऐप्लिकेशन के लिए एक बेहतर विकल्प बना रहे हैं.