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