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

समस्या की शिकायत करें सोर्स देखें Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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

खास जानकारी देने से जुड़ी ज़रूरी शर्तें

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

अनुमान

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

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

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

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

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

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

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

बड़े पैमाने पर रिपॉज़िटरी

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

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

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

ऐतिहासिक

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

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

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

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

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

इंट्रिंसिक

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

रिमोट एक्ज़ीक्यूशन और कैश मेमोरी का इस्तेमाल करना मुश्किल है

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

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

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

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

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

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

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

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

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

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

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

  1. लाइब्रेरी के नियमों की चेन - लाइब्रेरी के नियमों की चेन का उदाहरण देखें. इसमें A, B पर निर्भर करता है, 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 की मेमोरी खपत को कम करने के लिए किए गए ज़्यादातर बदलाव, पहले इस्तेमाल किए गए फ़ॉर्मैट के बजाय depsets का इस्तेमाल करने से जुड़े थे.

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