Bazel का पैरलल इवैलुएशन और इंक्रीमेंटलिटी मॉडल.
डेटा मॉडल
डेटा मॉडल में ये आइटम शामिल होते हैं:
SkyValue
. इन्हें नोड भी कहा जाता है.SkyValues
ऐसे ऑब्जेक्ट होते हैं जिनमें बदलाव नहीं किया जा सकता. इनमें, बिल्ड के दौरान बनाया गया सारा डेटा और बिल्ड के इनपुट शामिल होते हैं. उदाहरण के लिए: इनपुट फ़ाइलें, आउटपुट फ़ाइलें, टारगेट, और कॉन्फ़िगर किए गए टारगेट.SkyKey
.SkyValue
का रेफ़रंस देने के लिए, छोटा और अपरिवर्तनीय नाम. उदाहरण के लिए,FILECONTENTS:/tmp/foo
याPACKAGE://foo
.SkyFunction
. अपनी कुंजियों और उन पर निर्भर नोड के आधार पर नोड बनाता है.- नोड ग्राफ़. ऐसा डेटा स्ट्रक्चर जिसमें नोड के बीच डिपेंडेंसी का संबंध होता है.
Skyframe
. इंक्रीमेंटल इवैलुएशन फ़्रेमवर्क का कोड नेम, जिस पर Bazel आधारित है.
आकलन
किसी बिल्ड में, बिल्ड के अनुरोध को दिखाने वाले नोड का आकलन किया जाता है. हम इस दिशा में काम कर रहे हैं, लेकिन इसमें बहुत सारा लेगसी कोड है. सबसे पहले, इसका SkyFunction
ढूंढा जाता है और टॉप-लेवल SkyKey
की कुंजी के साथ कॉल किया जाता है. इसके बाद, फ़ंक्शन उन नोड का आकलन करने का अनुरोध करता है जिनकी ज़रूरत उसे टॉप-लेवल नोड का आकलन करने के लिए होती है. इससे, अन्य फ़ंक्शन का आह्वान होता है और यह सिलसिला तब तक चलता रहता है, जब तक लीफ़ नोड (आम तौर पर, ये फ़ाइल सिस्टम में इनपुट फ़ाइलों को दिखाने वाले नोड होते हैं) तक न पहुंच जाए. आखिर में, हमें टॉप-लेवल SkyValue
की वैल्यू, कुछ साइड इफ़ेक्ट (जैसे, फ़ाइल सिस्टम में आउटपुट फ़ाइलें) और बिल्ड में शामिल नोड के बीच की डिपेंडेंसी का डायरेक्टेड ऐसाइक्लिक ग्राफ़ मिलता है.
अगर SkyFunction
यह पहले से नहीं बता सकता कि उसे अपना काम करने के लिए किन सभी नोड की ज़रूरत है, तो वह एक से ज़्यादा पास में SkyKeys
का अनुरोध कर सकता है. एक आसान उदाहरण, इनपुट फ़ाइल नोड का आकलन करना है, जो एक सिर्फ़ लिंक है: फ़ंक्शन, फ़ाइल को पढ़ने की कोशिश करता है और उसे पता चलता है कि यह एक सिर्फ़ लिंक है. इसलिए, वह सिर्फ़ लिंक के टारगेट को दिखाने वाले फ़ाइल सिस्टम नोड को फ़ेच करता है. हालांकि, यह खुद भी एक सिमलिंक हो सकता है. ऐसे में, ओरिजनल फ़ंक्शन को अपने टारगेट को भी फ़ेच करना होगा.
कोड में फ़ंक्शन को इंटरफ़ेस SkyFunction
से दिखाया जाता है. साथ ही, SkyFunction.Environment
नाम के इंटरफ़ेस से उसे सेवाएं दी जाती हैं. फ़ंक्शन ये काम कर सकते हैं:
env.getValue
को कॉल करके, किसी दूसरे नोड के आकलन का अनुरोध करें. अगर नोड उपलब्ध है, तो उसकी वैल्यू दिखाई जाती है. अगर नोड उपलब्ध नहीं है, तोnull
दिखाया जाता है. साथ ही, फ़ंक्शन को भीnull
दिखाना चाहिए. बाद वाले मामले में, डिपेंडेंट नोड का आकलन किया जाता है. इसके बाद, ओरिजनल नोड बिल्डर को फिर से शुरू किया जाता है. हालांकि, इस बार उसीenv.getValue
कॉल सेnull
वैल्यू नहीं मिलेगी.env.getValues()
को कॉल करके, कई अन्य नोड के आकलन का अनुरोध करें. यह भी वही काम करता है. हालांकि, इसकी खास बात यह है कि इसकी मदद से, डिपेंडेंट नोड का आकलन एक साथ किया जाता है.- इनवोकेशन के दौरान कैलकुलेशन करना
- इसके साइड इफ़ेक्ट हों. उदाहरण के लिए, फ़ाइल सिस्टम में फ़ाइलें लिखना. ध्यान रखें कि दो अलग-अलग फ़ंक्शन एक-दूसरे के काम में रुकावट न डालें. आम तौर पर, लिखने से जुड़े साइड इफ़ेक्ट (जहां डेटा, Bazel से बाहर की ओर फ़्लो करता है) ठीक हैं, लेकिन पढ़ने से जुड़े साइड इफ़ेक्ट (जहां डेटा, रजिस्टर की गई डिपेंडेंसी के बिना Bazel में अंदर की ओर फ़्लो करता है) ठीक नहीं हैं. ऐसा इसलिए है, क्योंकि ये रजिस्टर नहीं की गई डिपेंडेंसी हैं और इनकी वजह से, इंक्रीमेंटल बिल्ड गलत हो सकते हैं.
SkyFunction
लागू करने के लिए, डेटा को ऐक्सेस करने का कोई दूसरा तरीका इस्तेमाल नहीं किया जाना चाहिए. जैसे, सीधे फ़ाइल सिस्टम को पढ़ना. ऐसा करने पर, Bazel पढ़ी गई फ़ाइल पर डेटा डिपेंडेंसी को रजिस्टर नहीं करता. इस वजह से, इंक्रीमेंटल बिल्ड गलत होते हैं.
जब किसी फ़ंक्शन के पास अपना काम करने के लिए ज़रूरत के मुताबिक डेटा हो जाता है, तो उसे null
वैल्यू के बजाय कोई दूसरी वैल्यू दिखानी चाहिए.
आकलन की इस रणनीति के कई फ़ायदे हैं:
- हर्मेटिकिटी. अगर फ़ंक्शन सिर्फ़ दूसरे नोड पर निर्भर होकर इनपुट डेटा का अनुरोध करते हैं, तो Bazel यह गारंटी दे सकता है कि इनपुट स्टेटस एक जैसा होने पर, एक ही डेटा वापस मिलेगा. अगर सभी Sky फ़ंक्शन डिटरमिनिस्टिक हैं, तो इसका मतलब है कि पूरा बिल्ड भी डिटरमिनिस्टिक होगा.
- सही और बेहतर इंक्रीमेंटलिटी. अगर सभी फ़ंक्शन का पूरा इनपुट डेटा रिकॉर्ड किया जाता है, तो Bazel सिर्फ़ उन नोड के सटीक सेट को अमान्य कर सकता है जिन्हें इनपुट डेटा में बदलाव होने पर अमान्य करना ज़रूरी है.
- पैरलल प्रोसेसिंग. फ़ंक्शन, डिपेंडेंसी का अनुरोध करके ही एक-दूसरे के साथ इंटरैक्ट कर सकते हैं. इसलिए, एक-दूसरे पर निर्भर न करने वाले फ़ंक्शन को एक साथ चलाया जा सकता है. साथ ही, Bazel यह गारंटी दे सकता है कि फ़ंक्शन को क्रम से चलाने पर भी वही नतीजा मिलेगा जो एक साथ चलाने पर मिलता है.
बढ़ोतरी
फ़ंक्शन, दूसरे नोड पर निर्भर करके ही इनपुट डेटा को ऐक्सेस कर सकते हैं. इसलिए, Bazel इनपुट फ़ाइलों से आउटपुट फ़ाइलों तक पूरा डेटा फ़्लो ग्राफ़ बना सकता है. साथ ही, इस जानकारी का इस्तेमाल सिर्फ़ उन नोड को फिर से बनाने के लिए कर सकता है जिन्हें असल में फिर से बनाने की ज़रूरत है: बदली गई इनपुट फ़ाइलों के सेट का रिवर्स ट्रांज़िटिव क्लोज़र.
खास तौर पर, इंक्रीमेंटलिटी की दो संभावित रणनीतियां मौजूद हैं: बॉटम-अप और टॉप-डाउन. कौनसा तरीका सबसे सही है, यह इस बात पर निर्भर करता है कि डिपेंडेंसी ग्राफ़ कैसा दिखता है.
बॉटम-अप इनवैलिडेशन के दौरान, ग्राफ़ बनने और बदले गए इनपुट के सेट के बारे में पता चलने के बाद, उन सभी नोड को अमान्य कर दिया जाता है जो बदली गई फ़ाइलों पर ट्रांज़िटिव तरीके से निर्भर होते हैं. यह तब सबसे सही होता है, जब हमें पता हो कि एक ही टॉप-लेवल नोड फिर से बनाया जाएगा. ध्यान दें कि नीचे से ऊपर की ओर अमान्य करने के लिए, पिछले बिल्ड की सभी इनपुट फ़ाइलों पर
stat()
चलाना ज़रूरी है. इससे यह पता चलता है कि उनमें बदलाव किया गया है या नहीं. बदली गई फ़ाइलों के बारे में जानने के लिए,inotify
या इससे मिलते-जुलते तरीके का इस्तेमाल करके, इस प्रोसेस को बेहतर बनाया जा सकता है.टॉप-डाउन इनवैलिडेशन के दौरान, टॉप-लेवल नोड के ट्रांसिशन क्लोज़र की जांच की जाती है. साथ ही, सिर्फ़ उन नोड को रखा जाता है जिनका ट्रांसिशन क्लोज़र क्लीन है. अगर हमें पता है कि मौजूदा नोड ग्राफ़ बड़ा है, लेकिन अगले बिल्ड में हमें इसका सिर्फ़ एक छोटा सबसेट चाहिए, तो यह तरीका बेहतर है: बॉटम-अप इनवैलिडेशन, पहले बिल्ड के बड़े ग्राफ़ को अमान्य कर देगा. वहीं, टॉप-डाउन इनवैलिडेशन, दूसरे बिल्ड के छोटे ग्राफ़ को ही अमान्य करता है.
फ़िलहाल, हम सिर्फ़ नीचे से ऊपर की ओर अमान्य होने की प्रोसेस करते हैं.
ज़्यादा डेटा पाने के लिए, हम बदलाव को कम करना का इस्तेमाल करते हैं: अगर किसी नोड को अमान्य कर दिया जाता है, लेकिन फिर से बनाने पर पता चलता है कि उसकी नई वैल्यू उसकी पुरानी वैल्यू जैसी ही है, तो इस नोड में हुए बदलाव की वजह से अमान्य किए गए नोड को “फिर से चालू” किया जाता है.
उदाहरण के लिए, अगर कोई व्यक्ति C++ फ़ाइल में किसी टिप्पणी में बदलाव करता है, तो इससे जनरेट की गई .o
फ़ाइल वही होगी. इसलिए, हमें लिंकर को फिर से कॉल करने की ज़रूरत नहीं है.
इंक्रीमेंटल लिंकिंग / कंपाइलेशन
इस मॉडल की मुख्य सीमा यह है कि किसी नोड को अमान्य करने का मतलब है कि उसे पूरी तरह से बदल दिया जाए: जब किसी डिपेंडेंसी में बदलाव होता है, तो डिपेंडेंट नोड को हमेशा नए सिरे से बनाया जाता है. भले ही, कोई बेहतर एल्गोरिदम मौजूद हो, जो बदलावों के आधार पर नोड की पुरानी वैल्यू में बदलाव कर दे. इसका इस्तेमाल इन स्थितियों में किया जा सकता है:
- इंक्रीमेंटल लिंकिंग
- जब किसी
.class
फ़ाइल में बदलाव करके उसे.jar
फ़ाइल में बदला जाता है, तो हम सैद्धांतिक तौर पर.jar
फ़ाइल को फिर से बनाने के बजाय, उसमें बदलाव कर सकते हैं.
फ़िलहाल, Bazel इन चीज़ों के साथ पूरी तरह से काम नहीं करता. हालांकि, इंक्रीमेंटल लिंकिंग के लिए कुछ हद तक काम करता है, लेकिन इसे Skyframe में लागू नहीं किया गया है. इसकी दो वजहें हैं: हमें परफ़ॉर्मेंस में सिर्फ़ सीमित फ़ायदा हुआ और यह पक्का करना मुश्किल था कि म्यूटेशन का नतीजा, क्लीन रीबिल्ड के नतीजे जैसा ही होगा. साथ ही, Google ऐसे बिल्ड को अहमियत देता है जिन्हें बिट-टू-बिट दोहराया जा सकता है.
अब तक, हम ऐप्लिकेशन को बेहतर तरीके से डेक्स करने के लिए, बिल्ड के ज़्यादा समय लेने वाले चरण को अलग-अलग हिस्सों में बांटते थे. इससे, हमें ऐप्लिकेशन की परफ़ॉर्मेंस का कुछ हद तक आकलन करने में मदद मिलती थी. यह तरीका इस तरह काम करता है: यह ऐप्लिकेशन की सभी क्लास को कई ग्रुप में बांटता है और उन पर अलग-अलग डेक्सिंग करता है. इस तरह, अगर किसी ग्रुप में क्लास में कोई बदलाव नहीं होता है, तो डेक्सिंग की प्रोसेस को फिर से शुरू करने की ज़रूरत नहीं होती.
Bazel के कॉन्सेप्ट पर आधारित मैपिंग
यहां SkyFunction
के कुछ ऐसे लागू होने के तरीकों के बारे में खास जानकारी दी गई है जिनका इस्तेमाल Bazel, बिल्ड करने के लिए करता है:
- FileStateValue.
lstat()
का नतीजा. मौजूदा फ़ाइलों में हुए बदलावों का पता लगाने के लिए, हम अतिरिक्त जानकारी भी कैलकुलेट करते हैं. यह Skyframe ग्राफ़ में सबसे निचले लेवल का नोड है और इस पर कोई डिपेंडेंसी नहीं होती. - FileValue. इसका इस्तेमाल, किसी फ़ाइल के असल कॉन्टेंट और/या रिज़ॉल्व किए गए पाथ के बारे में जानकारी देने वाली किसी भी चीज़ के लिए किया जाता है. यह संबंधित
FileStateValue
और ऐसे सभी सिमलिंक पर निर्भर करता है जिन्हें हल करना ज़रूरी है. जैसे,a/b
के लिएFileValue
कोa
औरa/b
के हल किए गए पाथ की ज़रूरत होती है.FileStateValue
के बीच का अंतर ज़रूरी है, क्योंकि कुछ मामलों में (उदाहरण के लिए, फ़ाइल सिस्टम ग्लोब (जैसे किsrcs=glob(["*/*.java"])
) का आकलन करने के लिए, फ़ाइल के कॉन्टेंट की ज़रूरत नहीं होती. - DirectoryListingValue. असल में,
readdir()
का नतीजा. यह डायरेक्ट्री से जुड़ेFileValue
पर निर्भर करता है. - PackageValue. यह किसी BUILD फ़ाइल के पार्स किए गए वर्शन को दिखाता है. यह उससे जुड़ी
BUILD
फ़ाइल केFileValue
पर निर्भर करता है. साथ ही, यह पैकेज में ग्लोब (BUILD
फ़ाइल के कॉन्टेंट को अंदरूनी तौर पर दिखाने वाला डेटा स्ट्रक्चर) को हल करने के लिए इस्तेमाल किए गए किसी भीDirectoryListingValue
पर भी ट्रांज़िशन करता है - ConfiguredTargetValue. कॉन्फ़िगर किए गए टारगेट को दिखाता है. यह टारगेट के विश्लेषण के दौरान जनरेट की गई कार्रवाइयों के सेट का टुपल होता है. साथ ही, यह कॉन्फ़िगर किए गए उन टारगेट को दी गई जानकारी भी दिखाता है जो इस टारगेट पर निर्भर करते हैं. यह उस
PackageValue
पर निर्भर करता है जिसमें संबंधित टारगेट मौजूद है, डायरेक्ट डिपेंडेंसी केConfiguredTargetValues
, और बिल्ड कॉन्फ़िगरेशन को दिखाने वाले खास नोड पर. - ArtifactValue. यह बिल्ड में मौजूद किसी फ़ाइल को दिखाता है. भले ही, वह सोर्स या आउटपुट आर्टफ़ैक्ट हो. आर्टफ़ैक्ट, फ़ाइलों के बराबर होते हैं और बिल्ड के चरणों को लागू करने के दौरान, फ़ाइलों को रेफ़र करने के लिए इनका इस्तेमाल किया जाता है. सोर्स फ़ाइलों के लिए, यह उससे जुड़े नोड के
FileValue
पर निर्भर करता है. आउटपुट आर्टफ़ैक्ट के लिए, यह उस कार्रवाई केActionExecutionValue
पर निर्भर करता है जिससे आर्टफ़ैक्ट जनरेट होता है. - ActionExecutionValue. किसी कार्रवाई के होने की जानकारी देता है. यह इनपुट फ़ाइलों के
ArtifactValues
पर निर्भर करता है. फ़िलहाल, यह कार्रवाई स्काई बटन में होती है. यह इस कॉन्सेप्ट के उलट है कि स्काई बटन छोटे होने चाहिए. हम इस अंतर को ठीक करने पर काम कर रहे हैं. ध्यान दें कि अगर Skyframe पर एक्सीक्यूशन फ़ेज़ नहीं चलाया जाता है, तोActionExecutionValue
औरArtifactValue
का इस्तेमाल नहीं किया जाता.