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

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

रिमोट कैश मेमोरी

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

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

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

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

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

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

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

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

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

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

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

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

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

Google पर डिस्ट्रिब्यूट की गई बिल्ड

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

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

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

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

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

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