डिस्ट्रिब्यूट किए गए बिल्ड

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

रिमोट कैशिंग

डिस्ट्रिब्यूटेड बिल्ड का सबसे आसान टाइप, वह है जो सिर्फ़ remote caching का फ़ायदा लेता है. इसे पहली इमेज में दिखाया गया है.

रिमोट कैशिंग के साथ डिस्ट्रिब्यूटेड बिल्ड

पहली इमेज. रिमोट कैशिंग दिखाने वाला डिस्ट्रिब्यूटेड बिल्ड

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

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

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

रिमोट एक्ज़ीक्यूशन

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

रिमोट एक्ज़िक्यूशन सिस्टम

दूसरी इमेज. रिमोट एक्ज़ीक्यूशन सिस्टम

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

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

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

Google में डिस्ट्रिब्यूटेड बिल्ड

Google, 2008 से डिस्ट्रिब्यूटेड बिल्ड सिस्टम का इस्तेमाल कर रहा है. इसमें, रिमोट कैशिंग और रिमोट एक्ज़ीक्यूशन, दोनों का इस्तेमाल किया जाता है. इसे तीसरी इमेज में दिखाया गया है.

हाई-लेवल बिल्ड सिस्टम

तीसरी इमेज. Google का डिस्ट्रिब्यूटेड बिल्ड सिस्टम

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

Google के रिमोट एक्ज़ीक्यूशन सिस्टम को Forge कहा जाता है. Blaze (Bazel का इंटरनल इक्विवेलेंट) में मौजूद Forge क्लाइंट, जिसे डिस्ट्रिब्यूटर कहा जाता है, हर ऐक्शन के लिए हमारे डेटा सेंटर में चल रहे एक जॉब को अनुरोध भेजता है. इसे शेड्यूलर कहा जाता है. शेड्यूलर, ऐक्शन के नतीजों का कैश बनाए रखता है. इससे, अगर सिस्टम के किसी अन्य उपयोगकर्ता ने पहले ही वह ऐक्शन बना लिया है, तो वह तुरंत जवाब दे पाता है. अगर ऐसा नहीं है, तो वह ऐक्शन को एक क्यू में डाल देता है. एक्ज़ीक्यूटर जॉब का एक बड़ा पूल, इस क्यू से लगातार ऐक्शन पढ़ता है, उन्हें एक्ज़ीक्यूट करता है, और नतीजों को सीधे ObjFS Bigtable में सेव करता है. ये नतीजे, एक्ज़ीक्यूटर के लिए आगे की कार्रवाइयों के लिए उपलब्ध होते हैं. साथ ही, इन्हें objfsd के ज़रिए एंड यूज़र डाउनलोड कर सकता है.

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