इस पेज पर, पर्सिस्टेंट वर्कर्स का इस्तेमाल करने का तरीका, फ़ायदे, ज़रूरी शर्तें, और वर्कर्स के सैंडबॉक्सिंग पर पड़ने वाले असर के बारे में बताया गया है.
पर्सिस्टेंट वर्कर्स, लंबे समय तक चलने वाली प्रोसेस होती हैं. इन्हें Bazel सर्वर शुरू करता है. ये प्रोसेस, असल टूल (आम तौर पर कंपाइलर) के रैपर के तौर पर काम करती हैं या खुद ही टूल होती हैं. लगातार काम करने वाले लोगों का फ़ायदा उठाने के लिए, टूल को
कंपाइलेशन का क्रम काम करना चाहिए. साथ ही, रैपर को टूल के एपीआई और नीचे बताए गए अनुरोध/जवाब फ़ॉर्मैट के बीच अनुवाद करना चाहिए. एक ही बिल्ड में --persistent_worker
फ़्लैग के साथ और उसके बिना भी एक ही वर्कर को कॉल किया जा सकता है और वह टूल को सही तरीके से शुरू करने, उससे बात करने के साथ-साथ बाहर निकलने पर काम करने वाले लोगों को बंद करने के लिए ज़िम्मेदार होता है. हर वर्क इंस्टेंस को <outputBase>/bazel-workers
के तहत एक अलग वर्किंग डायरेक्ट्री असाइन की जाती है. हालांकि, उसमें chroot नहीं किया जाता.
पर्सिस्टेंट वर्कर्स का इस्तेमाल करना, कार्रवाई को लागू करने की एक रणनीति है. इससे स्टार्ट-अप ओवरहेड कम होता है, JIT कंपाइलेशन ज़्यादा होता है, और कार्रवाई को लागू करने के दौरान, उदाहरण के लिए, एब्स्ट्रैक्ट सिंटैक्स ट्री को कैश मेमोरी में सेव किया जा सकता है. इस रणनीति से, लंबे समय तक चलने वाली प्रोसेस के लिए कई अनुरोध भेजकर, ये सुधार किए जाते हैं.
पर्सिस्टेंट वर्कर्स को कई भाषाओं के लिए लागू किया गया है. इनमें Java, Scala, Kotlin वगैरह शामिल हैं.
NodeJS रनटाइम का इस्तेमाल करने वाले प्रोग्राम, वर्कर प्रोटोकॉल को लागू करने के लिए, @baaz/worker हेल्पर लाइब्रेरी का इस्तेमाल कर सकते हैं.
पर्सिस्टेंट वर्कर का इस्तेमाल करना
Basel 0.27 और उसके बाद के वर्शन, बिल्ड को एक्ज़ीक्यूट करते समय डिफ़ॉल्ट रूप से परसिस्टेंट वर्कर का इस्तेमाल करते हैं. हालांकि, रिमोट एक्ज़ीक्यूशन को प्राथमिकता दी जाती है. जिन कार्रवाइयों के लिए लगातार काम करने वाले लोग काम नहीं करते उनके लिए,
बेज़ल हर कार्रवाई के लिए टूल इंस्टेंस शुरू कर देता है. स्थायी कर्मचारियों का इस्तेमाल करने के लिए, लागू टूल के लिए 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 गीगाहर्ट्ज़ Linux वर्कस्टेशन पर, बेज़ल (टारगेट
//src:bazel
) के शुरू होने से शुरू होने वाले कंपाइलेशन समय को दिखाता है. हर वर्कर कॉन्फ़िगरेशन के लिए, पांच क्लीन बिल चलाए जाते हैं और आखिरी चार का औसत लिया जाता है.
पहली इमेज. क्लीन बिल्ड की परफ़ॉर्मेंस में हुए सुधारों का ग्राफ़.
इस कॉन्फ़िगरेशन के लिए, दो वर्कर्स सबसे तेज़ कंपाइल करते हैं. हालांकि, एक वर्कर्स की तुलना में इसमें सिर्फ़ 14% का सुधार होता है. अगर आपको कम मेमोरी का इस्तेमाल करना है, तो एक कर्मचारी अच्छा विकल्प है.
आम तौर पर, इंक्रीमेंटल कंपाइलेशन से ज़्यादा फ़ायदा मिलता है. क्लीन बिल्ड बहुत कम ही होते हैं, लेकिन कंपाइलेशन के बीच एक फ़ाइल में बदलाव करना आम बात है. खास तौर पर, टेस्ट-ड्रिवन डेवलपमेंट में. ऊपर दिए गए उदाहरण में, कुछ ऐसी कार्रवाइयां भी हैं जो Java के बजाय, किसी दूसरी भाषा में की गई हैं. इनसे, इंक्रीमेंटल कंपाइल करने में लगने वाले समय पर असर पड़ सकता है.
AbstractContainerizingSandboxedSpawn.java में इंटरनल स्ट्रिंग को बदलने के बाद सिर्फ़ Java सोर्स को फिर से कंपाइल करने से स्पीड तीन गुना बढ़ जाती है (एक वॉर्मअप बिल्ड के बिना, औसतन 20 इंक्रीमेंटल बिल्ड
अस्वीकार किए गए)://src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar
दूसरी इमेज. इंक्रीमेंटल बिल्ड की परफ़ॉर्मेंस में हुए सुधारों का ग्राफ़.
स्पीड अप बदलाव, किए गए बदलाव पर निर्भर करता है. ऊपर दी गई स्थिति में, आम तौर पर इस्तेमाल होने वाले कॉन्स्टेंट को बदलने पर, प्रोसेस की स्पीड में छह गुना की बढ़ोतरी होती है.
हमेशा चलने वाले वर्कर में बदलाव करना
वर्कर्स को स्टार्ट-अप फ़्लैग की जानकारी देने के लिए, --worker_extra_flag
फ़्लैग पास किया जा सकता है. उदाहरण के लिए, --worker_extra_flag=javac=--debug
को पास करने पर, सिर्फ़ Javac के लिए डीबगिंग की सुविधा चालू हो जाती है.
इस फ़्लैग के हर इस्तेमाल के लिए, सिर्फ़ एक वर्कर फ़्लैग सेट किया जा सकता है. साथ ही, इसे सिर्फ़ एक याद रखने के लिए इस्तेमाल किया जा सकता है.
वर्कर्स को हर मेमोनिक के लिए अलग से ही नहीं बनाया जाता, बल्कि उनके स्टार्ट-अप फ़्लैग में होने वाले बदलावों के लिए भी बनाया जाता है. स्मृति सहायक और स्टार्ट-अप फ़्लैग के हर कॉम्बिनेशन को WorkerKey
में जोड़ा जाता है. साथ ही, हर WorkerKey
के लिए ज़्यादा से ज़्यादा worker_max_instances
वर्कर्स बनाए जा सकते हैं. अगला सेक्शन देखें और जानें कि ऐक्शन
कॉन्फ़िगरेशन, सेट-अप फ़्लैग कैसे तय कर सकता है.
--high_priority_workers
फ़्लैग का इस्तेमाल करके, कोई ऐसा स्मृति चिह्न तय किया जा सकता है जिसे सामान्य प्राथमिकता वाले स्मृति चिह्नों के बजाय चलाया जाना चाहिए. इससे उन कार्रवाइयों को प्राथमिकता देने में मदद मिल सकती है जो हमेशा क्रिटिकल पाथ में होती हैं. अगर ज़्यादा प्राथमिकता वाले दो या उससे ज़्यादा वर्कर अनुरोधों पर कार्रवाई करते हैं, तो दूसरे सभी वर्कर को चलने से रोक दिया जाता है. इस फ़्लैग का इस्तेमाल एक से ज़्यादा बार किया जा सकता है.
--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 पर बेज़ल को JSON फ़ॉर्मैट में, WorkResponse
भेजता है. इसके बाद, Bazel इस जवाब को पार्स करता है और मैन्युअल तरीके से उसे WorkResponse
प्रोटो में बदल देता है. JSON के बजाय, बाइनरी कोड में बदले गए प्रोटोबफ़ का इस्तेमाल करके जुड़े कर्मचारी से संपर्क करने के लिए, requires-worker-protocol
को proto
पर सेट किया जाएगा. उदाहरण के लिए:
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
अगर आपने प्रोग्राम चलाने से जुड़ी ज़रूरी शर्तों में requires-worker-protocol
शामिल नहीं किया है, तो Bazel डिफ़ॉल्ट रूप से, प्रोग्राम के बीच डेटा भेजने के लिए protobuf का इस्तेमाल करेगा.
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 मेलिंग सूची पर, काम करने वालों की रणनीति के बारे में जानकारी