लिखने के नियमों की चुनौतियां

किसी समस्या की शिकायत करना सोर्स देखना Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

इस पेज पर, Bazel के बेहतर नियम लिखने से जुड़ी समस्याओं और चुनौतियों के बारे में खास जानकारी दी गई है.

ज़रूरी शर्तों की खास जानकारी

  • अनुमान: सही जानकारी, थ्रुपुट, इस्तेमाल में आसानी, और इंतज़ार का समय (लेटेंसी) को ध्यान में रखें
  • मान्य शर्त: बड़े पैमाने पर रिपॉज़िटरी
  • मान्य: BUILD जैसी ब्यौरा भाषा
  • पुराना: लोड करने, विश्लेषण करने, और लागू करने के बीच का फ़र्क़ अब काम का नहीं है. हालांकि, इसका असर अब भी एपीआई पर पड़ता है
  • इंट्रिन्सिक: रिमोट से प्रोग्राम चलाना और कैश मेमोरी में सेव करना मुश्किल है
  • इंट्रिन्सिक: सही और तेज़ इंक्रीमेंटल बिल्ड के लिए, बदलाव की जानकारी का इस्तेमाल करने के लिए, असामान्य कोडिंग पैटर्न की ज़रूरत होती है
  • इंट्रिन्सिक: क्वाड्रैटिक टाइम और मेमोरी खर्च से बचना मुश्किल है

अनुमान

यहां बिल्ड सिस्टम के बारे में कुछ मान्यताएं दी गई हैं. जैसे, सटीक होने की ज़रूरत, इस्तेमाल में आसानी, थ्रूपुट, और बड़े पैमाने पर रिपॉज़िटरी. यहां दिए गए सेक्शन में, इन मान्यताओं के बारे में बताया गया है. साथ ही, नियमों को असरदार तरीके से लिखने के लिए दिशा-निर्देश भी दिए गए हैं.

सही नतीजे, थ्रूपुट, इस्तेमाल में आसानी, और इंतज़ार का समय

हम मानते हैं कि इंक्रीमेंटल बिल्ड के लिए, बिल्ड सिस्टम को सबसे पहले सही होना चाहिए. किसी सोर्स ट्री के लिए, एक ही बिल्ड का आउटपुट हमेशा एक जैसा होना चाहिए. भले ही, आउटपुट ट्री कैसा भी दिखे. इसका मतलब है कि Bazel को किसी भी बिल्ड चरण में शामिल हर इनपुट के बारे में पता होना चाहिए, ताकि इनपुट में कोई बदलाव होने पर वह उस चरण को फिर से चला सके. Bazel के सटीक होने की सीमाएं होती हैं, क्योंकि यह कुछ जानकारी लीक करता है. जैसे, बिल्ड की तारीख / समय. साथ ही, यह कुछ तरह के बदलावों को अनदेखा करता है. जैसे, फ़ाइल एट्रिब्यूट में बदलाव. सैंडबॉक्सिंग की मदद से, यह पक्का किया जा सकता है कि कोई भी ऐसी इनपुट फ़ाइल न पढ़ी जाए जिसकी जानकारी नहीं दी गई है. सिस्टम की अंदरूनी सीमाओं के अलावा, सही होने से जुड़ी कुछ समस्याएं भी हैं. इनमें से ज़्यादातर समस्याएं, फ़ाइलसेट या C++ नियमों से जुड़ी हैं. ये दोनों समस्याएं हल करना मुश्किल है. हम इन समस्याओं को ठीक करने के लिए लगातार काम कर रहे हैं.

बिल्ड सिस्टम का दूसरा लक्ष्य, ज़्यादा थ्रूपुट हासिल करना है. हम रिमोट तौर पर प्रोग्राम चलाने की सेवा के लिए, मशीन के मौजूदा ऐलोकेशन में क्या किया जा सकता है, इसकी सीमाओं को लगातार बढ़ा रहे हैं. अगर रिमोट टास्क को लागू करने की सेवा पर ज़्यादा लोड पड़ता है, तो कोई भी काम नहीं कर पाएगा.

इसके बाद, इस्तेमाल में आसानी की बात आती है. रिमोट तौर पर प्रोग्राम चलाने की सेवा के एक जैसे (या मिलते-जुलते) फ़ुटप्रिंट वाले कई सही तरीकों में से, हम उस तरीके को चुनते हैं जिसका इस्तेमाल करना आसान हो.

इंतज़ार का समय, किसी बिल्ड को शुरू करने से लेकर ज़रूरी नतीजा पाने में लगने वाले समय को दिखाता है. भले ही, वह नतीजा पास या फ़ेल हुए टेस्ट का टेस्ट लॉग हो या BUILD फ़ाइल में टाइप होने वाली गड़बड़ी का मैसेज.

ध्यान दें कि ये लक्ष्य अक्सर ओवरलैप होते हैं. रिमोट तरीके से प्रोसेस करने की सेवा के थ्रुपुट के तौर पर, इंतज़ार का समय उतना ही ज़रूरी है जितना कि आसानी से इस्तेमाल करने के लिए सही होना.

बड़े पैमाने पर डेटा स्टोर करने की सुविधा

बिल्ड सिस्टम को बड़े रिपॉज़िटरी के स्केल पर काम करना चाहिए. बड़े स्केल का मतलब है कि यह एक ही हार्ड ड्राइव में फ़िट नहीं होता. इसलिए, डेवलपर की सभी मशीनों पर पूरा चेकआउट करना असंभव है. मीडियम साइज़ के किसी बिल्ड को, हज़ारों BUILD फ़ाइलों को पढ़ना और पार्स करना होगा. साथ ही, लाखों ग्लोब का आकलन करना होगा. सिद्धांत रूप से, एक ही मशीन पर सभी BUILD फ़ाइलें पढ़ी जा सकती हैं. हालांकि, हम अब तक तय समय और मेमोरी में ऐसा नहीं कर पाए हैं. इसलिए, यह ज़रूरी है कि BUILD फ़ाइलों को अलग से लोड और पार्स किया जा सके.

BUILD जैसी जानकारी वाली भाषा

इस संदर्भ में, हम एक ऐसी कॉन्फ़िगरेशन भाषा का इस्तेमाल करते हैं जो लाइब्रेरी और बाइनरी नियमों के एलान में BUILD फ़ाइलों और उनकी इंटरडिपेंडेंसी से मिलती-जुलती है. BUILD फ़ाइलों को अलग से पढ़ा और पार्स किया जा सकता है. साथ ही, हम सोर्स फ़ाइलों को देखने से भी बचते हैं. हालांकि, हम यह देखना ज़रूर चाहते हैं कि वे मौजूद हैं या नहीं.

ऐतिहासिक

Bazel के वर्शन के बीच अंतर होते हैं, जिनकी वजह से समस्याएं आती हैं. इनमें से कुछ समस्याओं के बारे में यहां बताया गया है.

लोड करने, विश्लेषण करने, और लागू करने के बीच का फ़र्क़ अब पुराना हो गया है. हालांकि, अब भी इसका असर एपीआई पर पड़ता है

तकनीकी तौर पर, किसी नियम के लिए यह जानना काफ़ी है कि किसी कार्रवाई को रिमोट तौर पर लागू करने से ठीक पहले, उस कार्रवाई की इनपुट और आउटपुट फ़ाइलें क्या हैं. हालांकि, ओरिजनल Bazel कोड बेस में, लोड किए जा रहे पैकेज को अलग-अलग रखा जाता था. इसके बाद, कॉन्फ़िगरेशन (मुख्य रूप से कमांड-लाइन फ़्लैग) का इस्तेमाल करके नियमों का विश्लेषण किया जाता था और उसके बाद ही कोई कार्रवाई की जाती थी. यह फ़र्क़, आज भी नियमों के एपीआई का हिस्सा है. हालांकि, Bazel के कोर को अब इसकी ज़रूरत नहीं है. ज़्यादा जानकारी यहां दी गई है.

इसका मतलब है कि Rules API को नियम इंटरफ़ेस के बारे में जानकारी चाहिए. जैसे, इसमें कौनसे एट्रिब्यूट हैं और एट्रिब्यूट किस तरह के हैं. कुछ अपवादों में, एपीआई, लोडिंग के दौरान कस्टम कोड को चलाने की अनुमति देता है, ताकि आउटपुट फ़ाइलों के इनपुट नाम और एट्रिब्यूट की इनपुट वैल्यू का हिसाब लगाया जा सके. उदाहरण के लिए, 'foo' नाम का java_library नियम, 'libfoo.jar' नाम का आउटपुट जनरेट करता है. इसका रेफ़रंस, बिल्ड ग्राफ़ में मौजूद अन्य नियमों से लिया जा सकता है.

इसके अलावा, किसी नियम का विश्लेषण करने पर, किसी भी सोर्स फ़ाइल को पढ़ा नहीं जा सकता या किसी कार्रवाई के आउटपुट की जांच नहीं की जा सकती. इसके बजाय, उसे बिल्ड चरणों और आउटपुट फ़ाइल के नामों का एक आंशिक डायरेक्टेड बायपार्टाइट ग्राफ़ जनरेट करना होगा. यह ग्राफ़, सिर्फ़ नियम और उसकी डिपेंडेंसी से तय होता है.

मूल

कुछ ऐसी प्रॉपर्टी होती हैं जिनके लिए नियम लिखना मुश्किल होता है. इनमें से कुछ सबसे सामान्य प्रॉपर्टी के बारे में यहां बताया गया है.

रिमोट से प्रोग्राम चलाना और कैश मेमोरी में सेव करना मुश्किल है

रिमोट से प्रोग्राम चलाने और कैश मेमोरी का इस्तेमाल करने से, बड़ी रिपॉज़िटरी में बिल्ड करने में लगने वाला समय, एक मशीन पर बिल्ड करने के मुकाबले करीब दो गुना कम हो जाता है. हालांकि, इसे जिस स्केल पर काम करना है वह काफ़ी बड़ा है: Google की रिमोट एक्सीक्यूशन सेवा को हर सेकंड बड़ी संख्या में अनुरोधों को हैंडल करने के लिए डिज़ाइन किया गया है. साथ ही, यह प्रोटोकॉल, सेवा के पक्ष पर ग़ैर-ज़रूरी राउंड ट्रिप और ग़ैर-ज़रूरी काम से सावधानी से बचता है.

फ़िलहाल, प्रोटोकॉल के मुताबिक यह ज़रूरी है कि बिल्ड सिस्टम को किसी कार्रवाई के सभी इनपुट पहले से पता हों. इसके बाद, बिल्ड सिस्टम एक यूनीक ऐक्शन फ़िंगरप्रिंट कैलकुलेट करता है और शेड्यूलर से कैश मेमोरी में मौजूद डेटा का इस्तेमाल करने के लिए कहता है. अगर कैश हिट मिलता है, तो शेड्यूलर, आउटपुट फ़ाइलों के डाइजेस्ट के साथ जवाब देता है. बाद में, डाइजेस्ट की मदद से फ़ाइलों को ऐक्सेस किया जाता है. हालांकि, इससे Bazel के नियमों पर पाबंदियां लगती हैं. इन नियमों के तहत, सभी इनपुट फ़ाइलों को पहले से एलान करना ज़रूरी होता है.

सही और तेज़ इंक्रीमेंटल बिल्ड के लिए, बदलाव की जानकारी का इस्तेमाल करने के लिए, असामान्य कोडिंग पैटर्न की ज़रूरत होती है

ऊपर, हमने बताया था कि सही तरीके से काम करने के लिए, Bazel को उन सभी इनपुट फ़ाइलों के बारे में पता होना चाहिए जो किसी बिल्ड चरण में इस्तेमाल की जाती हैं. इससे यह पता चलता है कि वह बिल्ड चरण अब भी अप-टू-डेट है या नहीं. पैकेज लोड करने और नियमों के विश्लेषण के लिए भी यही बात लागू होती है. हमने Skyframe को इस काम के लिए डिज़ाइन किया है. Skyframe एक ग्राफ़ लाइब्रेरी और आकलन फ़्रेमवर्क है. यह किसी लक्ष्य नोड (जैसे, 'इन विकल्पों के साथ //foo बनाएं') को उसके घटकों में बांट देता है. इसके बाद, इन घटकों का आकलन करके उन्हें जोड़कर यह नतीजा दिया जाता है. इस प्रोसेस के तहत, Skyframe पैकेज पढ़ता है, नियमों का विश्लेषण करता है, और कार्रवाइयां करता है.

हर नोड पर, Skyframe यह ट्रैक करता है कि किसी भी नोड ने अपने आउटपुट का हिसाब लगाने के लिए किन नोड का इस्तेमाल किया. यह ट्रैकिंग, लक्ष्य नोड से लेकर इनपुट फ़ाइलों (जो Skyframe नोड भी हैं) तक की जाती है. इस ग्राफ़ को मेमोरी में साफ़ तौर पर दिखाने से, बिल्ड सिस्टम को यह पता लगाने में मदद मिलती है कि किसी इनपुट फ़ाइल में किए गए बदलाव से किन नोड पर असर पड़ा है. इनमें इनपुट फ़ाइल बनाना या मिटाना भी शामिल है. साथ ही, आउटपुट ट्री को उसकी सही स्थिति में वापस लाने के लिए, कम से कम काम किया जाता है.

इसके तहत, हर नोड डिपेंडेंसी डिस्कवरी प्रोसेस करता है. हर नोड, डिपेंडेंसी का एलान कर सकता है. इसके बाद, उन डिपेंडेंसी के कॉन्टेंट का इस्तेमाल करके, और भी डिपेंडेंसी का एलान किया जा सकता है. सिद्धांत रूप से, यह हर नोड के लिए एक थ्रेड वाले मॉडल के साथ अच्छी तरह से मैप होता है. हालांकि, मीडियम साइज़ के बिल्ड में हज़ारों Skyframe नोड होते हैं. इन्हें मौजूदा Java टेक्नोलॉजी की मदद से आसानी से नहीं बनाया जा सकता. साथ ही, पुरानी वजहों से, फ़िलहाल हमें Java का इस्तेमाल करना पड़ता है. इसलिए, इसमें लाइटवेट थ्रेड और कंटिन्यूएशन नहीं होते.

इसके बजाय, Bazel तय साइज़ वाले थ्रेड पूल का इस्तेमाल करता है. हालांकि, इसका मतलब है कि अगर कोई नोड ऐसी डिपेंडेंसी का एलान करता है जो अभी उपलब्ध नहीं है, तो हो सकता है कि हमें उस आकलन को रोकना पड़े और डिपेंडेंसी उपलब्ध होने पर, उसे फिर से शुरू करना पड़े. ऐसा शायद किसी दूसरी थ्रेड में किया जाए. इसका मतलब है कि नोड को बार-बार ऐसा नहीं करना चाहिए. किसी नोड को सीरियल क्रम में N डिपेंडेंसी का एलान करने पर, उसे N बार रीस्टार्ट किया जा सकता है. इससे O(N^2) समय लगता है. इसके बजाय, हम डिपेंडेंसी के बारे में पहले से ही एक साथ जानकारी देने की कोशिश करते हैं. इसके लिए, कभी-कभी कोड को फिर से व्यवस्थित करना पड़ता है. इसके अलावा, रीस्टार्ट की संख्या को कम करने के लिए, किसी नोड को कई नोड में बांटना पड़ता है.

ध्यान दें कि फ़िलहाल, यह टेक्नोलॉजी नियमों वाले एपीआई में उपलब्ध नहीं है. इसके बजाय, नियमों वाले एपीआई को अब भी लोड करने, विश्लेषण करने, और लागू करने के लेगसी कॉन्सेप्ट का इस्तेमाल करके तय किया जाता है. हालांकि, एक बुनियादी पाबंदी यह है कि अन्य नोड को ऐक्सेस करने के लिए, फ़्रेमवर्क का इस्तेमाल करना ज़रूरी है, ताकि वह उनसे जुड़ी डिपेंडेंसी को ट्रैक कर सके. भले ही, बिल्ड सिस्टम को जिस भाषा में लागू किया गया हो या नियम जिस भाषा में लिखे गए हों (ये एक ही भाषा के होने ज़रूरी नहीं हैं), नियम बनाने वाले लोगों को Skyframe को बायपास करने वाली स्टैंडर्ड लाइब्रेरी या पैटर्न का इस्तेमाल नहीं करना चाहिए. Java के लिए, इसका मतलब है कि java.io.File के साथ-साथ, रिफ़्लेक्शन के किसी भी टाइप और इनमें से किसी भी काम करने वाली लाइब्रेरी का इस्तेमाल न करना. इन लो-लेवल इंटरफ़ेस के डिपेंडेंसी इंजेक्शन की सुविधा वाली लाइब्रेरी को, Skyframe के लिए अब भी सही तरीके से सेट अप करना होगा.

हमारा सुझाव है कि नियम बनाने वाले लोगों को, भाषा के पूरे रनटाइम के बारे में पहले से न बताएं. ऐसे एपीआई का गलती से इस्तेमाल होने का खतरा बहुत ज़्यादा है - पहले Bazel के कई गड़बड़ियां, असुरक्षित एपीआई का इस्तेमाल करने वाले नियमों की वजह से हुई थीं. भले ही, ये नियम Bazel टीम या डोमेन के अन्य विशेषज्ञों ने लिखे थे.

क्वाड्रैटिक समय और मेमोरी खर्च से बचना मुश्किल है

Skyframe की ज़रूरी शर्तों, Java का इस्तेमाल करने से जुड़ी पुरानी समस्याओं, और नियमों के एपीआई के पुराने होने के अलावा, लाइब्रेरी और बाइनरी नियमों पर आधारित किसी भी बिल्ड सिस्टम में, गलती से क्वाड्रैटिक समय या मेमोरी खर्च होने की समस्या एक बुनियादी समस्या है. आम तौर पर, दो तरह के पैटर्न होते हैं जिनकी वजह से मेमोरी का इस्तेमाल चौथे घात में होता है. साथ ही, प्रोसेस में लगने वाला समय भी चौथे घात में होता है.

  1. लाइब्रेरी के नियमों की चेन - इस मामले में, लाइब्रेरी के नियमों की चेन A, B पर निर्भर करती है, जो C पर निर्भर करती है वगैरह. इसके बाद, हमें इन नियमों के ट्रांसिशन क्लोज़र के आधार पर, कुछ प्रॉपर्टी का हिसाब लगाना है. जैसे, हर लाइब्रेरी के लिए Java रनटाइम क्लासपाथ या C++ लिंकर कमांड. हम आसानी से, स्टैंडर्ड सूची को लागू कर सकते हैं. हालांकि, इससे पहले से ही मेमोरी का इस्तेमाल दोगुना हो जाता है: पहली लाइब्रेरी में क्लासपाथ पर एक एंट्री होती है, दूसरी में दो, तीसरी में तीन वगैरह. कुल मिलाकर, 1+2+3+...+N = O(N^2) एंट्री होती हैं.

  2. एक ही लाइब्रेरी के नियमों पर आधारित बाइनरी नियम - इस मामले पर विचार करें जहां बाइनरी का एक सेट एक ही लाइब्रेरी के नियमों पर निर्भर करता है — जैसे, अगर आपके पास कई टेस्ट नियम हैं जो एक ही लाइब्रेरी कोड की जांच करते हैं. मान लें कि N नियमों में से आधे नियम बाइनरी नियम हैं और आधे लाइब्रेरी नियम हैं. अब मान लें कि हर बाइनरी, लाइब्रेरी के नियमों के ट्रांज़िशन क्लोज़र के आधार पर कैलकुलेट की गई किसी प्रॉपर्टी की कॉपी बनाती है. जैसे, Java रनटाइम क्लासपाथ या C++ लिंकर कमांड लाइन. उदाहरण के लिए, यह C++ लिंक ऐक्शन की कमांड लाइन स्ट्रिंग के तौर पर दिखाया जा सकता है. N/2 एलिमेंट की N/2 कॉपी, O(N^2) मेमोरी होती है.

कस्टम कलेक्शन क्लास, ताकि द्विघात जटिलता से बचा जा सके

इन दोनों स्थितियों से Bazel पर काफ़ी असर पड़ता है. इसलिए, हमने कस्टम कलेक्शन क्लास का एक सेट शुरू किया है. यह हर चरण में कॉपी करने से बचकर, मेमोरी में जानकारी को असरदार तरीके से कंप्रेस करता है. इनमें से ज़्यादातर डेटा स्ट्रक्चर में सेमैंटिक सेट होते हैं. इसलिए, हमने इसे depset कहा है. इसे इंटरनल लागू करने में NestedSet भी कहा जाता है. पिछले कुछ सालों में, Bazel की मेमोरी खपत को कम करने के लिए किए गए ज़्यादातर बदलाव, पहले इस्तेमाल किए गए तरीकों के बजाय depset का इस्तेमाल करने के लिए किए गए थे.

माफ़ करें, डेपसेट का इस्तेमाल करने से सभी समस्याएं अपने-आप हल नहीं होतीं. खास तौर पर, हर नियम में डेपसेट को दोहराने से, फिर से चौथे घात का समय लगने लगता है. नेस्ट किए गए सेट में, सामान्य कलेक्शन क्लास के साथ इंटरऑपरेबिलिटी की सुविधा देने के लिए, कुछ हेल्पर मेथड भी होते हैं. माफ़ करें, इनमें से किसी एक मेथड में गलती से नेस्ट किए गए सेट को पास करने पर, कॉपी करने की सुविधा चालू हो जाती है. साथ ही, रैंडम ऐक्सेस मेमोरी का इस्तेमाल फिर से शुरू हो जाता है.