इस पेज पर, पर्सिस्टेंट वर्कर्स को इस्तेमाल करने का तरीका, फ़ायदे, ज़रूरी शर्तें, और सैंडबॉक्स की प्रोसेस पर वर्कर्स के पड़ने वाले असर के बारे में बताया गया है.
पर्सिस्टेंट वर्कर, लंबे समय तक चलने वाली प्रोसेस होती है. इसे Bazel सर्वर शुरू करता है. ये प्रोसेस, असल टूल (आम तौर पर कंपाइलर) के रैपर के तौर पर काम करती है या खुद ही एक टूल होती है. पर्सिस्टेंट वर्कर्स का फ़ायदा पाने के लिए, यह ज़रूरी है कि टूल क्रम में कंपाइल करने का काम करता हो. साथ ही, रैपर को टूल के एपीआई और यहां बताए गए अनुरोध/रिस्पॉन्स फ़ॉर्मैट के दायरे में ही अनुवाद करना होगा. एक ही बिल्ड में, एक ही वर्कर को --persistent_worker
फ़्लैग के साथ और उसके बिना भी कॉल किया जा सकता है. साथ ही, टूल को सही तरीके से शुरू करने और उससे इंटरैक्ट करने के साथ-साथ, बाहर निकलने पर वर्कर्स को बंद करने की ज़िम्मेदारी भी इसकी होती है. हर वर्कर इंस्टेंस को <outputBase>/bazel-workers
के तहत एक अलग वर्किंग डायरेक्ट्री असाइन की जाती है. हालांकि, उसमें chroot नहीं किया जाता.
पर्सिस्टेंट वर्कर्स का इस्तेमाल करना, कार्रवाई को लागू करने की एक रणनीति है. इससे स्टार्ट-अप ओवरहेड कम होता है, JIT कंपाइलेशन ज़्यादा होता है, और कार्रवाई को लागू करने के दौरान, ऐब्स्ट्रैक्ट सिंटैक्स ट्री को कैश मेमोरी में सेव किया जा सकता है. इस रणनीति से, लंबे समय तक चलने वाली प्रोसेस के लिए कई अनुरोध भेजकर, ये सुधार किए जाते हैं.
पर्सिस्टेंट वर्कर्स को कई भाषाओं के लिए लागू किया गया है. इनमें Java, Scala, Kotlin वगैरह शामिल हैं.
NodeJS रनटाइम का इस्तेमाल करने वाले प्रोग्राम, वर्क प्रोटोकॉल को लागू करने के लिए, @bazel/worker हेल्पर लाइब्रेरी का इस्तेमाल कर सकते हैं.
पर्सिस्टेंट वर्कर का इस्तेमाल करना
Bazel 0.27 और उसके बाद के वर्शन में, बिल्ड को डिफ़ॉल्ट रूप से पर्सिस्टेंट वर्कर्स का इस्तेमाल करके चलाया जाता है. हालांकि, रिमोट से चलाने को प्राथमिकता दी जाती है. जिन कार्रवाइयों के लिए पर्सिस्टेंट वर्कर्स काम नहीं करते, वैसी हर कार्रवाई के लिए Bazel, टूल का एक नया इंस्टेंस शुरू करता है. लागू टूल के नेमोनिक के लिए worker
रणनीति सेट करके, अपने बिल्ड को साफ़ तौर पर, स्थायी वर्कर्स का इस्तेमाल करने के लिए सेट किया जा सकता है. सबसे सही तरीके के तौर पर, इस उदाहरण में local
को worker
रणनीति के विकल्प के तौर पर शामिल किया गया है:
bazel build //my:target --strategy=Javac=worker,local
लोकल रणनीति के बजाय वर्कर्स रणनीति का इस्तेमाल करने से, कॉम्पाइलेशन की स्पीड काफ़ी बढ़ सकती है. हालांकि, यह इस बात पर निर्भर करता है कि इसे कैसे लागू किया गया है. Java के लिए, बिल्ड 2 से 4 गुना तेज़ हो सकते हैं. कभी-कभी, इंक्रीमेंटल कंपाइलेशन के लिए ज़्यादा भी हो सकते हैं. वर्कर्स की मदद से Bazel को कंपाइल करने में, सामान्य से करीब 2.5 गुना कम समय लगता है. ज़्यादा जानकारी के लिए, "कर्मचारियों की संख्या चुनना" सेक्शन देखें.
अगर आपके पास रिमोट बिल्ड एनवायरमेंट भी है, जो आपके लोकल बिल्ड एनवायरमेंट से मेल खाता है, तो एक्सपेरिमेंट के तौर पर उपलब्ध डाइनैमिक रणनीति का इस्तेमाल किया जा सकता है. यह रणनीति, रिमोट और वर्कर्स, दोनों तरह के एक्सीक्यूशन की तुलना करती है. डाइनैमिक रणनीति चालू करने के लिए, --experimental_spawn_scheduler फ़्लैग पास करें. यह रणनीति, वर्कर्स को अपने-आप रन कर देती है. इसलिए, worker
रणनीति तय करने की ज़रूरत नहीं है. हालांकि, विकल्प के तौर पर अब भी local
या sandboxed
का इस्तेमाल किया जा सकता है.
कर्मचारियों की संख्या चुनना
हर मेनिमोन के लिए, वर्कर्स इंस्टेंस की डिफ़ॉल्ट संख्या चार होती है. हालांकि, इसमें बदलाव किया जा सकता है. इसके लिए, worker_max_instances
फ़्लैग का इस्तेमाल करें. उपलब्ध सीपीयू का अच्छा इस्तेमाल करने और JIT कंपाइलेशन और कैश मेमोरी में हिट की संख्या के बीच एक समझौता होता है. ज़्यादा वर्कर्स होने पर, ज़्यादा टारगेट को JIT किए गए कोड को रन करने और कोल्ड कैश मेमोरी को ऐक्सेस करने के लिए, स्टार्ट-अप की लागत चुकानी होगी. अगर आपको कुछ ही टारगेट बनाने हैं, तो एक वर्कर, कंपाइलेशन की स्पीड और रिसॉर्स के इस्तेमाल के लिए बेहतर विकल्प हो सकता है. उदाहरण के लिए, समस्या #8586 देखें.
worker_max_instances
फ़्लैग, हर नेमोनिक और फ़्लैग सेट (नीचे देखें) के लिए, वर्कर इंस्टेंस की ज़्यादा से ज़्यादा संख्या सेट करता है. इसलिए, अगर डिफ़ॉल्ट वैल्यू को बनाए रखा जाता है, तो मिक्स किए गए सिस्टम में काफ़ी ज़्यादा मेमोरी का इस्तेमाल किया जा सकता है. इंक्रीमेंटल बिल्ड के लिए, एक से ज़्यादा वर्कर्स इंस्टेंस का फ़ायदा और भी कम होता है.
इस ग्राफ़ में, 64 जीबी रैम वाले 6-कोर हाइपर-थ्रेडेड Intel Xeon 3.5 GHz Linux वर्कस्टेशन पर, Bazel (टारगेट //src:bazel
) के लिए, शुरू से कंपाइल करने में लगने वाला समय दिखाया गया है. हर वर्कर कॉन्फ़िगरेशन के लिए, पांच क्लीन बिल्ड रन किए जाते हैं और आखिरी चार का औसत लिया जाता है.
पहली इमेज. क्लीन बिल्ड की परफ़ॉर्मेंस में हुए सुधारों का ग्राफ़.
इस कॉन्फ़िगरेशन के लिए, दो वर्कर सबसे तेज़ी से कॉम्पाइल करते हैं. हालांकि, एक वर्कर की तुलना में इसमें सिर्फ़ 14% का सुधार होता है. अगर आपको कम मेमोरी का इस्तेमाल करना है, तो एक वर्कर का विकल्प चुनें.
आम तौर पर, इंक्रीमेंटल कंपाइलेशन से ज़्यादा फ़ायदा मिलता है. क्लीन बिल्ड कम ही होते हैं. हालांकि, कंपाइल करने के बीच एक फ़ाइल में बदलाव करना आम बात है. खास तौर पर, टेस्ट-ड्रिवन डेवलपमेंट में. ऊपर दिए गए उदाहरण में, कुछ ऐसी कार्रवाइयां भी हैं जो Java के बजाय किसी दूसरी भाषा में की गई हैं. इनसे, इंक्रीमेंटल कंपाइल करने में लगने वाले समय पर असर पड़ सकता है.
AbstractContainerizingSandboxedSpawn.java में किसी इंटरनल स्ट्रिंग कॉन्स्टेंट को बदलने के बाद, सिर्फ़ Java सोर्स को फिर से कॉम्पाइल करने (//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar
) पर, प्रोसेस की स्पीड तीन गुना बढ़ जाती है. इसमें, एक वॉर्मअप बिल्ड को छोड़कर, औसतन 20 इंक्रीमेंटल बिल्ड होते हैं:
दूसरी इमेज. इंक्रीमेंटल बिल्ड की परफ़ॉर्मेंस में हुए सुधारों का ग्राफ़.
स्पीड में होने वाली बढ़ोतरी, किए जा रहे बदलाव पर निर्भर करती है. ऊपर दी गई स्थिति में, आम तौर पर इस्तेमाल होने वाले कॉन्स्टेंट को बदलने पर, प्रोसेस की स्पीड में छह गुना की बढ़ोतरी होती है.
पर्सिस्टेंट वर्कर्स में बदलाव करना
वर्कर्स को स्टार्ट-अप फ़्लैग की जानकारी देने के लिए, --worker_extra_flag
फ़्लैग को पास किया जा सकता है. इसके लिए नेमोनिक की मदद ली जा सकती है. उदाहरण के लिए, --worker_extra_flag=javac=--debug
को पास करने पर, सिर्फ़ Javac के लिए डीबगिंग की सुविधा चालू हो जाती है.
इस फ़्लैग के इस्तेमाल के लिए, सिर्फ़ एक वर्कर फ़्लैग सेट किया जा सकता है. साथ ही, यह सिर्फ़ एक नेमोनिक के लिए सेट किया जा सकता है.
वर्कर्स को हर नेमोनिक के लिए अलग से ही नहीं बनाया जाता, बल्कि उनके स्टार्ट-अप फ़्लैग में होने वाले बदलावों के लिए भी बनाया जाता है. नेमोनिक और स्टार्ट-अप फ़्लैग के हर कॉम्बिनेशन को WorkerKey
में जोड़ा जाता है. साथ ही, हर WorkerKey
के लिए, ज़्यादा से ज़्यादा worker_max_instances
वर्कर्स बनाए जा सकते हैं. अगले सेक्शन में देखें कि ऐक्शन कॉन्फ़िगरेशन, सेट-अप फ़्लैग की जानकारी कैसे दे सकता है.
--worker_sandboxing
फ़्लैग पास करने पर, हर वर्कर अनुरोध अपने सभी इनपुट के लिए, एक अलग सैंडबॉक्स डायरेक्ट्री का इस्तेमाल करता है. सैंडबॉक्स को सेट अप करने में कुछ ज़्यादा समय लगता है. खास तौर पर, macOS पर. हालांकि, इससे सटीक जानकारी मिलने की गारंटी मिलती है.
--worker_quit_after_build
फ़्लैग का इस्तेमाल, मुख्य रूप से डीबग करने और प्रोफ़ाइल बनाने के लिए किया जाता है. यह फ़्लैग, बिल्ड पूरा होने के बाद सभी वर्कर्स को बंद कर देता है. यह जानकारी --worker_verbose
को पास करके भी मिल सकती है कि वर्कर्स क्या कर रहे हैं. यह फ़्लैग, WorkRequest
में verbosity
फ़ील्ड में दिखता है. इससे वर्कर्स को लागू करने के बारे में ज़्यादा जानकारी मिलती है.
वर्कर्स अपने लॉग, <outputBase>/bazel-workers
डायरेक्ट्री में सेव करते हैं. उदाहरण के लिए, /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log
.
फ़ाइल के नाम में वर्कर आईडी और नेमोनिक होते हैं. हर नेमोनिक के लिए एक से ज़्यादा WorkerKey
हो सकती हैं. इसलिए, आपको किसी नेमोनिक के लिए worker_max_instances
से ज़्यादा लॉग फ़ाइलें दिख सकती हैं.
Android बिल्ड की ज़्यादा जानकारी के लिए, Android बिल्ड की परफ़ॉर्मेंस वाले पेज पर जाएं.
पर्सिस्टेंट वर्कर्स लागू करना
वर्कर बनाने के तरीके के बारे में ज़्यादा जानकारी के लिए, परसिस्टेंट वर्कर्स बनाना पेज देखें.
इस उदाहरण में, JSON का इस्तेमाल करने वाले वर्कर्स के लिए Starlark कॉन्फ़िगरेशन दिखाया गया है:
args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
ctx.actions.write(
output = args_file,
content = "\n".join(["-g", "-source", "1.5"] + ctx.files.srcs),
)
ctx.actions.run(
mnemonic = "SomeCompiler",
executable = "bin/some_compiler_wrapper",
inputs = inputs,
outputs = outputs,
arguments = [ "-max_mem=4G", "@%s" % args_file.path],
execution_requirements = {
"supports-workers" : "1", "requires-worker-protocol" : "json" }
)
इस परिभाषा के साथ, इस ऐक्शन का पहला इस्तेमाल, कमांड लाइन /bin/some_compiler -max_mem=4G --persistent_worker
को रन करने से शुरू होगा. Foo.java
को कंपाइल करने का अनुरोध, कुछ ऐसा दिखेगा:
ध्यान दें: प्रोटोकॉल बफ़र स्पेसिफ़िकेशन में "स्नेक केस" (request_id
) का इस्तेमाल किया जाता है, जबकि JSON प्रोटोकॉल में "कैमल केस" (requestId
) का इस्तेमाल किया जाता है. इस दस्तावेज़ में, हम JSON के उदाहरणों में कैमल केस का इस्तेमाल करेंगे. हालांकि, प्रोटोकॉल के बावजूद फ़ील्ड के बारे में बात करते समय, स्नेक केस का इस्तेमाल करेंगे.
{
"arguments": [ "-g", "-source", "1.5", "Foo.java" ]
"inputs": [
{ "path": "symlinkfarm/input1", "digest": "d49a..." },
{ "path": "symlinkfarm/input2", "digest": "093d..." },
],
}
वर्कर को यह डेटा, stdin
पर न्यूलाइन डीलिमिटेड JSON फ़ॉर्मैट में मिलता है, क्योंकि requires-worker-protocol
को JSON पर सेट किया गया है. इसके बाद, वर्कर ऐक्शन करता है और अपने stdout पर Bazel को JSON फ़ॉर्मैट में WorkResponse
भेजता है. इसके बाद, Bazel इस जवाब को पार्स करता है और मैन्युअल तरीके से उसे WorkResponse
प्रोटो में बदल देता है. JSON के बजाय, बाइनरी में कोड किए गए प्रोटोबफ़ का इस्तेमाल करके, असोसिएटेड वर्कर के साथ इंटरैक्ट करने के लिए, requires-worker-protocol
को proto
पर सेट किया जाएगा. जैसे:
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
अगर प्रोग्राम रन से जुड़ी ज़रूरी शर्तों में requires-worker-protocol
नहीं है, तो Bazel डिफ़ॉल्ट रूप से प्रोटोबफ़ का इस्तेमाल करके वर्कर्स से इंटरैक्ट करेगा.
Bazel, नेमोनिक और शेयर किए गए फ़्लैग से WorkerKey
का पता लगाता है. इसलिए, अगर इस कॉन्फ़िगरेशन में max_mem
पैरामीटर को बदलने की अनुमति दी जाती है, तो इस्तेमाल की गई हर वैल्यू के लिए एक अलग वर्कर स्पॉन किया जाएगा. अगर बहुत ज़्यादा वैरिएशन इस्तेमाल किए जाते हैं, तो इससे ज़्यादा मेमोरी खर्च हो सकती है.
फ़िलहाल, हर वर्कर एक बार में सिर्फ़ एक अनुरोध प्रोसेस कर सकता है. एक्सपेरिमेंट के तौर पर उपलब्ध मल्टीप्लेक्स वर्कर्स सुविधा की मदद से, एक से ज़्यादा थ्रेड का इस्तेमाल किया जा सकता है. हालांकि, इसके लिए ज़रूरी है कि इस्तेमाल किया जा रहा टूल मल्टीथ्रेड हो और रैपर को इसकी जानकारी देने के लिए सेट अप किया गया हो.
इस GitHub रिपॉज़िटरी में, आपको Java और Python, दोनों में लिखे गए वर्कर रैपर्स के उदाहरण दिख सकते हैं. अगर JavaScript या TypeScript में काम किया जा रहा है, तो @bazel/worker package और nodejs worker example मददगार हो सकते हैं.
वर्कर्स, सैंडबॉक्सिंग पर कैसे असर डालते हैं?
डिफ़ॉल्ट रूप से worker
रणनीति का इस्तेमाल करने पर, ऐक्शन local
रणनीति की तरह ही सैंडबॉक्स में रन नहीं करता. सैंडबॉक्स में सभी वर्कर्स को चलाने के लिए, --worker_sandboxing
फ़्लैग सेट किया जा सकता है. इससे यह पक्का किया जा सकता है कि टूल के हर एक बार इस्तेमाल होने पर, सिर्फ़ वे इनपुट फ़ाइलें दिखें जो उसमें होनी चाहिए. हालांकि, यह टूल अब भी अनुरोधों के बीच, कैश मेमोरी के ज़रिए जानकारी लीक कर सकता है. dynamic
रणनीति का इस्तेमाल करने के लिए,
वर्कर को सैंडबॉक्स में रखना ज़रूरी है.
वर्कर्स के साथ कंपाइलर कैश मेमोरी का सही इस्तेमाल करने के लिए, हर इनपुट फ़ाइल के साथ एक डाइजेस्ट पास किया जाता है. इसलिए, कंपाइलर या रैपर, फ़ाइल को पढ़े बिना यह जांच कर सकता है कि इनपुट अब भी मान्य है या नहीं.
अनचाही कैश मेमोरी से बचने के लिए इनपुट डाइजेस्ट का इस्तेमाल करने पर भी, सैंडबॉक्स किए गए वर्कर्स, पूरी तरह से सैंडबॉक्स किए गए वर्कर्स की तुलना में कम सख्त सैंडबॉक्सिंग देते हैं. इसकी वजह यह है कि टूल, ऐसे अन्य इंटरनल स्टेटस को सेव कर सकता है जिस पर पिछले अनुरोधों का असर पड़ा है.
मल्टीप्लेक्स वर्कर्स को सिर्फ़ तब सैंडबॉक्स किया जा सकता है, जब वर्कर्स को लागू करने की सुविधा सैंडबॉक्सिंग के साथ काम करती हो. साथ ही, सैंडबॉक्सिंग को --experimental_worker_multiplex_sandboxing
फ़्लैग की मदद से अलग से रन करना होगा. ज़्यादा जानकारी के लिए, डिज़ाइन दस्तावेज़ देखें).
इस बारे में और पढ़ें
पर्सिंस्टेंट वर्कर्स के बारे में ज़्यादा जानकारी के लिए, ये देखें:
- ओरिजनल पर्सिस्टेंट वर्कर्स ब्लॉग पोस्ट
- Haskell लागू करने के बारे में जानकारी
- माइक मोरेटी की ब्लॉग पोस्ट
- Bazel की मदद से फ़्रंट-एंड डेवलपमेंट: Angular/TypeScript और Asana के साथ काम करने वाले पर्सिस्टेंट वर्कर्स
- Bazel की रणनीतियों के बारे में जानकारी
- bazel-discuss मेलिंग सूची पर, काम करने वालों की रणनीति के बारे में जानकारी