सॉफ़्टवेयर सिस्टम में विफलताएँ अपरिहार्य हैं। इन विफलताओं को कैसे संभाला जाता है, यह सिस्टम के प्रदर्शन, विश्वसनीयता और व्यवसाय की अंतिम पंक्ति को महत्वपूर्ण रूप से प्रभावित कर सकता है। इस पोस्ट में, मैं विफलता के सकारात्मक पहलुओं पर चर्चा करना चाहता हूँ। आपको विफलता की तलाश क्यों करनी चाहिए, विफलता क्यों अच्छी है, और विफलता से बचना आपके एप्लिकेशन की विश्वसनीयता को क्यों कम कर सकता है। हम फ़ेल-फ़ास्ट बनाम फ़ेल-सेफ़ की चर्चा से शुरू करेंगे; यह हमें सामान्य रूप से विफलताओं के बारे में दूसरी चर्चा की ओर ले जाएगा।
एक साइड नोट के रूप में, यदि आपको इस और इस श्रृंखला की अन्य पोस्ट की सामग्री पसंद है, तो मेरी जाँच करें
फेल-फास्ट सिस्टम को अप्रत्याशित स्थिति का सामना करने पर तुरंत काम करना बंद करने के लिए डिज़ाइन किया गया है। यह तत्काल विफलता त्रुटियों को जल्दी पकड़ने में मदद करती है, जिससे डिबगिंग अधिक सरल हो जाती है।
फेल-फास्ट दृष्टिकोण यह सुनिश्चित करता है कि त्रुटियों को तुरंत पकड़ा जाए। उदाहरण के लिए, प्रोग्रामिंग भाषाओं की दुनिया में, जावा इस दृष्टिकोण को अपनाता है, जब null
मान का सामना होता है तो तुरंत NullPointerException
उत्पन्न करता है, सिस्टम को रोकता है और त्रुटि को स्पष्ट करता है। यह तत्काल प्रतिक्रिया डेवलपर्स को समस्याओं को जल्दी से पहचानने और संबोधित करने में मदद करती है, जिससे उन्हें और अधिक गंभीर होने से रोका जा सकता है।
त्रुटियों को जल्दी पकड़कर और रोककर, विफलता-तेज़ प्रणालियाँ कैस्केडिंग विफलताओं के जोखिम को कम करती हैं, जहाँ एक त्रुटि से अन्य त्रुटियाँ उत्पन्न होती हैं। इससे सिस्टम में फैलने से पहले समस्याओं को रोकना और हल करना आसान हो जाता है, जिससे समग्र स्थिरता बनी रहती है।
फेल-फास्ट सिस्टम के लिए यूनिट और इंटीग्रेशन टेस्ट लिखना आसान है। यह लाभ तब और भी स्पष्ट होता है जब हमें टेस्ट विफलता को समझने की आवश्यकता होती है। फेल-फास्ट सिस्टम आमतौर पर त्रुटि स्टैक ट्रेस में समस्या की ओर सीधे इशारा करते हैं।
हालाँकि, शीघ्र विफलता वाली प्रणालियों के अपने जोखिम होते हैं, विशेष रूप से उत्पादन परिवेश में:
फेल-सेफ सिस्टम एक अलग दृष्टिकोण अपनाते हैं, जिसका लक्ष्य अप्रत्याशित परिस्थितियों में भी ठीक होकर काम करना और जारी रखना होता है। यह उन्हें अनिश्चित या अस्थिर वातावरण के लिए विशेष रूप से उपयुक्त बनाता है।
माइक्रोसर्विसेज फेल-सेफ सिस्टम का एक प्रमुख उदाहरण है, जो अपनी वास्तुकला के माध्यम से लचीलेपन को अपनाता है। सर्किट ब्रेकर, भौतिक और सॉफ्टवेयर-आधारित दोनों, कैस्केडिंग विफलताओं को रोकने के लिए विफल कार्यक्षमता को डिस्कनेक्ट करते हैं, जिससे सिस्टम को संचालन जारी रखने में मदद मिलती है।
फेल-सेफ सिस्टम यह सुनिश्चित करते हैं कि सिस्टम कठोर उत्पादन वातावरण में भी टिके रह सकें, जिससे भयावह विफलता का जोखिम कम हो जाता है। यह उन्हें विशेष रूप से मिशन-महत्वपूर्ण अनुप्रयोगों के लिए उपयुक्त बनाता है, जैसे कि हार्डवेयर डिवाइस या एयरोस्पेस सिस्टम, जहाँ त्रुटियों से सुचारू रूप से उबरना महत्वपूर्ण है।
हालाँकि, विफलता-सुरक्षित प्रणालियों में कुछ कमियाँ भी हैं:
यह निर्धारित करना चुनौतीपूर्ण है कि कौन सा तरीका बेहतर है, क्योंकि दोनों के अपने-अपने गुण हैं। फेल-फास्ट सिस्टम तत्काल डिबगिंग, कैस्केडिंग विफलताओं का कम जोखिम और बग का शीघ्र पता लगाने और समाधान प्रदान करते हैं। इससे समस्याओं को जल्दी पकड़ने और ठीक करने में मदद मिलती है, जिससे उन्हें फैलने से रोका जा सकता है।
विफलता-सुरक्षित प्रणालियां त्रुटियों को सुचारु रूप से संभाल लेती हैं, जिससे वे मिशन-महत्वपूर्ण प्रणालियों और अस्थिर वातावरण के लिए अधिक उपयुक्त हो जाती हैं, जहां भयावह विफलताएं विनाशकारी हो सकती हैं।
प्रत्येक दृष्टिकोण की शक्तियों का लाभ उठाने के लिए, एक संतुलित रणनीति प्रभावी हो सकती है:
संतुलित दृष्टिकोण के लिए कोडिंग, समीक्षा, टूलिंग और परीक्षण प्रक्रियाओं के दौरान स्पष्ट और सुसंगत कार्यान्वयन की भी आवश्यकता होती है, जिससे यह सुनिश्चित हो सके कि यह निर्बाध रूप से एकीकृत है। फेल-फास्ट ऑर्केस्ट्रेशन और ऑब्जर्वेबिलिटी के साथ अच्छी तरह से एकीकृत हो सकता है। प्रभावी रूप से, यह फेल-सेफ पहलू को डेवलपर परत के बजाय OPS की एक अलग परत में ले जाता है।
यहीं से चीजें दिलचस्प हो जाती हैं। यह फेल-सेफ और फेल-फास्ट के बीच चयन करने के बारे में नहीं है। यह उनके लिए सही लेयर चुनने के बारे में है। उदाहरण के लिए, यदि किसी त्रुटि को फेल-सेफ दृष्टिकोण का उपयोग करके डीप लेयर में हैंडल किया जाता है, तो उस पर ध्यान नहीं दिया जाएगा। यह ठीक हो सकता है, लेकिन यदि उस त्रुटि का कोई प्रतिकूल प्रभाव पड़ता है (प्रदर्शन, कचरा डेटा, भ्रष्टाचार, सुरक्षा, आदि), तो हमें बाद में समस्या होगी और हमें इसका कोई सुराग नहीं मिलेगा।
सही समाधान यह है कि सभी त्रुटियों को एक ही परत में संभाला जाए, आधुनिक प्रणालियों में सबसे ऊपरी परत OPS परत होती है और यह सबसे अधिक समझ में आता है। यह त्रुटि को उन इंजीनियरों को रिपोर्ट कर सकता है जो त्रुटि से निपटने के लिए सबसे योग्य हैं। लेकिन वे तत्काल शमन भी प्रदान कर सकते हैं, जैसे कि किसी सेवा को फिर से शुरू करना, अतिरिक्त संसाधन आवंटित करना, या किसी संस्करण को वापस करना।
हाल ही में, मैं एक व्याख्यान में था जहाँ वक्ताओं ने अपने अपडेट किए गए क्लाउड आर्किटेक्चर को सूचीबद्ध किया। उन्होंने एक ऐसे फ्रेमवर्क का उपयोग करके माइक्रोसर्विसेस के लिए शॉर्टकट लेने का विकल्प चुना जो उन्हें विफलता के मामले में पुनः प्रयास करने की अनुमति देता है। दुर्भाग्य से, विफलता उस तरह से व्यवहार नहीं करती जैसा हम चाहते हैं। आप इसे केवल परीक्षण के माध्यम से पूरी तरह से समाप्त नहीं कर सकते। पुनः प्रयास विफलता-सुरक्षित नहीं है। वास्तव में, इसका मतलब तबाही हो सकता है।
उन्होंने अपने सिस्टम का परीक्षण किया, और "यह काम करता है", यहां तक कि उत्पादन में भी। लेकिन मान लें कि कोई भयावह स्थिति उत्पन्न होती है, तो उनका पुनः प्रयास तंत्र उनके अपने सर्वर के विरुद्ध सेवा अस्वीकार करने वाले हमले के रूप में काम कर सकता है। इस तरह के एड-हॉक आर्किटेक्चर के विफल होने के तरीकों की संख्या दिमाग हिला देने वाली है।
यह बात विशेष रूप से तब महत्वपूर्ण हो जाती है जब हम असफलताओं को पुनः परिभाषित करते हैं।
सॉफ़्टवेयर सिस्टम में विफलता केवल क्रैश के बारे में नहीं है। क्रैश को एक सरल और तत्काल विफलता के रूप में देखा जा सकता है, लेकिन विचार करने के लिए और भी जटिल मुद्दे हैं। वास्तव में, कंटेनर के युग में क्रैश शायद सबसे अच्छी विफलताएँ हैं। एक सिस्टम बिना किसी रुकावट के सहजता से पुनः आरंभ होता है।
डेटा करप्शन क्रैश से कहीं ज़्यादा गंभीर और घातक है। इसके दीर्घकालिक परिणाम होते हैं। करप्ट डेटा सुरक्षा और विश्वसनीयता संबंधी समस्याओं को जन्म दे सकता है, जिन्हें ठीक करना चुनौतीपूर्ण होता है, जिसके लिए व्यापक पुनर्कार्य की आवश्यकता होती है और संभावित रूप से अप्राप्य डेटा होता है।
क्लाउड कंप्यूटिंग ने सर्किट ब्रेकर और रीट्रीज़ जैसी रक्षात्मक प्रोग्रामिंग तकनीकों को जन्म दिया है, जो विफलताओं को पकड़ने और उन्हें संभालने के लिए व्यापक परीक्षण और लॉगिंग पर जोर देती हैं। एक तरह से, इस माहौल ने हमें गुणवत्ता के मामले में पीछे धकेल दिया।
डेटा स्तर पर एक फेल-फास्ट सिस्टम ऐसा होने से रोक सकता है। बग को संबोधित करना एक सरल समाधान से परे है। इसके लिए इसके मूल कारण को समझना और पुनरावृत्ति को रोकना, व्यापक लॉगिंग, परीक्षण और प्रक्रिया सुधारों तक विस्तारित होना आवश्यक है। यह सुनिश्चित करता है कि बग को पूरी तरह से संबोधित किया गया है, जिससे इसके दोबारा होने की संभावना कम हो जाती है।
अगर यह उत्पादन में बग है, तो आपको शायद इसे वापस करना चाहिए, अगर आप तुरंत उत्पादन वापस नहीं कर सकते हैं। यह हमेशा संभव होना चाहिए, और अगर यह संभव नहीं है, तो यह ऐसी चीज है जिस पर आपको काम करना चाहिए।
किसी समस्या को ठीक करने से पहले विफलताओं को पूरी तरह से समझना चाहिए। अपनी खुद की कंपनियों में मैं अक्सर दबाव के कारण उस कदम को छोड़ देता था, एक छोटे से स्टार्टअप में यह क्षम्य है। बड़ी कंपनियों में, हमें मूल कारण को समझने की आवश्यकता है। बग और उत्पादन समस्याओं के लिए डीब्रीफिंग की संस्कृति आवश्यक है। फिक्स में प्रक्रिया शमन भी शामिल होना चाहिए जो समान समस्याओं को उत्पादन तक पहुंचने से रोकता है।
फेल-फास्ट सिस्टम को डीबग करना बहुत आसान है। उनके पास स्वाभाविक रूप से सरल वास्तुकला है, और किसी विशिष्ट क्षेत्र में किसी समस्या को इंगित करना आसान है। मामूली उल्लंघनों (जैसे, सत्यापन) के लिए भी अपवादों को फेंकना महत्वपूर्ण है। यह ढीले सिस्टम में व्याप्त बग के कैस्केडिंग प्रकारों को रोकता है।
इसे यूनिट परीक्षणों द्वारा और अधिक लागू किया जाना चाहिए जो हमारे द्वारा परिभाषित सीमाओं को सत्यापित करते हैं और सत्यापित करते हैं कि उचित अपवाद फेंके गए हैं। कोड में पुनर्प्रयासों से बचना चाहिए क्योंकि वे डिबगिंग को असाधारण रूप से कठिन बनाते हैं, और उनका उचित स्थान OPS परत में है। इसे और अधिक सुविधाजनक बनाने के लिए, डिफ़ॉल्ट रूप से टाइमआउट कम होना चाहिए।
विफलता ऐसी चीज़ नहीं है जिसे हम टाल सकते हैं, भविष्यवाणी कर सकते हैं, या पूरी तरह से परीक्षण कर सकते हैं। विफलता होने पर हम केवल यही कर सकते हैं कि आघात को कम किया जाए। अक्सर यह "नरमीकरण" लंबे समय तक चलने वाले परीक्षणों का उपयोग करके प्राप्त किया जाता है जिसका उद्देश्य हमारे अनुप्रयोगों के कमजोर बिंदुओं को खोजने के लक्ष्य के साथ यथासंभव चरम स्थितियों को दोहराना होता है। यह शायद ही कभी पर्याप्त होता है। मजबूत सिस्टम को अक्सर वास्तविक उत्पादन विफलताओं के आधार पर इन परीक्षणों को संशोधित करने की आवश्यकता होती है।
फेल-सेफ का एक बेहतरीन उदाहरण REST प्रतिक्रियाओं का कैश होगा जो हमें सेवा बंद होने पर भी काम करते रहने देता है। दुर्भाग्य से, इससे जटिल आला मुद्दे पैदा हो सकते हैं जैसे कैश पॉइज़निंग या ऐसी स्थिति जिसमें प्रतिबंधित उपयोगकर्ता के पास कैश के कारण अभी भी पहुँच थी।
फ़ेल-सेफ़ को सिर्फ़ प्रोडक्शन/स्टेजिंग और OPS लेयर में ही सबसे बेहतर तरीके से लागू किया जाता है। इससे प्रोडक्शन और डेव के बीच होने वाले बदलावों की मात्रा कम हो जाती है, हम चाहते हैं कि वे यथासंभव समान हों, फिर भी यह एक ऐसा बदलाव है जो प्रोडक्शन पर नकारात्मक प्रभाव डाल सकता है। हालाँकि, इसके लाभ बहुत ज़्यादा हैं, क्योंकि ऑब्ज़र्वेबिलिटी से सिस्टम विफलताओं की स्पष्ट तस्वीर मिल सकती है।
यहाँ चर्चा मेरे हाल ही के अवलोकनीय क्लाउड आर्किटेक्चर के निर्माण के अनुभव से थोड़ी प्रभावित है। हालाँकि, यही सिद्धांत किसी भी प्रकार के सॉफ़्टवेयर पर लागू होता है, चाहे वह एम्बेडेड हो या क्लाउड में। ऐसे मामलों में हम अक्सर कोड में फेल-सेफ को लागू करना चुनते हैं, इस मामले में मैं इसे एक विशिष्ट परत में लगातार और सचेत रूप से लागू करने का सुझाव दूंगा।
लाइब्रेरी/फ्रेमवर्क का एक विशेष मामला भी है जो अक्सर इन स्थितियों में असंगत और खराब तरीके से प्रलेखित व्यवहार प्रदान करता है। मैं खुद अपने कुछ कामों में ऐसी असंगतता का दोषी हूं। यह एक आसान गलती है।
यह डिबगिंग के सिद्धांत पर मेरी आखिरी पोस्ट है जो डिबगिंग पर मेरी किताब/कोर्स का हिस्सा है। हम अक्सर डिबगिंग को उस कार्रवाई के रूप में सोचते हैं जो हम तब करते हैं जब कुछ विफल हो जाता है। ऐसा नहीं है। डिबगिंग उसी क्षण शुरू हो जाती है जब हम कोड की पहली पंक्ति लिखते हैं। हम ऐसे निर्णय लेते हैं जो डिबगिंग प्रक्रिया को प्रभावित करेंगे क्योंकि हम कोड करते हैं, अक्सर हम इन निर्णयों से अनजान होते हैं जब तक कि हम विफल नहीं हो जाते।
मुझे उम्मीद है कि यह पोस्ट और सीरीज़ आपको ऐसा कोड लिखने में मदद करेगी जो अज्ञात के लिए तैयार हो। डिबगिंग, अपने स्वभाव से, अप्रत्याशित से निपटता है। परीक्षण मदद नहीं कर सकते। लेकिन जैसा कि मैंने अपने पिछले पोस्ट में बताया, ऐसे कई सरल अभ्यास हैं जिन्हें हम अपना सकते हैं जो तैयारी को आसान बना देंगे। यह एक बार की प्रक्रिया नहीं है, यह एक पुनरावृत्त प्रक्रिया है जिसमें विफलता का सामना करने पर लिए गए निर्णयों का पुनर्मूल्यांकन करने की आवश्यकता होती है।