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

समस्या की शिकायत करें सोर्स देखें

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

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

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

अनुमान

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

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

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

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

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

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

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

बड़े पैमाने पर डेटा स्टोर करने की जगह

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

ऐसी भाषा लिखें जो ब्यौरे में दी गई हो

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

ऐतिहासिक

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

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

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

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

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

व्यक्ति

कुछ ऐसे स्वाभाविक प्रॉपर्टी होते हैं जो लिखने के नियमों को चुनौती भरा बना देती हैं. साथ ही, कुछ सबसे सामान्य प्रॉपर्टी के बारे में नीचे दिए गए सेक्शन में बताया गया है.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

क्वाड्रेटिक जटिलता से बचने के लिए कस्टम कलेक्शन क्लास

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

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