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

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

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

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

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

अनुमान

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

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

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

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

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

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

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

डेटा स्टोर करने की बहुत बड़ी जगह

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

बिल्ड जैसी भाषा

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

ऐतिहासिक

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

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

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

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

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

मूल

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

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

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

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

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

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

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

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

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

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

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

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

मामले को और भी खराब बनाने के लिए, 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) मेमोरी होती है.

क्वाड्रेटिक कॉम्प्लेक्सिटी से बचने के लिए कस्टम कलेक्शन क्लास

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

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