- MSA के साथ 2,800 से अधिक services चला कर काफ़ी value मिल रही है
- लेकिन इस architecture के साथ चुनौतियाँ भी हैं। उनमें से एक है सभी services में library changes करना
- आम तौर पर आपको latest libraries, consistent library versions, और कम upgrade effort में से दो ही चुनने पड़ते हैं
केंद्रीकृत migration strategy
- Monzo का मानना है कि centrally driven migration approach के ज़रिए ऊपर बताई गई तीनों खूबियों को उच्च स्तर पर हासिल किया जा सकता है
- migration की ज़िम्मेदारी service owners को देने के बजाय, वे एक single team द्वारा migration lead करवाना पसंद करते हैं
- इससे high coordination overhead (जो slow migrations की ओर ले जाता है) और project stall होने का risk (जो inconsistency की ओर ले जाता है) टाला जा सकता है
- ताकि एक single team reasonable time में migration पूरा कर सके, वे इन बातों पर काफ़ी निर्भर करते हैं:
- core technology choices (जैसे high degree of consistency, monorepo का उपयोग)
- automation का बड़े पैमाने पर उपयोग (जैसे bulk service deployment tools, automated rollback checks)
OpenTracing SDK से OpenTelemetry SDK में migration
- हाल ही में OpenTracing SDK से OpenTelemetry SDK में जाने वाले project के ज़रिए Monzo ने अपनी strategy को व्यवहार में उतारा
- सभी services Jaeger में tracing data export करती हैं। पहले यह OpenTracing और Jaeger Go SDK के ज़रिए किया जाता था
- ये libraries अब deprecated हैं, और community अब OpenTelemetry पर एकजुट हो चुकी है
- tracing system को बेहतर बनाने की नींव रखने के लिए वे पहले deprecated libraries को OpenTelemetry SDK से बदलना चाहते थे
migration principles
- service owners के लिए transparent centralized migration. coordination overhead को कम करने और migration रुकने के risk को घटाने के लिए वे ऐसी strategy पसंद करते हैं जिसे एक single team centrally execute कर सके
- zero downtime. ज़्यादातर migrations बैंक के core functions के लिए critical services को छूती हैं, इसलिए downtime स्वीकार्य नहीं है
- gradual roll-forward, fast rollback. बड़े changes के लिए अगर समस्या आए तो blast radius कम करने हेतु क्रमिक roll-forward संभव होना चाहिए। लेकिन ज़रूरत पड़ने पर सब कुछ जल्दी rollback भी किया जा सके
- automation के लिए 80/20 rule. large-scale migrations में आम तौर पर बदलावों का बड़ा हिस्सा common template में फिट बैठता है। ऐसे changes आसानी से automate किए जा सकते हैं। अधिक unique use cases में automation का return कम होता जाता है, इसलिए उन्हें case-by-case सुलझाना ज़्यादा efficient होता है। बुरी surprises से बचने और progress track करना आसान बनाने के लिए ज़रूरी changes को पहले से इन दो categories में बाँटना अच्छा होता है
migration strategy
- Monzo में migration के कुछ तयशुदा steps हैं जिनका वे व्यवस्थित रूप से उपयोग करते हैं
1. planning और alignment
- migration के साथ काफ़ी risk जुड़ा होता है। यह न केवल large number of services को प्रभावित करता है, बल्कि migration चलाने वाली team के पास migrated services के बारे में बहुत कम (या बिल्कुल भी) context नहीं हो सकता
- वे services में consistency चाहते हैं, लेकिन exceptions हमेशा होते हैं, और वे ऐसी surprises को जितना जल्दी हो सके पकड़ना चाहते हैं
- इसलिए यह बहुत महत्वपूर्ण है कि planning process transparent हो और हर engineer को योगदान देने का मौका मिले
- इसके लिए दो processes हैं:
- proposal: Monzo में इन्हें बहुत लिखा जाता है। लगभग हर महत्वपूर्ण चीज़ आखिरकार एक single Slack channel में share की जाती है जहाँ कंपनी का हर व्यक्ति अपनी राय दे सकता है
- architecture review: बड़े changes के लिए वे synchronous architecture review meetings करते हैं, जहाँ अधिक विवादास्पद या risky क्षेत्रों को गहराई से देखा जाता है। लक्ष्य approval या sign-off लेना नहीं, बल्कि design की स्थिति को अर्थपूर्ण रूप से आगे बढ़ाकर project को तेज़ करना है
2. पुरानी library को wrap करना
-
नई library install करके service code update कर उसे call कराने के बजाय, उन्होंने पहले पुरानी library को wrap करने का फ़ैसला किया
- वे underlying library calls को intercept कर सकते थे और dynamic configuration के आधार पर तय कर सकते थे कि कौन-सी implementation इस्तेमाल करनी है। इससे सभी services को redeploy किए बिना आसानी से roll-forward और rollback किया जा सकता था
- नई library में कुछ types/functions काफ़ी अलग थे। सभी call sites को update करने में बहुत effort लगता, और कुछ मामलों में नई API का फ़ायदा सीमित था। पुरानी library को wrap करके वे ऐसे मामलों में पुरानी library जैसा interface बनाए रख सके, जिससे call sites को update करना आसान हुआ
-
library wrapping के दूसरे फ़ायदे:
- अपनी telemetry library से instrumentation किया जा सकता है
- ज़्यादा opinionated interface दिया जा सकता है
3. call sites update करना
-
इस library का उपयोग एक common pattern का पालन करता था:
- पूरे codebase में कई बार referenced functions/types की संख्या कम थी
- इसके बाद functions/types की एक long tail थी, जिन्हें केवल कुछ जगहों पर reference किया जाता था
-
उन्होंने इन दोनों मामलों को अलग-अलग तरीके से संभाला:
- जो थोड़े से functions/types कई जगह referenced थे, उन्हें जितना संभव हो automate किया गया। इस library के लिए उन्होंने automated refactoring के लिए मुख्य रूप से
goplsऔरgorenameपर भरोसा किया - जो long tail functions/types केवल कुछ जगह referenced थे, उन्हें manual case-by-case approach से संभाला गया। कुछ मामलों में उन्हें manually migrate किया गया। दूसरे मामलों में पता चला कि ज़्यादा standard API के ज़रिए वही काम किया जा सकता है, इसलिए उस पर switch कर दिया गया। इसका मतलब यह था कि उन्हें अब special case की तरह संभालने की ज़रूरत नहीं रही, और साथ ही wrapper library की API को छोटा और opinionated रखने का अतिरिक्त फ़ायदा मिला
- जो थोड़े से functions/types कई जगह referenced थे, उन्हें जितना संभव हो automate किया गया। इस library के लिए उन्होंने automated refactoring के लिए मुख्य रूप से
-
पुरानी library को wrap करने के अलावा, उन्होंने पुरानी library पर नए dependencies बनने से भी रोका। यह semgrep के ज़रिए CI checks जोड़कर किया गया
4. नई library को wrap करना
- जब पुरानी library wrap हो गई, तो wrapper library के पीछे नई library जोड़ना शुरू किया जा सकता था
- शुरुआत में नई implementation configuration के ज़रिए disabled रखी गई। इसका मतलब था कि behavior change की उम्मीद नहीं थी और master branch में changes को धीरे-धीरे merge करना जारी रखा जा सकता था
5. bulk service deployment
- नई implementation enable करना शुरू करने से पहले यह सुनिश्चित करना ज़रूरी था कि चल रही सभी services नई implementation को support कर सकें
- अन्य तरह के library changes में एक समय में केवल नई feature वाली services के subset को deploy किया जा सकता है। लेकिन tracing library के मामले में, अगर कोई service नई library पर migrate हो चुकी है, तो वह जिन सभी services को (transitionally) call कर सकती है, उन सबको भी नई functionality support करनी चाहिए
- बड़ी संख्या में service deployments को manage करने के लिए उन्होंने एक bulk deployment tool बनाया, जो asynchronous batch jobs के रूप में सभी services में library changes push कर सकता था
- संभावित bad deployments के प्रभाव को कम करने के लिए:
- automated rollback checks का उपयोग
- सबसे कम critical services को पहले deploy करना। उन्होंने सभी services को "tier" tag दिया था, और bulk deployment tool इसका उपयोग सबसे कम risky deployments को प्राथमिकता देने के लिए करता था
6. configuration के ज़रिए rollout control
- bulk deployment tool की समस्या यह थी कि वह अपेक्षाकृत धीमा था। वे जिस चीज़ से सच में बचना चाहते थे, वह यह थी कि सभी services deploy हो जाएँ और फिर पता चले कि नई library में समस्या है और जल्दी rollback संभव नहीं है
- इसलिए नई implementation को enabled state में deploy करने के बजाय, उन्होंने ऐसी capability deploy की जिससे configuration system के ज़रिए नई implementation enable की जा सके
- सामान्य deployment की तुलना में यहाँ configuration system का फ़ायदा यह था कि यह तेज़ था। सभी services हर 60 seconds में configuration refresh करती थीं, इसलिए ज़रूरत पड़ने पर जल्दी rollback किया जा सकता था
- इससे यह भी संभव हुआ कि नई implementation कब इस्तेमाल होगी, इस पर कहीं ज़्यादा control रहे। उदाहरण के लिए इसे केवल कुछ खास users या requests के random percentage के लिए enable किया जा सकता था
- इस मामले में उन्होंने केवल team-owned API endpoints पर rollout करने का चुनाव किया, और बढ़ती हुई probability के साथ इसे enable किया
7. cleanup
- नई implementation पर पूरी तरह switch हो जाने के बाद वे wrapper library से पुरानी implementation हटाने का संतोषजनक काम करते हैं
migration superpowers
- इस तरह की centralized migration Monzo की कुछ बुनियादी technical choices और उन tools की वजह से संभव हुई जिनमें वे लगातार निवेश करते रहे हैं
- consistent technology: सभी services Go में लिखी गई थीं और पुरानी library का एक ही version उपयोग करती थीं। इससे changes को automate करना काफ़ी आसान हो गया। उदाहरण के लिए, केवल एक single refactoring tool की ज़रूरत थी (हर language के लिए अलग नहीं)
- Monorepo: सभी service code एक single monorepo में होने से large-scale refactoring को एक single commit में करना कहीं आसान हो गया। साथ ही CI checks के ज़रिए किसी specific library के उपयोग को globally enforce करना भी संभव हुआ, जिससे consistency बनी रहती है
- bulk deployment: जब deployable components बहुत अधिक हों, तब library changes push करने के लिए automated deployment process की ज़रूरत होती है
- lightweight और flexible configuration service: deployment process सुरक्षित है, लेकिन धीमा (हर deployment में कुछ मिनट)। large number of services में नई functionality को जल्दी और तुरंत enable/disable करने के लिए एक हल्का और flexible process चाहिए
निष्कर्ष
- अतीत में वे migrations को distributed करने की कोशिश करते थे, लेकिन इसका परिणाम लगभग हमेशा अधूरे migrations और बहुत अधिक coordination effort के रूप में निकला
- यही वजह है कि Monzo centralized migration को मज़बूती से प्राथमिकता देता है। एक team को अपेक्षाकृत अधिक cost उठानी पड़ती है, लेकिन कुल मिलाकर कम effort लगता है और consistency बनाए रखने की संभावना काफ़ी बढ़ जाती है
- यह approach एक virtuous cycle बनाती है:
- migration चलाने वाली team के पास migration automation tools में निवेश करने की मज़बूत प्रेरणा होती है
- यह technical consistency बनाए रखने में भी मदद करती है (जिससे tools बनाना आसान होता है)
- लेकिन automation की सीमा को लेकर वे फिर भी practical रहते हैं — 80/20 rule लागू करते हैं
- Monzo जिन tools में लगातार निवेश करता है, उनके अलावा यह approach केवल उन कुछ core technical choices की वजह से संभव हुई जो शुरुआत में की गई थीं
- मुख्य रूप से इसलिए कि वे एक opinionated और limited technology set का उपयोग करते हैं
अभी कोई टिप्पणी नहीं है.