हर पैमाने पर Git
बड़े पैमाने पर Git रिपॉज़िटरी होस्ट करना किसी दुःस्वप्न से कम नहीं है। जब Linus Torvalds ने नरक के सूचना प्रबंधक का पहला संस्करण डिज़ाइन किया (दरअसल, यही Git की टैगलाइन है, देख लें), तो उनके मन में एक बहुत ही विशिष्ट उपयोग का मामला था: उनका अपना। वे Linux Kernel के विकास में इस्तेमाल हो रहे वितरित संस्करण नियंत्रण सिस्टम BitKeeper को बदलना चाहते थे। ज़ाहिर है, इसका विकल्प भी वितरित होना था। Kernel एक असामान्य सॉफ़्टवेयर प्रोजेक्ट है; यह बेहद विकेंद्रीकृत है और इसके अनेक उपप्रणालियों के लिए अलग-अलग रखरखावकर्ता हैं। वितरित संस्करण नियंत्रण सिस्टम इस वर्कफ़्लो के लिए स्वाभाविक रूप से उपयुक्त है।
बीस साल बाद, Git उद्योग का एक मानक बन चुका है, लेकिन सच यह है कि इसकी वितरित प्रकृति अब फायदे से ज़्यादा बाधा है। औसत ओपन-सोर्स सॉफ़्टवेयर प्रोजेक्ट विकेंद्रीकृत वर्कफ़्लो के साथ काम नहीं करता। औसत कंपनी तो बिल्कुल नहीं करती। वे वितरित मॉडल के कई फायदों का उपयोग करते हैं (जैसे ऑफ़लाइन काम करना, पुश में देरी करना आदि), लेकिन वे एक केंद्रीकृत होस्ट पर बहुत अधिक निर्भर हैं। और Git रिपॉज़िटरी होस्ट करना, जैसा कि पता चलता है, बेहद मुश्किल काम है।
Git में क्या कठिन है?
बड़े पैमाने पर Git रिपॉज़िटरी होस्ट करने की चुनौती Git के डिज़ाइन में ही निहित है: वितरित संस्करण नियंत्रण प्रणाली होने का अर्थ है कि रिपॉज़िटरी के सभी इंस्टेंस एक जैसे होते हैं। Git सर्वर पर मौजूद रिपॉज़िटरी में ऐसा कुछ विशेष नहीं होता जो डेवलपर के लैपटॉप पर मौजूद रिपॉज़िटरी में न हो। शुरुआत में भले ही लगे कि इससे Git रिपॉज़िटरी होस्ट करना आसान हो जाता है (बस डिस्क पर मौजूद रिपॉज़िटरी की कॉपी के आगे एक HTTP डेमन लगा दें और आपका Git सर्वर तैयार है!), लेकिन स्केलेबिलिटी और विश्वसनीयता से जुड़ी कई कठिन चुनौतियाँ इसे उतना आसान नहीं रहने देतीं।
सामान्य Git रिपॉज़िटरी में आपका कोड और मेटाडेटा (फ़ाइलें, कमिट, tree) संपीड़ित करके packfiles में संग्रहीत किए जाते हैं — यह एक सरल बाइनरी सीरियलाइज़ेशन प्रारूप है, जिसे स्थानीय मशीन पर संभालना सुविधाजनक है, लेकिन सर्वर पर बड़े पैमाने पर प्रबंधित करना आदर्श नहीं है। Packfiles, Git स्टोरेज और Git नेटवर्किंग के मूलभूत घटक हैं। जब आप किसी रिपॉज़िटरी में डेटा पुश करते हैं या उससे फ़ेच करते हैं, तो वह packfile के रूप में स्थानांतरित होता है।
डिज़ाइन के अनुसार Git इसी तरह काम करता है, लेकिन यह सोचना भी उचित है कि ऐसा होना ज़रूरी नहीं है। आखिरकार, आप Git क्लाइंट को नियंत्रित नहीं करते (कम-से-कम अपने उपयोगकर्ताओं को परेशान किए बिना और बहुत अधिक अड़चन पैदा किए बिना नहीं), लेकिन अपने सर्वर की सीमाओं के भीतर आप जो चाहें कुछ भी कर सकते हैं। आपको packfiles इस्तेमाल करने के लिए कोई बाध्य नहीं करता — Linus आकर जाँच नहीं करने वाले हैं। एकमात्र प्रतिबंध यह है कि सभी Git ऑपरेशन के लिए आपको नेटवर्क पर packfiles प्राप्त और भेजनी होती हैं।
वर्षों के दौरान, बड़े पैमाने पर Git रिपॉज़िटरी होस्ट करने वाली कंपनियों ने पाया कि यह packfile-आधारित डिज़ाइन उपलब्धता और स्केलेबिलिटी, दोनों के लिए एक बड़ी सीमा है। Packfiles बड़ी बाइनरी फ़ाइलें होती हैं, जिन्हें Git के एक्सेस करने के लिए फ़ाइलसिस्टम पर मौजूद होना चाहिए। डिस्क पर मौजूद रिपॉज़िटरी के आगे HTTP सर्वर रखने वाले सरल तरीके की क्षमता बहुत सीमित है। आदर्श रूप से, आप चाहेंगे कि रिपॉज़िटरी कई डिस्क और कई मशीनों पर मौजूद हो (इससे आप कई Git ऑपरेशन समानांतर में चला सकते हैं और सर्वर क्रैश होने पर भी आपकी रिपॉज़िटरी उपलब्ध रहती है)। लेकिन यह कैसे किया जाए?
इसे हासिल करने के लिए, बढ़ती जटिलता के क्रम में, मोटे तौर पर तीन तरीके हैं: फ़ाइलसिस्टम को वितरित करें, packfiles को वितरित करें, या Git को ही वितरित करें।
Packfile के बिना Git
Git एक Content-addressable डेटा स्टोर है। Git रिपॉज़िटरी के सभी ऑब्जेक्ट (blobs, trees, commits आदि) अपने Content के SHA-1 के आधार पर कुंजीबद्ध होते हैं। यह वितरित कुंजी-मूल्य स्टोर से सहज रूप से बहुत अच्छी तरह मेल खाता है (कुंजी SHA-1 है; मूल्य वास्तविक ऑब्जेक्ट है) और रिपॉज़िटरी के स्टोरेज को बड़े पैमाने पर स्केल करने का एक साफ़ तरीका दे सकता है। लेकिन व्यवहार में यह काम नहीं करता।
समस्या यह है कि Git रिपॉज़िटरी का वास्तविक लेआउट एक निर्देशित अचक्रीय ग्राफ़ है (संक्षेप में DAG)। आप किसी भी ऑब्जेक्ट को उसके SHA से देख सकते हैं, लेकिन रेपो में सबसे मामूली ऑपरेशन करने के लिए भी आपको DAG को चरण-दर-चरण ट्रैवर्स करना पड़ता है।
अगर आप किसी रिपॉज़िटरी में हाल के परिवर्तनों की सूची बनाने जैसा ऑपरेशन करना चाहते हैं, तो आपको उसके commits को प्रोसेस करना होगा। किसी commit को प्रोसेस करने पर आपको उसके tree के root का पॉइंटर मिलता है। उस tree से आपको हर फ़ाइल और हर subtree के पॉइंटर मिलते हैं। मूल commit से आपको उसके parent का पॉइंटर मिलता है (जो history में उससे पहले आता है)। अहम बात यह है कि इस traversal के हर चरण में, पिछले पॉइंटर को fetch किए बिना आपको अगले पॉइंटर का मान पता नहीं होता। अगर हर fetch के लिए वितरित स्टोर तक राउंड ट्रिप करनी पड़े, तो लागत बहुत तेज़ी से बढ़ जाती है।
ऑब्जेक्ट स्तर पर Git को वितरित करने का यह तरीका पहले भी कई बार आज़माया गया है और बड़े पैमाने पर अक्सर विफल रहता है। सबसे आशाजनक कार्यान्वयन का प्रयास मेरे पूर्व मेंटर Shawn Pearce ने तब किया था, जब वे Google में संस्करण नियंत्रण सिस्टम टीम में काम कर रहे थे। उनका तरीका ऑब्जेक्ट को वितरित हैश तालिका में संग्रहीत करना था। यह केवल Java में बने कस्टम Git कार्यान्वयन JGit की बदौलत संभव था। किसी अच्छी पुरानी Java लाइब्रेरी की तरह, JGit सामान्य Git रिपॉज़िटरी के सभी विवरणों को अमूर्त करने के लिए पर्याप्त इंटरफ़ेस, फ़ैक्टरी और इंटरफ़ेस फ़ैक्टरी देता है, जिसमें उसके on-disk packfiles को DHT से बदलना भी शामिल है। हालाँकि सिस्टम काम करता था और सामान्य Git ऑपरेशन के लिए परिणाम पर्याप्त अच्छे थे, लेकिन Git प्रोटोकॉल की सीमाओं (जो, डेटा को सर्वर पर कैसे भी संग्रहीत करें, नेटवर्क पर packfiles भेजना अनिवार्य करती हैं) के कारण git clone का प्रदर्शन इतना खराब था कि इस डिज़ाइन को पूरी तरह छोड़ दिया गया।
GitHub और फ़ाइल सिस्टम
Git के Linux Kernel के दायरे से बाहर निकलना शुरू होने के कुछ साल बाद, San Francisco में एक जुझारू स्टार्टअप का जन्म हुआ। GitHub की स्थापना 2008 में एक सोशल कोडिंग प्लेटफ़ॉर्म के रूप में हुई थी, जिसका बेहद दूरदर्शी टैगलाइन था, "Git रिपॉज़िटरी होस्टिंग: अब सिरदर्द नहीं।" मैं मज़ाक नहीं कर रहा हूँ, खुद देख लें। 2008 में भी इस बात पर व्यापक सहमति थी कि Git के वितरित डिज़ाइन के बावजूद—या शायद उसी वजह से—Git रिपॉज़िटरी को उपयोगकर्ता-अनुकूल बनाने के लिए उन्हें होस्ट करने का एक केंद्रीकृत तरीका ज़रूरी था, और ऐसा करना बेहद तकलीफ़देह था। GitHub इसे बदलने के लिए प्रतिबद्ध था।
इसका प्लेटफ़ॉर्म शुरुआत में एक Rails मोनोलिथ था (और अधिकांशतः अब भी है)। सबसे शुरुआती संस्करण एक ही, भले ही शक्तिशाली, मशीन पर चल रहे थे, जिसमें एक Ruby सर्वर और उसके साथ डिस्क पर रिपॉज़िटरी की प्रतियां थीं। Rails ऐप को स्केल करना आसान है: उसके अधिक इंस्टेंस परिनियोजित करें। लेकिन इस विशेष मामले में, चूँकि Git शामिल है, वे जल्द ही उसी बार-बार उठने वाले सवाल से टकराए जिसे हम यहाँ हल करने की कोशिश कर रहे हैं: अगर Rails ऐप को डिस्क पर मौजूद Git रिपॉज़िटरी तक पहुँच चाहिए, तो उनकी अधिक प्रतियां कैसे परिनियोजित की जाएँ?
मितव्ययी और अलग तरह के लोगों का समूह होने के कारण, GitHub के शुरुआती सिस्टम इंजीनियरों ने अपने स्केलिंग की समस्याओं को ठीक करने के लिए संभवतः सबसे सरल तरीका आज़माया। विचार यह था कि अगर वे फ़ाइल सिस्टम को वितरित करने पर ध्यान दें (packfiles या स्वयं Git के बजाय), तो वे Rails ऐप को अपरिवर्तित रख सकते हैं और Git के साथ अजीब चीज़ें करने के बजाय लगातार बढ़ते उपयोगकर्ता आधार के लिए अधिक सुविधाएँ जारी करने में समय लगा सकते हैं। बहुत व्यावहारिक। लेकिन यह काम नहीं किया।
टीम ने Git डेटा के लिए वितरित फ़ाइल सिस्टम के कई तरीके आज़माए: सबसे स्पष्ट तरीका, सभी रिपॉज़िटरी को केंद्रीकृत सर्वर पर संग्रहीत करने के लिए NFS का उपयोग, जल्द ही छोड़ दिया गया। Git का डिफ़ॉल्ट कार्यान्वयन फ़ाइल सिस्टम के अर्थविज्ञान (locking, tearing, reading, syncing...) के बारे में बहुत-सी धारणाओं पर आधारित है, जो धीमे डेवलपर लैपटॉप के स्थानीय फ़ाइल सिस्टम पर ठीक-ठाक प्रदर्शन सुनिश्चित करती हैं, लेकिन नेटवर्क वाले फ़ाइल सिस्टम पर उनके व्यवहार पर कोई ध्यान नहीं देतीं। यह धीमा था और इसमें बग थे।
फ़ाइल सिस्टम को ब्लॉक स्तर पर प्रतिकृत करने वाली—स्पष्ट रूप से, पीछे मुड़कर देखने पर, भयावह—तकनीकों के साथ भी आगे प्रयास किए गए। GFS के साथ एक अल्पकालिक परिनियोजन। DRBD पर आधारित एक लंबे समय तक चलने वाला परिनियोजन। वे सभी एक दीवार से टकरा गए। उन्हें रोज़ाना संचालित करना भयानक था, और अच्छा प्रदर्शन भी इसकी भरपाई नहीं कर पाया। यह सब डिस्क पर packfiles के डिज़ाइन पर आकर सिमटता है।
हम पहले ही देख चुके हैं कि Git की ग्राफ-जैसी डेटा संरचनाएँ राउंड-ट्रिप को अत्यधिक महंगा बनाती हैं। दुर्भाग्य से, यही सिद्धांत डिस्क पर मौजूद अंतर्निहित डेटा पर भी लागू होता है। DAG में ऑब्जेक्ट्स के लेआउट और उन्हें packfile में रखने के तरीके के बीच कोई संबंध नहीं है। packfiles जनरेट करते समय इस्तेमाल की जाने वाली मुख्य हीयूरिस्टिक उनका आकार न्यूनतम रखना है; ऑब्जेक्ट्स को पूरे pack में बेतरतीब ढंग से रखा जाता है, वे संपीड़ित होते हैं, और अहम बात यह है कि उन्हें शायद ही कभी पूर्ण रूप में संग्रहीत किया जाता है। अधिकांश ऑब्जेक्ट्स उसी packfile में किसी दूसरे ऑब्जेक्ट के ऊपर डेल्टा के रूप में संग्रहीत होते हैं। किसी एक ऑब्जेक्ट को पढ़ने में, ग्राफ डेटा संरचना के अनेक तार्किक हॉप्स के बाद, डिस्क पर मौजूद फ़ॉर्मेट के भौतिक हॉप्स का अनुसरण भी शामिल होता है।
डेटा के गीगाबाइट्स में इस तरह का यादृच्छिक भ्रमण, जो किसी रिपॉज़िटरी पर किए गए हर Git ऑपरेशन के लिए ज़रूरी है, नेटवर्क वाले फ़ाइल सिस्टम के साथ बिल्कुल भी अच्छी तरह काम नहीं करता (चाहे वह फ़ाइल या ब्लॉक स्तर पर प्रतिकृत करता हो)। इसे बेहद धीमा हुए बिना चलाने का एकमात्र तरीका पूरी फ़ाइल को स्थानीय रूप से कैश करना है। लेकिन एक ही फ़ाइल सिस्टम में सैकड़ों हज़ार रिपॉज़िटरी होने पर कैशिंग कोई विकल्प नहीं है।
आखिरकार, GitHub के सिस्टम इंजीनियरों ने कठिन निर्णय लिया और फ़ाइल सिस्टम को वितरित करना छोड़ दिया। उन्होंने एक RPC सिस्टम विकसित करना शुरू किया ताकि रिपॉज़िटरी समर्पित फ़ाइल सर्वरों पर रह सकें, और सभी ऑपरेशन दूरस्थ रूप से करने के लिए Rails ऐप को अपडेट किया। इससे क्षैतिज स्केलेबिलिटी में अच्छी-खासी बढ़त मिली, लेकिन न तो उनकी उपलब्धता की समस्या ठीक हुई और न ही सबसे व्यस्त रिपॉज़िटरी का प्रदर्शन। आखिरकार, हर रिपॉज़िटरी अभी भी केवल एक ही मशीन पर संग्रहीत थी।
Spokes और सुसंगतता
Spokes को मूल रूप से GitHub में लगभग 2013 में विकसित किया गया था और तब से यह उद्योग का एक मानक बन गया है। अधिकांश Git होस्टिंग सेवाएँ अपने आर्किटेक्चर में Spokes के तरीके (Git रिपॉज़िटरी के लिए एप्लिकेशन-स्तरीय रेप्लिकेशन) के किसी न किसी रूप का उपयोग करती हैं। Spokes के इतने वर्षों तक सफलतापूर्वक काम करने का मुख्य कारण यह है कि इसमें तीन बुनियादी विकल्प चुने गए थे, जो समय के साथ इष्टतम साबित हुए हैं:
- यह Git को स्वयं वितरित नहीं करता; यह packfile स्तर पर काम करता है।
- यह सारा डेटा स्थानीय NVMe डिस्क पर वास्तविक Git रिपॉज़िटरी के रूप में संग्रहीत करता है।
- यह Git डेटा का रेप्लिकेशन करता है, लेकिन सभी प्रतियों को सुसंगत रूप से सिंक में रखता है।
जिन packfiles के यादृच्छिक रीड पैटर्न पर हमने अभी चर्चा की है, उनके कारण सादे Git रिपॉज़िटरी को NVMe ड्राइव पर संग्रहीत करना यह सुनिश्चित करने के लिए लगभग अनिवार्य है कि सभी बुनियादी Git ऑपरेशन तेज़ रहें। इससे clones भी कुशल रहते हैं, क्योंकि डेटा को Git क्लाइंट की अपेक्षा के अनुरूप बदलना नहीं पड़ता। इससे आप Git के ऊपर उत्पाद बनाने पर ध्यान केंद्रित कर पाते हैं, बजाय इसके कि स्वयं Git के ऐसे fork का रखरखाव करें जो आपकी असामान्य रिपॉज़िटरी पर काम कर सके।
डेटा की सभी प्रतियों को सुसंगत रूप से सिंक में रखना भी बेहद महत्वपूर्ण है। यह बात अक्सर अनुभव से ही समझ में आती है, लेकिन Git क्लाइंट वास्तव में eventual consistency के साथ अच्छी तरह काम नहीं करता। यदि आपका स्थानीय Git क्लाइंट किसी commit को push करता है और फिर fetch के तुरंत बाद उसे पढ़ नहीं पाता, तो यह गंभीर समस्या है। Git के लिए यह बहुत भ्रमित करने वाला होता है। यदि आप अपनी CI पाइपलाइन को सौ runners पर चलाते हैं और उनमें से तीन आपके रिपॉज़िटरी को clone करने के बाद वह commit नहीं पाते, जिसका उन्हें परीक्षण करना है, तो यह भी गंभीर समस्या है। साथ ही, यह उपयोगकर्ता अनुभव को बहुत खराब बनाता है।
Git रिपॉज़िटरी के eventually consistent दृश्य के साथ काम करने में बहुत-सी पेचीदगियाँ हैं, चाहे क्लाइंट पर हों या बैकएंड में। इसलिए Spokes यह सुनिश्चित करने के लिए बहुत अधिक जटिलता स्वीकार करता है कि सिस्टम हमेशा पूरी तरह सुसंगत रहे। आइए देखें कि इसका वास्तव में क्या अर्थ है।
Spokes एक सहमति-आधारित वितरित सिस्टम है। यह आपके Git रिपॉज़िटरी की कई प्रतियों को अलग-अलग सर्वरों पर संग्रहीत करके काम करता है। जब भी आप नया डेटा push करते हैं, एक orchestrator आपके push को सभी जगह भेजता है, ताकि आपके रिपॉज़िटरी के हर instance को उसकी एक प्रति मिल सके। "fan-out" को 3PC (तीन-चरण कमिट) नामक पारंपरिक सहमति एल्गोरिदम के साथ सिंक्रनाइज़ किया जाता है, ताकि push तभी स्वीकृत हो जब अधिकांश नोड उसकी पुष्टि करें।
Spokes 3PC का उपयोग कैसे करता है, इस पर आगे बात करने से पहले हमें समझना होगा कि Git push कैसे काम करता है। Git push के दो कंपोनेंट होते हैं: एक packfile और एक रेफरेंस ट्रांज़ैक्शन। packfile, जिसके बारे में हम पहले ही बात कर चुके हैं, उन objects को शामिल करता है जिन्हें आप रिपॉज़िटरी में push कर रहे हैं (आपके परिवर्तनों वाले blobs, trees और commits)। ट्रांज़ैक्शन एक या अधिक references (जैसे जिस branch पर आप काम कर रहे हैं) को आपके अभी-अभी push किए गए commits की ओर इंगित करने के लिए अपडेट करके आपके परिवर्तनों को वास्तव में रिपॉज़िटरी में प्रकाशित करता है।
यह विभाजन यहाँ बहुत सुविधाजनक है, क्योंकि कोई pushed commit तब तक दिखाई नहीं देता (Git की शब्दावली में "reachable") जब तक उसकी ओर इंगित करने वाला reference अपडेट नहीं हो जाता। इसका मतलब है कि हम अपने pushes के लिए सहमति को packfiles को एक साथ सभी hosts पर भेजकर लागू कर सकते हैं (यहाँ सिंक्रनाइज़ करने की ज़रूरत नहीं है) और फिर रेफरेंस ट्रांज़ैक्शन के साथ three-phase commit करके। रेफरेंस ट्रांज़ैक्शन packfile की तुलना में बहुत छोटा होता है और इसे सिंक्रनाइज़ करना भी तेज़ होता है। Git में रेफरेंस ट्रांज़ैक्शन तैयार करने का समर्थन भी है: यह reference पर lock प्राप्त कर सकता है, सत्यापित कर सकता है कि मौजूदा मान अपेक्षित मान ही है, और फिर ट्रांज़ैक्शन के लिए commit या abort कमांड मिलने तक lock बनाए रख सकता है।
इस डिज़ाइन के साथ, हम सुनिश्चित करते हैं कि प्रत्येक push सभी replicas में पूरी तरह सिंक्रनाइज़ हो। फिर reads (fetches, clones) को सुरक्षित रूप से किसी भी एक replica पर रूट किया जा सकता है, क्योंकि हर replica हमेशा अद्यतित रहता है।
Spokes मूलतः इसी तरह काम करता है और पिछले 13 वर्षों से काफी अच्छी तरह काम कर रहा है। बेशक, Spokes पूर्ण नहीं है — कोई भी सिस्टम नहीं होता। 2026 में लोगों के Git रिपॉज़िटरी इस्तेमाल करने का तरीका बहुत बदल गया है, और इस दौरान हमने वितरित सिस्टम बनाने के बारे में कई महत्वपूर्ण सबक सीखे हैं। समय और अनुभव ने दिखाया है कि Spokes के कौन-से विकल्प इष्टतम साबित हुए और कौन-से नहीं।
एक कमी जो गंभीर साबित हुई है, वह है 3PC की सीमित क्षैतिज स्केलेबिलिटी। जब Spokes को पहली बार जारी किया गया था, तब प्रति रिपॉज़िटरी तीन प्रतिकृतियां आदर्श थीं। तीन प्रतियों से आप औसत रिपॉज़िटरी को अतिरिक्त क्षमता के साथ संभाल सकते थे, और इतनी रिडंडेंसी भी थी कि एक मशीन बंद हो जाने पर भी पुश स्वीकार किए जा सकें।
2026 में स्थिति बहुत अलग है। अब किसी Enterprise कंपनी की औसत रिपॉज़िटरी एक विशाल मोनोरेपो है। ऐसी रिपॉज़िटरी का ट्रैफ़िक संभालने के लिए तीन प्रतिकृतियां पर्याप्त नहीं हैं, खासकर CI के मामले में। बेशक, डरावने स्केल पर टेल प्रभाव के अलावा Spokes को तीन से अधिक प्रतिकृतियों के साथ चलाने से कुछ नहीं रोकता। तीन-चरणीय कमिट Git ट्रांज़ैक्शन मॉडल से बहुत सहजता से मैप होता है, लेकिन एक सहमति एल्गोरिदम के रूप में इसकी मूलभूत सीमाएं हैं: हर चरण की लेटेंसी क्लस्टर के सभी सर्वरों में सबसे धीमे सर्वर पर निर्भर करती है। क्लस्टर में जितनी अधिक प्रतिकृतियां जोड़ते हैं, पुश थ्रूपुट उतना ही खराब होता जाता है।
यह स्केलेबिलिटी बाधा दूसरी दिशा में भी लागू होती है। जब एजेंट बड़े पैमाने पर Git रिपॉज़िटरी के साथ काम करते हैं, तो वे अक्सर मोनोरेपो के बजाय बड़ी संख्या में छोटी रिपॉज़िटरी बनाते हैं, जिनमें से कई अस्थायी होती हैं और अधिकांश का शायद ही कभी उपयोग होता है। Spokes को यहां कठिनाई होती है क्योंकि इन प्रत्येक रिपॉज़िटरी के लिए अभी भी तीन प्रतिकृतियां आवश्यक हैं। तीन अधिकतर निष्क्रिय प्रतिकृतियां, जिन्हें कम नहीं किया जा सकता, क्योंकि तब सिस्टम पूरी तरह सुसंगत नहीं रहेगा और डेटा हानि संभव होगी। तीन-चरणीय कमिट के साथ, न्यूनतम सीमा हमेशा बहुत ऊंची और अधिकतम सीमा बहुत कम होती है।
एक और कमी, जिसे शुरुआत में देख पाना असंभव था लेकिन झेलने के बाद जो पीड़ादायक रूप से स्पष्ट हो जाती है, वह यह है कि बड़े पैमाने पर Spokes का संचालन मुश्किल हो सकता है। क्योंकि डिस्क पर मौजूद रिपॉज़िटरी ही सहमति के लिए हमेशा आधिकारिक स्रोत होती हैं, इसलिए हर रिपॉज़िटरी की हर प्रति बहुत महत्वपूर्ण होती है। आपको रिपॉज़िटरी को मवेशियों की तरह नहीं, पालतू जानवरों की तरह संभालना पड़ता है।
इसका अर्थ है कि, सबसे पहले, आपको ठीक-ठीक पता होना चाहिए कि हर रिपॉज़िटरी कहां है। इससे एक बाहरी डेटाबेस पर निर्भरता (और उपलब्धता की संभावित समस्या) जुड़ जाती है, जिसमें हर रिपॉज़िटरी को उसकी प्रतिकृति वाली प्रत्येक मशीन से मैप करने वाली बहुत बड़ी रूटिंग टेबल रखनी होती है। प्रत्येक रिपॉज़िटरी का चेकसम भी होना चाहिए और उस टेबल में उसके चेकसम लगातार अपडेट होते रहने चाहिए, ताकि यह सुनिश्चित हो सके कि रिपॉज़िटरी डिस्क पर वैध बनी रहे। जैसे ही रिपॉज़िटरी के साथ कुछ बुरा होता है (और यकीन मानिए, बुरी चीजें हर समय होती रहती हैं — व्यवहार में Git बहुत नखरीला हो सकता है), आपको उसका पता लगाकर उसे स्वस्थ स्थिति में वापस लाने के लिए मरम्मत कार्य शेड्यूल करना होता है। और आपको यह बहुत जल्दी करना चाहिए! क्योंकि, फिर से, डिस्क पर मौजूद रिपॉज़िटरी ही आधिकारिक स्रोत हैं। भ्रष्ट प्रति, अनुपस्थित प्रति जितनी ही खराब होती है। अगर तीन में से दो प्रतियां भ्रष्ट हैं, तो सिस्टम अब पुश स्वीकार नहीं कर सकता: कोरम नहीं है।
Continuity
Continuity (संक्षेप में Cnt) Cursor में विकसित किया गया हमारा Git स्टोरेज सिस्टम है, जिसका तरीका बेहद स्पष्ट है: Spokes की खूबियों से सीखना और उन चीज़ों को ठीक करना जिन्हें अब, कई वर्षों बाद, हम समस्याएँ मानते हैं।
Cnt एक सरल सिस्टम है (कोई सिस्टम सरल हुए बिना संचालित करने में आसान नहीं हो सकता)। इसका मूल प्रिमिटिव एक write-ahead log है, जिसे हम S3-संगत ऑब्जेक्ट स्टोरेज में रखते हैं। उत्पादन में हम सीधे S3 पर चलते हैं, लेकिन इसे हमने इस तरह डिज़ाइन किया है कि किसी भी क्लाउड पर परिनियोजित किया जा सके।
जब किसी रिपॉज़िटरी में पुश आता है, तो हम उसे S3 में WAL एंट्री के रूप में स्टोर करते हैं। हम किसी पुश को पूरी तरह स्थायी रूप से संग्रहीत होने से पहले कभी स्वीकार नहीं करते। हर पुश अलग ऑब्जेक्ट के रूप में स्टोर किया जाता है; हम पुश की गई packfile को डिस्क पर लिखते हैं और साथ ही S3 पर अपलोड करते हैं। हालाँकि, WAL एंट्री अपलोड करने से वह प्रकाशित नहीं होती। कोई पुश तभी दिखाई देता है, जब हम रिपॉज़िटरी की स्थानीय कॉपी पर उसका रेफरेंस ट्रांज़ैक्शन सफलतापूर्वक तैयार कर लेते हैं और WAL इंडेक्स फ़ाइल में WAL एंट्री का पॉइंटर रिकॉर्ड कर लेते हैं—यह फ़ाइल भी स्टोर में एक अलग ऑब्जेक्ट होती है। इससे सभी पुश रैखिकीकरण योग्य हो जाते हैं।
हम हर पुश के लिए सिर्फ़ एक S3 राइट करने से बचने की कोशिश करते हैं, क्योंकि व्यस्त रिपॉज़िटरी में इससे S3 PUT ऑपरेशन की लेटेंसी के आधार पर पुश थ्रूपुट की एक कठोर सीमा लग जाती है। सावधानी से ट्यून किए गए बैचिंग कार्यान्वयन और प्रतिकृतियों के कोरम के बजाय सिर्फ़ एक स्थानीय रिपॉज़िटरी के साथ रेफरेंस ट्रांज़ैक्शन को सिंक्रनाइज़ करने की आवश्यकता के कारण, हमारे पास ऐसा सिस्टम है जो हमारी डिस्क की क्षमता के अनुसार उतनी तेज़ी से पुश स्वीकार कर सकता है।
रिपॉज़िटरी की स्थानीय कॉपी, निश्चित रूप से, बहुत तेज़ NVMe ड्राइव पर संग्रहीत एक सामान्य Git रिपॉज़िटरी है। हम वही करते हैं जो Spokes करता है, क्योंकि मेरे विचार में Spokes ने इसे बिल्कुल सही किया है। इससे हमें Git समुदाय के शानदार OSS कार्य का पुनः उपयोग करने की सुविधा मिलती है, जिसमें अपस्ट्रीम Git क्लाइंट और उसके कई प्रदर्शन अनुकूलन शामिल हैं। इससे हम Git के साथ अजीब चीज़ें करने के बजाय नई सुविधाएँ शिप करने पर ध्यान केंद्रित कर सकते हैं।
सहमति
हमने देखा है कि Spokes क्लस्टर को संचालित करना मुश्किल बनाने वाली बातों में से एक यह है कि हर सर्वर पर प्रत्येक रिपॉज़िटरी की लोकेशन का ट्रैक रखना बेहद ज़रूरी है। Cnt इसे बिल्कुल अलग ढंग से करता है। हर रिपॉज़िटरी कहाँ होती है? जवाब है, "कहीं भी"। इससे कोई फर्क नहीं पड़ता! हम रिपॉज़िटरी को डिस्क पर मौजूद एक वॉर्म कैश की तरह मानते हैं, लेकिन स्रोत का सत्य हमेशा S3 में मौजूद write-ahead log होता है। सिस्टम स्टेटलेस है और इसमें कोई रूटिंग टेबल नहीं है (और न ही संचालित करने के लिए कोई रिलेशनल डेटाबेस — हैशटैग blessed)। अगर किसी होस्ट पर पहुँचने पर कोई रिपॉज़िटरी स्थानीय डिस्क पर नहीं मिलती, तो हम उसे WAL से materialize कर देते हैं। हम यह बहुत कुशलता से कर सकते हैं, लेकिन जाहिर है कि हम इसे हर समय नहीं करना चाहते, क्योंकि यह संसाधनों की बर्बादी होगी। उत्पादन में, हम किसी रिपॉज़िटरी ID को उन नोड की सूची से मैप करने के लिए rendezvous hashing का उपयोग करते हैं, जहाँ उसके होने की उम्मीद होती है। रिपॉज़िटरी को रूट करने के लिए हमें केवल रिपॉज़िटरी ID और क्लस्टर में स्वस्थ नोड के मौजूदा सेट की ज़रूरत होती है। लेकिन अगर यह स्थिति सिंक से बाहर हो जाए (जैसे कोई नोड अस्वस्थ हो जाए), तो वह भी बिल्कुल ठीक है। हम बस रिपॉज़िटरी को अगले नोड पर materialize कर देंगे।
सहमति का क्या? चुनावों का क्या? किसी रिपॉज़िटरी के लिए कौन-सा सर्वर primary है? इससे भी कोई फर्क नहीं पड़ता! यहाँ न कोई स्टेट है, न सहमति। कोई भी सर्वर primary हो सकता है। write-ahead log के सभी अपडेट S3 पर atomic compare-and-swap (CAS) ऑपरेशन के साथ सिंक्रनाइज़ किए जाते हैं, इसलिए किसी रिपॉज़िटरी का कोई भी instance सुरक्षित रूप से push स्वीकार कर सकता है। फिर, रूटिंग की तरह ही, किसी भी मनमाने सर्वर को primary के तौर पर काम करने देना सबसे कुशल तरीका नहीं है (इससे CAS retries होते हैं, जो pushes में देरी कर सकते हैं), इसलिए व्यवहार में हम हमेशा उसी सर्वर को primary चुनते हैं जो rendezvous hashing से मिली ranked सूची में सबसे पहले होता है। लेकिन विशेष परिस्थितियों में — जैसे डिप्लॉयमेंट, failover या नेटवर्क में क्षणिक रुकावट के दौरान — हमें इस बात की चिंता नहीं होती कि primary कौन-सा सर्वर है। सिस्टम को इस तरह डिज़ाइन किया गया है कि अवनत स्थिति में भी वह हमेशा सही रहे और स्वस्थ होने पर हमेशा तेज़ रहे।
रेप्लिकेशन
S3 में राइट-अहेड लॉग होने से स्केलिंग की संभावनाओं की पूरी दुनिया खुल जाती है। हमारे पास वास्तव में कितनी भी प्रतिकृतियाँ हो सकती हैं, क्योंकि S3 की स्केलेबिलिटी बेजोड़ है और सभी प्रतिकृतियाँ सीधे वहीं से अपडेट हो जाती हैं। हम अपने क्लस्टर में गॉसिप UDP पैकेट भेजकर ऑप्टिमिस्टिक रेप्लिकेशन करते हैं। हर पुश के बाद प्रत्येक प्रतिकृति को सीधे S3 से अपडेट करने के लिए पैकेट में सभी आवश्यक मेटाडेटा होते हैं। “यह तो पागलपन है,” मैं आपको समय और स्थान के पार अपनी स्क्रीन के पीछे बुदबुदाते हुए सुन सकता हूँ। “UDP कोई विश्वसनीय ट्रांसपोर्ट नहीं है।” बिल्कुल नहीं है। डिस्ट्रिब्यूटेड सिस्टम में कुछ भी विश्वसनीय नहीं होता! वायर विश्वसनीय नहीं है, रूटिंग विश्वसनीय नहीं है, और टोपोलॉजी भी विश्वसनीय नहीं है। लेकिन कोई बात नहीं: इससे फर्क नहीं पड़ता। हर प्रतिकृति को WAL इंडेक्स के उस आखिरी संस्करण का ETag पता होता है, जिसके साथ वह अपडेट है। जब आप किसी प्रतिकृति पर रीड ऑपरेशन करते हैं, तो हम अपेक्षित ETag के साथ S3 पर कंडीशनल GET करते हैं। बिना मुख्य भाग वाली 304 प्रतिक्रिया (सुविधाजनक रूप से, लगभग तुरंत होने वाला ऑपरेशन — औसतन 10ms से कम, क्योंकि यह केवल-मेटाडेटा वाला S3 ऑपरेशन है) का मतलब है कि हम अपडेट हैं और तुरंत fetch या clone सर्व कर सकते हैं। 200 प्रतिक्रिया WAL इंडेक्स के नवीनतम संस्करण के साथ आती है, जिसका उपयोग हम रीड सर्व करने से पहले अपडेट होने के लिए करते हैं।
रेप्लिकेशन UDP पैकेट खो जाए या टोपोलॉजी बदलने के कारण गलत सर्वर पर पहुँच जाए, इससे कोई फर्क नहीं पड़ता। सभी प्रतिकृतियों पर सभी रीड पूरी तरह सुसंगत हैं, क्योंकि उन्हें स्रोत सत्य, यानी S3, के विरुद्ध सत्यापित किया जाता है। सिस्टम को इस तरह डिज़ाइन किया गया है कि अवनत स्थिति में भी यह हमेशा सही रहे और स्वस्थ होने पर हमेशा तेज़ रहे।
इसके दो निहितार्थ हैं। पहला, क्योंकि सिस्टम हमेशा सुसंगत रहता है, इसके ऊपर इन्फ्रास्ट्रक्चर बनाना बेहद आसान है। हम (हमारे एजेंट, हमारा वेब इंटरफ़ेस, हमारे क्लाइंट) हमेशा रिपॉज़िटरी का वैश्विक रूप से सुसंगत दृश्य देखते हैं। और क्योंकि सिस्टम दोनों दिशाओं में स्केल होता है, हर रिपॉज़िटरी को प्रतिकृतियों की बिल्कुल सही संख्या मिलती है। एक बड़े मोनोरेपो को उसके CI जॉब्स का पूरा लोड संभालने के लिए सैकड़ों प्रतिकृतियों पर डिप्लॉय किया जा सकता है। एजेंट्स द्वारा बनाई गई लाखों छोटी रिपॉज़िटरी को एक-एक प्रतिकृति पर सर्व किया जा सकता है; उपलब्धता सुनिश्चित करने के लिए हमें एक से अधिक की आवश्यकता नहीं है, क्योंकि S3 स्रोत सत्य है। वास्तव में, निष्क्रिय रिपॉज़िटरी को उसकी भी ज़रूरत नहीं होती: जब किसी प्रतिकृति पर कुछ समय तक ट्रैफ़िक नहीं आता, तो हम उसे नोड की डिस्क से गार्बेज कलेक्ट कर देते हैं और अगली बार fetch आने पर WAL से उसे फिर से तैयार कर देते हैं।
कॉम्पैक्शन
Write-ahead लॉग्स को समय-समय पर कॉम्पैक्ट करना आवश्यक है। लॉग को अनियंत्रित रूप से बढ़ने नहीं दिया जा सकता: पूर्ण पुनर्स्थापना में हर प्रविष्टि फिर से चलाई जाती है, इसलिए प्रविष्टियाँ जितनी अधिक होंगी, लागत उतनी ही बढ़ेगी।
संयोग से, एक सामान्य Git रिपॉज़िटरी को भी समय-समय पर कॉम्पैक्शन की आवश्यकता होती है, भले ही Git WAL पर आधारित नहीं है। हमने देखा है कि Git रिपॉज़िटरी में स्टोरेज की मूल इकाई packfile है। हर बार जब आप किसी रिपॉज़िटरी की रिमोट कॉपी पर पुश करते हैं या अपनी स्थानीय कॉपी में fetch करते हैं, तो एक नई packfile बनती है। यह अनिश्चित समय तक स्केल नहीं करता: हर packfile का अपना इंडेक्स होता है, जिससे Git उसमें मौजूद ऑब्जेक्ट्स को कुशलता से ढूँढ सकता है, लेकिन यह खोज केवल हर packfile के स्तर पर ही कुशल होती है। अगर आप किसी विशिष्ट ऑब्जेक्ट को ढूँढ रहे हैं और आपकी रिपॉज़िटरी में 100 packfiles हैं, तो आपको हर packfile का इंडेक्स खोलकर ऑब्जेक्ट खोजना होगा, जब तक वह किसी packfile में न मिल जाए। कोई कार्रवाई तब कुशल नहीं रहती, जब उसे सैकड़ों या हज़ारों बार करना पड़े।
आधुनिक Git ने इस समस्या से निपटने में काफी प्रगति की है; अब यह multi-pack indexes और incremental geometric compaction का समर्थन करता है। लेकिन आखिरकार आपको डिस्क पर मौजूद अपनी Git रिपॉज़िटरी को repack करना ही पड़ता है। ऐतिहासिक रूप से, Spokes जैसी प्रणालियों के लिए यह उपलब्धता की एक निरंतर समस्या रही है, क्योंकि repacking बहुत CPU-गहन कार्रवाई है, भले ही इसे क्रमिक रूप से किया जाए, और इसे सिस्टम की सभी प्रतिकृतियों पर करना पड़ता है। एक ही रिपॉज़िटरी के लिए दो या अधिक Spokes नोड्स पर गलती से maintenance कार्रवाई ट्रिगर करने पर रिपॉज़िटरी आसानी से fail over हो सकती है।
यहाँ हम कॉम्पैक्शन की लागत को वितरित करते हैं। केवल primary ही कॉम्पैक्शन करता है, और इसका परिणाम डिस्क पर मौजूद रिपॉज़िटरी और WAL, दोनों पर लागू होता है। चूँकि सभी प्रतिकृतियाँ WAL का अनुसरण करती हैं, वे कॉम्पैक्शन इवेंट्स का भी अनुसरण करती हैं। प्रतिकृतियाँ repack नहीं करतीं; वे CPU के बजाय बैंडविड्थ का उपयोग करते हुए S3 से पहले से कॉम्पैक्ट किए गए packs डाउनलोड करती हैं।
स्केल
रेप्लिकेशन और कॉम्पैक्शन वे दो प्रमुख कारक हैं जो तय करते हैं कि लोड के दौरान Git स्टोरेज सिस्टम का प्रदर्शन कैसा रहेगा। जैसा कि हमने अभी देखा, दोनों आपस में गहराई से जुड़े हैं: कोई रिपॉज़िटरी प्रति सेकंड जितने अधिक पुश लेती है, रीड प्रदर्शन उतना ही घटता है, क्योंकि Git ऑपरेशन को कुशल बनाए रखने के लिए हर पुश की पैकफ़ाइलों को कॉम्पैक्ट करना आवश्यक है। इन पुश को रेप्लिकेट करने पर कॉम्पैक्शन भी या तो रेप्लिकेट करना होगा या हर प्रतिकृति पर अलग से करना होगा।
Continuity का WAL-प्रथम डिज़ाइन पूर्णतः सुसंगत क्षैतिज स्केलेबिलिटी प्रदान करता है: आप मनचाही संख्या में प्रतिकृतियाँ परिनियोजित कर सकते हैं, और केवल-पढ़ने योग्य Git ऑपरेशन का थ्रूपुट उनके साथ रैखिक रूप से बढ़ता है। क्लस्टर की सभी प्रतिकृतियाँ पूर्णतः सुसंगत होने के कारण, हम Git प्रोटोकॉल (क्लोन, फेच) और रिपॉज़िटरी के ऊपर Origin द्वारा किए जाने वाले सभी RPC ऑपरेशन (वेब UI इंटरैक्शन, REST API, हमारे सभी एजेंटिक इंटरफ़ेस आदि) को स्केल कर सकते हैं।
हमने 100 तक प्रतिकृतियों के साथ सिंथेटिक स्ट्रेस टेस्ट चलाए हैं और पुश थ्रूपुट में किसी रिग्रेशन के बिना रीड के लिए सुसंगत रैखिक स्केलिंग देखी है।
किसी क्लस्टर का पुश थ्रूपुट इस बात पर निर्भर करता है कि हम S3 में अपने WAL को कितनी कम लेटेंसी पर अपडेट कर सकते हैं। S3 Standard का उपयोग करके, हम डेटा को कॉम्पैक्ट करते हुए और कॉम्पैक्ट किए गए डेटा को अन्य सभी नोड पर रेप्लिकेट करते हुए 120 पुश/सेकंड तक संभाल सकते हैं। हमने S3 Express One Zone पर उच्च-प्रदर्शन क्लस्टर भी परिनियोजित किए हैं, जहाँ PUT ऑपरेशन की लेटेंसी बहुत कम है। वहाँ हम 300 से अधिक पुश/सेकंड ले सकते हैं, और व्यवहार में हम उस गति से सीमित होते हैं जिस पर Git डिस्क पर मौजूद डेटा को कॉम्पैक्ट कर सकता है। कॉम्पैक्शन के प्रभाव को घटाने के लिए हम इस डेटा को डिस्क पर व्यवस्थित करने के नए तरीकों पर काम कर रहे हैं। हमारा लक्ष्य अपनी कठोर टिकाऊपन और सुसंगतता गारंटियों से समझौता किए बिना Git रिपॉज़िटरी द्वारा कोड स्वीकार करने की गति को लगातार अनुकूलित करना है।
- S3 Standard
- S3 Express One Zone
everysphere, Cursor के मोनोरेपो के लिए पुश/क्लोन थ्रूपुट।
सभी पुश रैखिकीकरण योग्य हैं और स्वीकृति से पहले बाहरी स्टोरेज में स्थायी रूप से संग्रहीत किए जाते हैं।
सभी क्लोन पूर्णतः सुसंगत हैं।
सत्य के स्रोत के रूप में WAL
S3 एक बेहतरीन तकनीक है। S3 API के साथ शुरू हुई ब्लॉब स्टोरेज की अवधारणा बड़े डेटा स्टोरेज सिस्टम के लिए एक अत्यंत शक्तिशाली आधारभूत घटक साबित हुई है, और यह Git रिपॉज़िटरी होस्ट करने पर भी पूरी तरह लागू होती है। यहाँ प्रस्तुत डिज़ाइन कई मायनों में नया है, लेकिन पैकफ़ाइलों को ब्लॉब के रूप में संग्रहीत करने वाला यह पहला डिज़ाइन नहीं है। Azure DevOps (Microsoft के अपने GitHub का Microsoft का अपना प्रतिस्पर्धी) में एक बेहद सफल Git स्टोरेज सिस्टम है, जो पैकफ़ाइलों को ब्लॉब स्टोरेज में और उनके रेफरेंस को रिलेशनल डेटाबेस (MS SQL Server) में संग्रहीत करता है। ऐसे सिस्टम में कई तरह के समझौते होते हैं। रिलेशनल डेटाबेस बड़े रेफरेंस ट्रांज़ैक्शन के साथ अच्छी तरह स्केल करता है। लेकिन फिर आपको रिलेशनल डेटाबेस चलाना पड़ता है। हमारा दृढ़ विश्वास है कि Git डेटा की सुसंगतता किसी भी अन्य विचार से अधिक महत्वपूर्ण है। इसी बात ने हमें बाहरी डेटाबेस पर निर्भर न रहने वाला WAL-आधारित सिस्टम डिज़ाइन करने के लिए निर्णायक रूप से प्रेरित किया।
प्रोडक्शन में किसी Git रिपॉज़िटरी के साथ कई चीज़ें गलत हो सकती हैं। स्थिर डेटा का भ्रष्ट होना, रीपैकिंग के दौरान बग्स, पुश के दौरान रेस कंडीशन। यह कॉर्नर केस का एक बड़ा संग्रह है। इनमें से अधिकांश को Git अपस्ट्रीम में ठीक कर दिया गया है। लेकिन सभी को नहीं। कोई भी सिस्टम बग्स से मुक्त नहीं होता, यहाँ तक कि वे भी नहीं जो OSS हैं और व्यापक रूप से परिनियोजित हैं। हमारा सुसंगतता मॉडल सुनिश्चित करता है कि हम किसी रिपॉज़िटरी पर होने वाली हर मूलभूत कार्रवाई को ट्रैक करें। हम किसी पुश को तब तक स्वीकार नहीं करते, जब तक वह WAL में पूरी तरह स्थायी रूप से संग्रहीत न हो जाए। हम सभी पुश को रैखिक क्रम में व्यवस्थित करते हैं। हम जिस भी रिपॉज़िटरी तक पहुँचते हैं, उसका हर दृश्य हमेशा पूरी तरह सुसंगत होता है। चूँकि हर पुश WAL में है, हम किसी रिपॉज़िटरी की अब तक की हर स्थिति देख सकते हैं। हमारे पास सभी पुश और सभी रीपैक के लिए पूर्ण प्रॉवेनेंस डेटा है। हम प्रत्येक प्रतिकृति को रिवाइंड और फास्ट-फ़ॉरवर्ड कर सकते हैं। हमें किसी बाहरी डेटाबेस के साथ कोई स्थिति सिंक्रनाइज़ नहीं करनी पड़ती, चाहे वह केवल रेफरेंस संग्रहीत करने वाला डेटाबेस हो या सभी ऑब्जेक्ट डेटा संग्रहीत करने वाला डेटाबेस। जब (न कि यदि) हमें Git में कोई बग मिलता है, तो हम ठीक-ठीक पता लगा सकते हैं कि क्या हुआ और उसे वापस कर सकते हैं। और Git में पहले से मौजूद बग्स के अलावा, हम बहुत कम नए बग्स जोड़ते हैं, क्योंकि इस पूरे समय सभी Git ऑपरेशन डिस्क पर मौजूद एक सामान्य Git रिपॉज़िटरी पर, तैयार टूलिंग का उपयोग करके किए जाते हैं।
ऑरिजिन
हम अच्छी तरह जानते हैं कि किसी और के स्रोत कोड को होस्ट करना कितना महत्वपूर्ण है। मेरा मानना है कि इस ब्लॉग पोस्ट को पढ़ने और समझने वाला हर व्यक्ति भी इसे उतनी ही गंभीरता से समझता है। अगर किसी कंपनी के डेवलपर्स उसके Git रिपॉज़िटरी से पुश या पुल नहीं कर पाते, तो कंपनी का कामकाज ठप हो सकता है। आपके CI सिस्टम में पाँच मिनट के डाउनटाइम से होने वाली उत्पादकता की लागत को डॉलर में मापना मुश्किल है, लेकिन किसी भी पैमाने से देखें तो यह बहुत बड़ी रकम है।
एजेंट्स ने सॉफ़्टवेयर के साथ काम करने का तरीका बुनियादी रूप से बदल दिया है और कई मायनों में इस स्थिति को और भी खराब कर दिया है। अधिक कोड, अधिक PRs, अधिक CI रन। इन सबके केंद्र में संस्करण नियंत्रण है, और शायद इसे रातोंरात बदलना सबसे कठिन है।
हम Cursor में कई महीनों से आंतरिक रूप से इन चुनौतियों का सामना कर रहे हैं। हमने ऐसा प्लेटफ़ॉर्म बनाने में काफी सोच-विचार और सावधानी बरती है, जो हमारे लिए इनका समाधान करे और उम्मीद है कि हमारे ग्राहकों के लिए भी करे। फिलहाल हमारा ध्यान अधिक विश्वसनीयता, बेहतर प्रदर्शन और बड़े पैमाने की क्षमता की ओर सहजतम संक्रमण उपलब्ध कराने और माइग्रेशन को यथासंभव आसान बनाने पर है।
ऑरिजिन कोई प्रयोग नहीं है; यह इन्हीं सिस्टमों को बनाने के कई दशकों के अनुभव का परिणाम है—ऐसे लोगों का अनुभव, जो इन चुनौतियों की व्यापकता को गहराई से समझते हैं। हमारे पास एक इंजीनियरिंग और परिचालन दर्शन है, जो कारगर साबित हुआ है, और संस्करण नियंत्रण के परिदृश्य के साथ-साथ इसे निरंतर विकसित करते रहने की दृढ़ प्रतिबद्धता भी है।
हमें उम्मीद है कि आप हम पर और हमारे प्लेटफ़ॉर्म पर भरोसा करेंगे।