2.20

View in English

2.20 त्रुटि प्रबंधन और रेज़िलिएंस पैटर्न

अवलोकन और प्रेरणा

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

यह अध्याय कोड और कंपोनेंट स्तर पर रेज़िलिएंस के बारे में है: किसी फ़ंक्शन, मॉड्यूल, या API के भीतर के विकल्पों के बारे में। यह अध्याय 3.5 का पूरक है, जो सिस्टम स्तर पर रेज़िलिएंस (लोड बैलेंसिंग, रेप्लिकेशन, सेवाओं के बीच फ़ेलओवर) को कवर करता है। अध्याय 3.5 पूरे प्लेटफ़ॉर्म को तब भी खड़ा रखता है जब कोई क्षेत्र अंधेरे में चला जाता है; यह अध्याय एक अकेले रिक्वेस्ट को आपका डेटा भ्रष्ट करने या बिना किसी निशान के गायब होने से बचाता है। दोनों एक-दूसरे को मज़बूत करते हैं। यदि आपके आर्किटेक्चर में एक सर्किट ब्रेकर है लेकिन उसके पीछे का कोड एक्सेप्शन निगल जाता है, तो उसका बहुत कम मतलब रह जाता है, और एक डिफ़ेंसिव फ़ंक्शन आपको नहीं बचा सकता अगर आसपास के सिस्टम में कोई रिडंडेंसी नहीं है। यह अध्याय अध्याय 2.9 (सॉफ़्टवेयर निर्माण) पर भी आधारित है, जहाँ त्रुटि प्रबंधन अनेक अनुशासनों में से एक था; यहाँ यह पूरा विषय बन जाता है।

बड़ी टीमों के लिए, संगति (consistency) ही सबसे बड़ा इनाम है। जब सैकड़ों इंजीनियर सैकड़ों अलग-अलग तरीकों से त्रुटियाँ संभालते हैं, तो हर सेवा एक पहेली बन जाती है और हर घटना एक खुदाई। उद्यम परिवेश में, यह असंगति हर ऑडिट और हर इंटीग्रेशन की लागत बढ़ा देती है। सरकार और अन्य उच्च-दांव वाले सिस्टम में, दांव और भी तीखे हैं: सटीकता, सुरक्षित विफलता, और एक स्पष्ट ऑडिट ट्रेल ऐसी विशेषताएँ नहीं हैं जिन्हें बाद में जोड़ा जाए, बल्कि वे गुण हैं जो सिस्टम में पहले कमिट से ही होने चाहिए। एक लाभ प्रणाली जो चुपचाप गलत गणना करती है, या एक रिकॉर्ड प्रणाली जो बिना लॉग किए किसी विफलता को खो देती है, केवल बग-युक्त नहीं है। यह इस तरह से अविश्वसनीय है जो उसके पीछे की संस्था को क्षीण कर देती है।

मुख्य सिद्धांत

  • त्रुटियों, दोषों, और विफलताओं के बीच अंतर करें, और प्रत्येक को सही स्तर पर संभालें।
  • फ़ेल-फ़ास्ट या फ़ेल-सेफ़ को जानबूझकर, संदर्भ के अनुसार चुनें, कभी संयोगवश नहीं।
  • हर फ़ंक्शन और API के त्रुटि-प्रबंधन अनुबंध (contract) को स्पष्ट और ईमानदार बनाएँ।
  • सीमाओं (boundaries) पर सत्यापन करें; उनके भीतर भरोसा करें; व्यामोह के बिना बचाव करें।
  • किसी त्रुटि को कभी चुपचाप निगलें नहीं; उसे सामने लाएँ, रैप करें, या जानबूझकर संभालें।
  • आइडेम्पोटेंस, टाइमआउट, बैकऑफ़, और जिटर के साथ रीट्राई को सुरक्षित बनाएँ।
  • त्रुटि पथ (error path) को उतना ही डिज़ाइन ध्यान दें जितना हैप्पी पथ को देते हैं।

सिफ़ारिशें

त्रुटियों, दोषों, और विफलताओं में अंतर करें

लापरवाह शब्दावली लापरवाह प्रबंधन को जन्म देती है, इसलिए स्पष्ट शब्दों से शुरुआत करें। दोष (fault) सिस्टम में एक खामी है: एक बग, एक गलत कॉन्फ़िगरेशन, एक डिपेंडेंसी जो डाउन है। त्रुटि (error) वह ग़लत आंतरिक स्थिति है जो किसी दोष के कारण उत्पन्न होती है: जहाँ कोई मान होना चाहिए वहाँ null, एक बैलेंस जो अब मेल नहीं खाता। विफलता (failure) वह है जो बाहरी पर्यवेक्षक देखता है: रिक्वेस्ट गलत उत्तर लौटाती है, या कोई उत्तर नहीं लौटाती। एक दोष कई त्रुटियाँ पैदा कर सकता है, और कई त्रुटियाँ किसी के भी दृश्य विफलता बनने से पहले पकड़ी जा सकती हैं। त्रुटि प्रबंधन का पूरा उद्देश्य इसी शृंखला को तोड़ना है, त्रुटि को उपयोगकर्ता या ऑडिटर तक विफलता के रूप में पहुँचने से पहले पकड़ लेना है।

यह शब्दावली यह भी बताती है कि कार्रवाई कहाँ करनी है। दोषों को समीक्षा, टेस्टिंग, और कॉन्फ़िगरेशन में संबोधित किया जाता है। त्रुटियों को रनटाइम पर इस अध्याय के पैटर्न के ज़रिए संबोधित किया जाता है। विफलताओं को ऑब्ज़र्वेबिलिटी (अध्याय 9.2) और अध्याय 3.5 के सिस्टम-स्तरीय रेज़िलिएंस के ज़रिए संबोधित किया जाता है। जब आपकी टीम ये शब्द साझा करती है, तो घटना समीक्षाएँ अधिक धारदार हो जाती हैं: आप ठीक-ठीक बता सकते हैं कि शृंखला कहाँ टूटनी चाहिए थी और नहीं टूटी, बजाय इसके कि “बग” क्या था इस पर बहस करें।

संदर्भ के अनुसार फ़ेल-फ़ास्ट या फ़ेल-सेफ़ चुनें

फ़ेल-फ़ास्ट का अर्थ है कि जिस क्षण कुछ गलत हो, उसी क्षण रुक जाना, खराब स्थिति पर आगे बढ़ने से इनकार करना ताकि समस्या तेज़ी से और अपने कारण के नज़दीक सामने आए। फ़ेल-सेफ़ का अर्थ है एक ज्ञात, हानिरहित स्थिति में उतर जाना और जो सुरक्षित रूप से दिया जा सकता है उसे देना जारी रखना। कोई भी सार्वभौमिक रूप से सही नहीं है, और कौशल यह है कि संदर्भ के अनुसार चुना जाए। डेवलपमेंट के दौरान और आंतरिक सीमाओं पर, फ़ेल-फ़ास्ट आपका मित्र है: एक प्रोग्राम जो किसी उल्लंघित इनवेरिएंट पर रुक जाता है, वह आपको एक लंबे रहस्य के बजाय एक छोटा स्टैक ट्रेस देता है। प्रोडक्शन में, उपयोगकर्ता-सम्मुख सिस्टम के किनारों पर, फ़ेल-सेफ़ अक्सर जीतता है: एक सिफ़ारिश पैनल जो कुछ नहीं लौटाता, वह एक चेकआउट पेज से बेहतर है जो लोड ही न हो।

हर सीमा के लिए इसे जानबूझकर तय करें और निर्णय को लिख लें। एक फ़्लाइट-कंट्रोल या मेडिकल-डिवाइस कंपोनेंट एक परिभाषित स्थिति में फ़ेल-सेफ़ होता है, क्योंकि भ्रष्ट डेटा पर आगे बढ़ना किसी को नुकसान पहुँचा सकता है। एक लेजर पोस्टिंग फ़ेल-फ़ास्ट होती है, क्योंकि गलत एंट्री पोस्ट करना कुछ भी पोस्ट न करने से बुरा है। गलत जोड़ी दोनों दिशाओं में खतरनाक है: जहाँ फ़ेल-फ़ास्ट चाहिए था वहाँ फ़ेल-सेफ़ भ्रष्टाचार को छुपा देता है, और जहाँ फ़ेल-सेफ़ चाहिए था वहाँ फ़ेल-फ़ास्ट एक सौंदर्यगत हिचकी को एक आउटेज में बदल देता है।

अपना त्रुटि-संकेतन तंत्र चुनें और इसे संगत रूप से इस्तेमाल करें

भाषाएँ आपको यह संकेत देने के दो व्यापक तरीके देती हैं कि कुछ गलत हुआ है। एक्सेप्शन हैंडलिंग कॉल स्टैक में एक ऑब्जेक्ट को तब तक ऊपर फेंकती है जब तक कोई हैंडलर उसे पकड़ न ले, जिससे त्रुटि पथ मुख्य लॉजिक से अलग हो जाता है। दूसरा विकल्प स्पष्ट त्रुटि मान (explicit error values) हैं: फ़ंक्शन एक परिणाम और एक त्रुटि दोनों लौटाता है, और कॉलर को दोनों की जाँच करनी होती है। कई आधुनिक भाषाएँ इसे Result टाइप के साथ औपचारिक रूप देती हैं, जिसे अक्सर Result या Either कहा जाता है, जो कॉलर को मान इस्तेमाल करने से पहले सफलता या विफलता को अनरैप करने पर मजबूर करता है। हर तरीके की अपनी कीमत है। एक्सेप्शन हैप्पी पथ को साफ़ रखते हैं लेकिन कंट्रोल फ़्लो को छुपा सकते हैं और डेवलपर्स को कैच-ऑल ब्लॉक की ओर लुभा सकते हैं जो जानकारी मिटा देते हैं। स्पष्ट परिणाम हर विफलता को टाइप सिग्नेचर में दृश्यमान बनाते हैं लेकिन औपचारिकता बढ़ाते हैं और यदि भाषा जाँच को अनिवार्य नहीं बनाती तो उन्हें नज़रअंदाज़ किया जा सकता है।

सही उत्तर इस बारे में कम है कि कौन-सा तंत्र इस्तेमाल किया जाए, और इस बारे में ज़्यादा है कि संगति और ईमानदारी बनी रहे। वह मुहावरा चुनें जिसे आपकी भाषा और इकोसिस्टम पसंद करते हैं, और इसे अपनी सभी सेवाओं में समान रूप से लागू करें ताकि पाठक को हमेशा पता रहे कि विफलता कैसे यात्रा करती है। एक्सेप्शन को वास्तव में असाधारण स्थितियों के लिए सुरक्षित रखें, न कि “उपयोगकर्ता नहीं मिला” जैसे सामान्य कंट्रोल फ़्लो के लिए, जिसे एक सामान्य परिणाम के रूप में मॉडल करना बेहतर है। आप जो भी चुनें, किसी विफलता को कभी अदृश्य न होने दें: एक अनचेक्ड त्रुटि मान उतना ही खतरनाक है जितना एक खाली कैच ब्लॉक। एक बड़े कोडबेस में, एक लिखित परंपरा और उपेक्षित त्रुटियों को चिह्नित करने वाला एक लिंटर किसी भी व्यक्ति की व्यक्तिगत पसंद से बेहतर होता है।

त्रुटि-प्रबंधन अनुबंध को स्पष्ट बनाएँ

हर फ़ंक्शन और हर API का एक त्रुटि-प्रबंधन अनुबंध होता है, चाहे किसी ने इसे लिखा हो या नहीं। यह इस बात का जवाब देता है: यहाँ क्या गलत हो सकता है, आपको इसके बारे में कैसे पता चलेगा, और जब यह होता है तो स्थिति के बारे में क्या गारंटी है? उस अनुबंध को स्पष्ट बनाएँ। दस्तावेज़ करें कि कोई फ़ंक्शन कौन-सी त्रुटियाँ लौटा या फेंक सकता है, रिकवरेबल त्रुटियों (जिन्हें कॉलर समझदारी से रीट्राई या फ़ॉलबैक कर सकता है) को अनरिकवरेबल त्रुटियों (जिन्हें कॉलर ठीक नहीं कर सकता और जिन्हें आगे बढ़ाना या रोकना चाहिए) से अलग करें, और बताएँ कि क्या विफलता पर फ़ंक्शन स्थिति को अपरिवर्तित छोड़ता है। यह अंतिम गुण, जिसे कभी-कभी स्ट्रॉन्ग एक्सेप्शन गारंटी कहा जाता है, का मतलब है कि विफल कॉल ऐसी है मानो हुई ही न हो, जो ठीक वही है जो कॉलर को सुरक्षित रूप से रीट्राई करने देता है।

किसी सार्वजनिक या क्रॉस-टीम API के लिए, यह अनुबंध इंटरफ़ेस का हिस्सा है, उतना ही वास्तविक जितने पैरामीटर के टाइप। एक छोटा, स्थिर त्रुटि वर्गीकरण (taxonomy) डिज़ाइन करें: श्रेणियों का एक सीमित समूह जैसे वैलिडेशन एरर, नॉट फ़ाउंड, कॉन्फ़्लिक्ट, अनऑथराइज़्ड, डिपेंडेंसी-अनअवेलेबल, और इंटरनल एरर। इसके बाद कॉलर स्ट्रिंग पार्स किए बिना श्रेणी के आधार पर शाखा (branch) ले सकते हैं। एक स्पष्ट वर्गीकरण कई सेवाओं में त्रुटि प्रबंधन को कंपोज़ेबल बनाता है, और यह विफलताओं को ऑडिट-योग्य बनाता है, क्योंकि हर विफलता एक ज्ञात, नामित प्रकार से मेल खाती है।

सीमाओं पर सत्यापन करें और व्यामोह के बिना बचाव करें

किसी ट्रस्ट सीमा को पार करने वाले डेटा (एक नेटवर्क रिक्वेस्ट, एक फ़ाइल, उपयोगकर्ता इनपुट, दूसरी सेवा से आया संदेश) को तब तक शत्रुतापूर्ण मानें जब तक उसे सत्यापित न कर लिया जाए, और उसे सीमा पर, एक बार, पूरी तरह से सत्यापित करें। यह समझ-बूझ के साथ लागू किया गया डिफ़ेंसिव प्रोग्रामिंग है। किसी ऐसे मॉड्यूल के भीतर जिसके इनपुट आप पहले ही सत्यापित कर चुके हैं, हर पंक्ति पर बार-बार जाँच करना लॉजिक को छुपा देता है और ठीक उन्हीं विफलताओं को दबा देता है जिन्हें आप देखना चाहते हैं। अनुशासन यह है: किनारों पर सख्ती से बचाव करें, उनके भीतर भरोसा करें। जहाँ डेटा प्रवेश करता है वहाँ संरचना, सीमाओं (ranges), और इनवेरिएंट को सत्यापित करें, इसे ऐसे टाइप में बदलें जो अवैध स्थितियों को अप्रस्तुत करने योग्य बना दें, और आंतरिक कोड को यह मान लेने दें कि वह साफ़ डेटा के साथ काम कर रहा है।

व्यामोह की एक वास्तविक कीमत है। null जाँचों और डिफ़ेंसिव शाखाओं में दबा हुआ कोड पढ़ना कठिन होता है, और इससे भी बुरा यह कि यह अक्सर एक स्पष्ट विफलता को एक चुप्पी में बदल देता है, जहाँ अलार्म बजाना चाहिए था वहाँ एक डिफ़ॉल्ट मान लौटा देता है। ऐसी डिफ़ेंसिवनेस जो बग को छुपा देती है, सुरक्षा नहीं है; यह टालमटोल है।

रीट्राई को सुरक्षित, सीमित, और विनम्र बनाएँ

कई दोष अस्थायी होते हैं: एक क्षणिक नेटवर्क झटका, एक सेवा का पुनरारंभ, एक संक्षिप्त लॉक कंटेंशन। किसी भी वितरित सिस्टम (अध्याय 3.3) में, ये आंशिक विफलताएँ अपवाद नहीं बल्कि सामान्य स्थिति होती हैं। रीट्राई करना स्वाभाविक प्रतिक्रिया है, लेकिन एक भोली-भाली रीट्राई लूप एक लोडेड बंदूक है। पहला, जिस ऑपरेशन को आप रीट्राई करते हैं उसे आइडेम्पोटेंट बनाएँ, यानी उसे दो बार करने का प्रभाव एक बार करने जैसा ही हो। आइडेम्पोटेंस के बिना, टाइमआउट के बाद रीट्राई एक कार्ड को दो बार चार्ज कर सकता है या दो रिकॉर्ड बना सकता है, क्योंकि आप नहीं बता सकते कि पहला प्रयास विफल हुआ था या केवल उसकी पावती (acknowledgement) खो गई थी। writes के लिए आइडेम्पोटेंसी कुंजियों (idempotency keys) का इस्तेमाल करें ताकि प्राप्तकर्ता किसी दोहराव को पहचान और डिड्यूप कर सके।

दूसरा, हर रिमोट कॉल पर एक टाइमआउट लगाएँ ताकि कोई अटकी हुई डिपेंडेंसी आपको भी न अटका दे। तीसरा, एक्सपोनेंशियल बैकऑफ़ के साथ रीट्राई के बीच अंतराल रखें, हर प्रयास के बाद प्रतीक्षा को दोगुना करें, और जिटर (एक छोटा यादृच्छिक विलंब) जोड़ें ताकि एक साथ रिकवर हो रहे हज़ारों क्लाइंट एक ऐसी भगदड़ में समन्वित न हो जाएँ जो रिकवर हो रही सेवा को फिर से नीचे गिरा दे। चौथा, रीट्राई की संख्या और कुल समय की सीमा तय करें, फिर शालीनता से हार मान लें। सीमा, बैकऑफ़, जिटर, और आइडेम्पोटेंस के बिना रीट्राई उन सबसे आम तरीकों में से एक है जिनसे एक छोटी हिचकी अपने ही कारण बनी एक आउटेज बन जाती है।

कोड में सर्किट ब्रेकर, बल्कहेड, और ग्रेसफ़ुल डिग्रेडेशन जोड़ें

जब कोई डिपेंडेंसी वास्तव में डाउन हो, तो उसे रीट्राई करना केवल प्रयास बर्बाद करता है और गड्ढे को गहरा करता है। एक सर्किट ब्रेकर किसी डिपेंडेंसी को की गई कॉल की विफलता दर पर नज़र रखता है, और जब विफलताएँ एक सीमा (threshold) पार कर जाती हैं, तो यह बर्बाद कॉल पर प्रतीक्षा करने के बजाय एक कूलडाउन अवधि के लिए तुरंत विफल होने के लिए “खुल” जाता है। कूलडाउन के बाद यह एक ट्रायल कॉल को गुज़रने देता है और यदि डिपेंडेंसी ठीक हो गई है तो फिर से बंद हो जाता है। यह आपके कॉलरों (ढेर सारे टाइमआउट के बजाय तेज़, पूर्वानुमेय विफलताएँ) और संघर्षरत डिपेंडेंसी (ठीक होने के लिए साँस लेने की जगह) दोनों की रक्षा करता है। बल्कहेड पैटर्न, जिसका नाम जहाज़ के वॉटरटाइट कंपार्टमेंट के नाम पर रखा गया है, संसाधनों को अलग-थलग करता है ताकि एक संतृप्त डिपेंडेंसी हर थ्रेड या कनेक्शन को खा न जाए और पूरी प्रक्रिया को डुबो न दे; आप हर डिपेंडेंसी को उसका अपना सीमित पूल देते हैं।

ये पैटर्न कोड स्तर पर ग्रेसफ़ुल डिग्रेडेशन के साथ जुड़ते हैं: जब कोई गैर-आवश्यक डिपेंडेंसी अनुपलब्ध हो, तो एक त्रुटि के बजाय एक कम लेकिन उपयोगी परिणाम लौटाएँ। स्टेलनेस नोट के साथ कैश्ड डेटा दिखाएँ, पर्सनलाइज़ेशन पैनल छुपाएँ, write को बाद के लिए कतार में डाल दें। यह अध्याय 3.5 के सिस्टम-स्तरीय रेज़िलिएंस का स्थानीय पूरक है: आर्किटेक्चर मशीनों में रिडंडेंसी प्रदान करता है, और आपका कोड तब समझदार व्यवहार प्रदान करता है जब कोई हिस्सा गायब हो।

त्रुटियों को संदर्भ के साथ रैप करें और उन्हें कभी न निगलें

एक त्रुटि जो अपने घटित होने के स्थान से दस परतों ऊपर “connection refused” पढ़ती है, लगभग बेकार है। जैसे-जैसे कोई त्रुटि फैलती है, उसे संदर्भ के साथ रैप करें: आप क्या करने की कोशिश कर रहे थे, कौन-सी इकाई या रिक्वेस्ट, कौन-सी डिपेंडेंसी, साथ ही मूल कारण को सुरक्षित रखते हुए ताकि जड़ खो न जाए। अच्छी भाषाएँ और लाइब्रेरियाँ इस त्रुटि-शृंखलन (error chaining) का सीधे समर्थन करती हैं। लक्ष्य यह है कि एक ही लॉग लाइन ऑन-कॉल इंजीनियर को बता दे कि क्या विफल हुआ, किस ऑपरेशन के दौरान, किस इनपुट के लिए। यह अध्याय 9.2 की ऑब्ज़र्वेबिलिटी और अध्याय 2.15 की डिबगिंग के लिए कच्चा माल है।

सबसे बड़ा पाप है किसी त्रुटि को निगल जाना: एक खाली कैच ब्लॉक, एक उपेक्षित रिटर्न वैल्यू, एक catch जो डीबग स्तर पर लॉग करता है और ऐसे आगे बढ़ता है मानो कुछ हुआ ही नहीं। निगली गई त्रुटि गायब नहीं होती; यह बाद में भ्रष्ट डेटा या एक अकथनीय दोष के रूप में फिर से उभरती है, अब अपने कारण से अलग हो चुकी। हर त्रुटि को इन तीन नियतियों में से एक को पूरा करना चाहिए: उसे संभालें (रिकवर करें या डिग्रेड करें), उसे रैप करके आगे बढ़ाएँ, या, स्टैक के शीर्ष पर, उसे पूरे संदर्भ के साथ लॉग करें और विफल हो जाएँ। यदि आप किसी त्रुटि को पकड़ते हैं और इनमें से कुछ भी नहीं करते, तो आपने अपने भविष्य के स्वयं से एक भावी घटना को छुपाने का विकल्प चुना है।

ट्रेड-ऑफ़: फ़ायदे और नुकसान

तरीकाफ़ायदेनुकसान
एक्सेप्शनसाफ़ हैप्पी पथ; अनचेक्ड होने पर नज़रअंदाज़ करना मुश्किलछुपा हुआ कंट्रोल फ़्लो; कैच-ऑल मिटाव के लिए लुभाता है
स्पष्ट त्रुटि मान / Result टाइपविफलता सिग्नेचर में दृश्यमान; प्रबंधन को अनिवार्य बनाता हैअधिक औपचारिकता; प्रवर्तन के बिना नज़रअंदाज़ किया जा सकता है
फ़ेल-फ़ास्टबग को ज़ोर से, कारण के नज़दीक सामने लाता हैकिनारे पर इस्तेमाल होने पर खराब उपयोगकर्ता अनुभव
फ़ेल-सेफ़सेवा देना जारी रखता है; उपयोगकर्ताओं और डेटा की रक्षा करता हैजहाँ फ़ेल-फ़ास्ट चाहिए था वहाँ इस्तेमाल होने पर भ्रष्टाचार छुपा सकता है
बैकऑफ़ के साथ रीट्राईअस्थायी दोषों को स्वचालित रूप से झेल लेता हैआइडेम्पोटेंस के बिना लोड बढ़ाता है और डबल-राइट करता है
सर्किट ब्रेकरतेज़ विफलताएँ; डिपेंडेंसियों को ठीक होने देता हैअतिरिक्त स्थिति और ट्यूनिंग; किसी स्थायी समस्या को छुपा सकता है
सीमाओं पर डिफ़ेंसिव सत्यापनखराब डेटा को जल्दी, एक बार, ज़ोर से पकड़ता हैअति करने पर लॉजिक को अव्यवस्थित करता है और असली विफलताओं को छुपाता है

मुख्य तनाव दृश्यता और शोर के बीच है। त्रुटियों को बहुत चुपचाप संभालें तो आप समस्याओं को तब तक छुपाते हैं जब तक वे महंगी न हो जाएँ; उन्हें बहुत ज़ोर से और हर जगह संभालें, तो आप संकेत को औपचारिकता में डुबो देते हैं और उन विफलताओं को छुपा देते हैं जो मायने रखती हैं। इसे स्थान और इरादे के आधार पर हल करें। सीमाओं पर ज़ोरदार और सख्त रहें, जहाँ खराब डेटा और डिपेंडेंसी विफलताएँ प्रवेश करती हैं। आंतरिक भाग में चुप और भरोसेमंद रहें, जहाँ इनपुट पहले से ही साफ़ हैं। हर सीमा के लिए फ़ेल-फ़ास्ट बनाम फ़ेल-सेफ़ तय करें और उसे लिख लें। लक्ष्य ऐसा कोड है जहाँ हर विफलता का ठीक एक स्पष्ट स्वामी और एक स्पष्ट नियति हो, और कुछ भी दरारों के बीच चुपचाप न गिरे।

अपनी टीम के साथ चर्चा करने के लिए प्रश्न

  1. क्या हमारी सभी सेवाओं में एक साझा त्रुटि वर्गीकरण और त्रुटि-प्रबंधन परंपरा है, या हर टीम अपने हिसाब से सुधार करती है? एक बड़ी टीम में यही वह अंतर है जो कंपोज़ होने वाली विफलताओं और भ्रम पैदा करने वाली विफलताओं के बीच होता है। जब एक सेवा किसी वैलिडेशन समस्या के लिए HTTP 500 लौटाती है, दूसरी एक टाइप्ड एक्सेप्शन फेंकती है, और तीसरी एक null लौटाती है, तो हर इंटीग्रेशन एक सौदेबाज़ी बन जाती है और हर घटना एक अनुवाद अभ्यास। एक ही तार्किक विफलता, मान लीजिए “रिकॉर्ड नहीं मिला,” के उदाहरण लाएँ जैसे वह आपकी तीन सेवाओं में दिखती है, और देखें कि वे इसे कितने अलग-अलग तरीकों से संकेत करती हैं। इसका जवाब एक लिखित मानक बनना चाहिए: त्रुटि श्रेणियों का एक सीमित समूह, उन्हें संकेत करने का एक संगत तरीका, और एक लिंटर या समीक्षा चेकलिस्ट जो इसे लागू करे। यहाँ संगति हर भावी इंटीग्रेशन, ऑडिट, और ऑन-कॉल शिफ्ट में लाभ देती है।

  2. क्या हमने हर महत्वपूर्ण सीमा के लिए जानबूझकर फ़ेल-फ़ास्ट या फ़ेल-सेफ़ चुना है, और क्या कोड उस चुनाव से मेल खाता है? अधिकांश टीमों ने यह निर्णय कभी स्पष्ट रूप से नहीं लिया है, जिसका मतलब है कि यह निर्णय उस व्यक्ति ने ले लिया जिसने पहले कोड लिखा, और असंगत रूप से। प्रतिस्पर्धी विचार वास्तविक हैं: फ़ेल-सेफ़ उपयोगकर्ताओं को सेवा देना जारी रखता है लेकिन भ्रष्टाचार को फैलने दे सकता है, जबकि फ़ेल-फ़ास्ट डेटा की रक्षा करता है लेकिन एक मामूली डिपेंडेंसी आउटेज को एक दृश्य विफलता में बदल सकता है। अपना घटना इतिहास लाएँ और सबसे बुरी कुछ घटनाओं के लिए पूछें कि क्या कोड उस तरह विफल हुआ जैसा आप पहले से पूछे जाने पर चुनते। जो प्रमाण आप चाहते हैं वह है आपकी सीमाओं का एक मानचित्र जिसमें हर एक पर एक जानबूझकर लेबल हो, खासकर जहाँ भी पैसा, सुरक्षा, या नागरिक रिकॉर्ड शामिल हों। जहाँ लेबल और कोड असहमत हों, वहाँ आपको अपना अगला सुधार मिल गया है।

  3. हमने आखिरी बार जानबूझकर किसी त्रुटि पथ का अभ्यास कब किया, और क्या उसने डिज़ाइन के अनुसार व्यवहार किया? त्रुटि पथ आमतौर पर आपके पास मौजूद सबसे कम टेस्ट किया गया कोड है, फिर भी यही वह जगह है जहाँ भरोसा जीता या खोया जाता है, और “हम सुरक्षित रूप से विफल होते हैं” एक ऐसा दावा है जिसे आप साबित नहीं कर सकते अगर आपने इसे कभी होते हुए नहीं देखा। बिना आइडेम्पोटेंस के एक रीट्राई लूप, एक सर्किट ब्रेकर जिसकी सीमा गलत है, एक शायद ही कभी पहुँची जाने वाली शाखा में निगली गई एक्सेप्शन: ये तब तक छुपी रहती हैं जब तक कोई असली घटना इन्हें आपके लिए न खोज ले। एक यथार्थवादी वातावरण में जानबूझकर विफलता डालने (एक मारी गई डिपेंडेंसी, एक प्रेरित टाइमआउट, एक बिगड़ा हुआ पेलोड) के परिणाम लाएँ। इसके बाद की कार्रवाई यह है कि विफलता इंजेक्शन को नियमित बनाया जाए, ताकि रिकवरी, डिग्रेडेशन, और सुरक्षित-विफलता व्यवहार को उम्मीद के बजाय लगातार सत्यापित किया जाए। कोई भी त्रुटि पथ जिसे आपने कभी ट्रिगर नहीं किया, वह एक ऐसा वादा है जिसे आपने कभी परखा नहीं।

  4. हमारे कौन-से write ऑपरेशन आइडेम्पोटेंट हैं, और कहाँ एक खोई हुई पावती के बाद रीट्राई किसी वास्तविक-दुनिया के प्रभाव को दोहरा देगा, जैसे कोई भुगतान या रिकॉर्ड? रीट्राई सबसे आम रेज़िलिएंस प्रतिक्रिया है, और लापरवाही से किए जाने पर, यह सबसे आम तरीका है जिससे एक अस्थायी झटका दोहराए गए पैसे या डेटा में बदल जाता है। एक बड़ी टीम में, रीट्राई लॉजिक अक्सर साझा क्लाइंट, मिडलवेयर, और व्यक्तिगत सेवाओं में एक साथ रहता है, इसलिए एक ही write को कई परतों पर रीट्राई किया जा सकता है और कुल व्यवहार का कोई स्वामी नहीं होता। प्रतिस्पर्धी खिंचाव यह है कि आइडेम्पोटेंसी कुंजियाँ, डिड्यूप्लिकेशन, और संग्रहीत रिक्वेस्ट परिणाम स्टोरेज और कोड जोड़ते हैं, और डिलीवरी दबाव में टीमें उन writes के लिए इन्हें छोड़ देती हैं जिन्हें वे ग़लती से सुरक्षित मान लेती हैं। अपने बाह्य रूप से दृश्यमान writes की एक सूची लाएँ, हर एक को चिह्नित करते हुए कि क्या उसमें आइडेम्पोटेंसी कुंजी है और प्राप्तकर्ता दोहराव को कैसे पहचानता और डिड्यूप करता है। उद्यम और सरकारी परिवेश में, पहले उन्हें चिह्नित करें जो पैसा हिलाते हैं या किसी नागरिक के रिकॉर्ड को बदलते हैं, क्योंकि दोहरा भुगतान या दोहराया गया लाभ केवल एक दोष नहीं बल्कि एक ऑडिट निष्कर्ष और कभी-कभी कानूनी जोखिम है।

  5. क्या हमारे टाइमआउट, सर्किट ब्रेकर, और बल्कहेड एक साझा, टेस्ट की गई लाइब्रेरी से आते हैं, या हर टीम उन्हें खुद हाथ से बनाती है? इन पैटर्न को समझाना आसान है और सूक्ष्म रूप से गलत बनाना भी आसान है: एक गायब टाइमआउट, एक ब्रेकर सीमा जो कभी ट्रिप नहीं होती, एक कनेक्शन पूल जिसका आकार ऐसा है कि एक धीमी डिपेंडेंसी पूरी प्रक्रिया को भूखा रख देती है। जब हर टीम इन्हें फिर से लागू करती है, तो आप कई थोड़ी-बहुत टूटी हुई प्रतियाँ जमा कर लेते हैं और एक बार खामी मिल जाने पर उसे ठीक करने के लिए कोई एक जगह नहीं होती। प्रतिस्पर्धी विचार यह है कि एक साझा लाइब्रेरी एक सामान्य इंटरफ़ेस और अपग्रेड गति थोपती है, और असामान्य रनटाइम या लेटेंसी आवश्यकताओं वाली टीमें इससे परेशान हो सकती हैं या इसके इर्द-गिर्द रास्ता निकाल सकती हैं। यह सर्वेक्षण लाएँ कि प्रोडक्शन में वास्तव में कितने अलग-अलग रीट्राई-और-ब्रेकर कार्यान्वयन चल रहे हैं, और किन सेवाओं के आउटबाउंड कॉल पर अभी भी बिल्कुल कोई टाइमआउट नहीं है। एक बड़े उद्यम या एजेंसी के लिए, एक जाँची-परखी साझा लाइब्रेरी सुरक्षा समीक्षकों और ऑडिटरों को दर्जनों के बजाय प्रमाणित करने के लिए एक ही कंपोनेंट भी देती है, जिससे हर समीक्षा की लागत कम हो जाती है।

  6. अगर कल रात कोई घटना हुई होती, तो क्या कोई भी ऑन-कॉल इंजीनियर उसे एक ही लॉग लाइन से ट्रेस कर सकता है, और क्या कोई ऑडिटर बाद में सिस्टम द्वारा दर्ज हर विफलता देख सकता है? एक रैप की गई, वर्गीकृत, अच्छी तरह लॉग की गई त्रुटि दस मिनट के निदान और आधी रात की खुदाई के बीच का अंतर है, और एक निगली गई त्रुटि एक भावी घटना है जिसे आपने अपने आप से छुपाया है। एक बड़ी टीम में, विफलताएँ कई सेवा हॉप पार करती हैं, इसलिए मूल्य संगत संदर्भ और सहसंबंध पहचानकर्ताओं (correlation identifiers) से आता है जो उन हॉप के आर-पार टिके रहते हैं, न कि किसी एक टीम की मेहनत से। प्रतिस्पर्धी तनाव लागत और शोर है: सब कुछ लॉग करें तो आप संकेत डुबो देते हैं और उसे संग्रहीत करने के लिए भुगतान करते हैं; बहुत कम लॉग करें तो आप यह पुनर्निर्मित नहीं कर सकते कि क्या हुआ। एक वास्तविक हालिया विफलता लाएँ और उसके रास्ते को शुरू से अंत तक चलें, हर उस हॉप को नोट करते हुए जहाँ संदर्भ छूट गया या कोई त्रुटि पकड़ी गई और त्याग दी गई। विनियमित और सरकारी सिस्टम में, इसे एक अनुपालन गुण के रूप में मानें, क्योंकि एक ऑडिट-अयोग्य विफलता, या एक ऐसा निर्णय जिसे आप वर्षों बाद समझा नहीं सकते, केवल एक परिचालन अंतर नहीं बल्कि एक कानूनी जोखिम है।

क्षेत्र-विशेष दृष्टिकोण

स्टार्टअप। मुट्ठी भर इंजीनियरों और बचाने के लिए कोई अतिरिक्त समय (runway) न होने पर, अपना त्रुटि-प्रबंधन बजट वहाँ खर्च करें जहाँ विफलता आपको कोई ग्राहक या आपका डेटा गंवा दे: हर आउटबाउंड कॉल पर टाइमआउट लगाएँ, अपने पैसा हिलाने वाले writes को आइडेम्पोटेंट बनाएँ, और उपेक्षित त्रुटियों के खिलाफ़ एक लिंट नियम जोड़ें। विस्तृत फ़्रेमवर्क छोड़ दें; मुख्य फ़ंक्शनों के लिए एक Result टाइप और गैर-महत्वपूर्ण डिपेंडेंसियों पर ग्रेसफ़ुल डिग्रेडेशन कुछ ही दिनों के काम में अधिकांश सुरक्षा दे देते हैं। डेवलपमेंट में फ़ेल-फ़ास्ट रहें ताकि बग ज़ोर से सामने आएँ, और तब तक हाथ से सर्किट ब्रेकर बनाने का विरोध करें जब तक आपके पास वास्तव में ऐसी कोई डिपेंडेंसी न हो जो इसकी हकदार हो।

छोटा व्यवसाय। स्टाफ़ में कोई रेज़िलिएंस विशेषज्ञ न होने और सीमित बजट के साथ, शुरू से पैटर्न बनाने के बजाय उस पर निर्भर रहें जो आपकी भाषा, फ़्रेमवर्क, और क्लाउड प्रदाता पहले से ही देते हैं: मैनेज्ड क्यू, प्रदाता-पक्ष रीट्राई, और लाइब्रेरी टाइमआउट अधिकांश टीमों की उम्मीद से ज़्यादा कवर करते हैं। निर्णय को खरीदने बनाम बनाने के रूप में फ़्रेम करें, और जहाँ भी कोई परिपक्व डिपेंडेंसी आपके लिए रीट्राई, बैकऑफ़, और आइडेम्पोटेंसी संभालती है, वहाँ खरीदें। अपना सीमित ध्यान उन एक या दो सीमाओं पर केंद्रित करें जहाँ कोई गलत या खोया हुआ लेन-देन वास्तव में नुकसान पहुँचाएगा, और सुनिश्चित करें कि वे सुरक्षित रूप से विफल हों और एक निशान छोड़ें।

उद्यम। कई टीमों में यह इनाम संगति है: एक साझा त्रुटि वर्गीकरण, टाइमआउट, रीट्राई, सर्किट ब्रेकर, और बल्कहेड के लिए एक सामान्य लाइब्रेरी, और एक लिंटर और समीक्षा चेकलिस्ट जो पाइपलाइन में इन्हें लागू करे। हर त्रुटि को सहसंबंध पहचानकर्ताओं के साथ एक एकीकृत ऑब्ज़र्वेबिलिटी प्लेटफ़ॉर्म में डालें ताकि एक विफलता को सेवा हॉप के आर-पार ट्रेस किया जा सके, और हर सीमा के लिए फ़ेल-फ़ास्ट बनाम फ़ेल-सेफ़ निर्णयों को मानकीकृत करें ताकि ऑडिट को स्थानीय आदतों के बिखराव के बजाय एक दस्तावेज़ीकृत, बचाव-योग्य पैटर्न मिले। साझा लाइब्रेरी को एक वास्तविक उत्पाद के रूप में गवर्न करें, क्योंकि वहाँ एक बार ठीक की गई खामी हर जगह ठीक हो जाती है।

सरकार। सटीकता, सुरक्षित विफलता, और एक टिकाऊ ऑडिट ट्रेल प्राथमिकताएँ नहीं बल्कि दायित्व हैं। पैसे या पात्रता को छूने वाले किसी भी उल्लंघित इनवेरिएंट पर फ़ेल-फ़ास्ट रहें, हर नागरिक-सम्मुख इनपुट को सीमा पर सत्यापित करें, और हर विफलता को इतने संदर्भ के साथ एक अपरिवर्तनीय लॉग में लिखें कि वर्षों बाद किसी निर्णय को समझाया और समीक्षित किया जा सके। खरीद (procurement) और लंबे सिस्टम जीवनचक्र का अर्थ है कि त्रुटि अनुबंधों को दस्तावेज़ीकृत होना चाहिए ताकि मूल लेखकों के जाने के बहुत बाद भी सरकारी कर्मचारी कोड को बनाए रख सकें, और किसी भी विक्रेता कंपोनेंट को अपने विफलता व्यवहार को एक अपारदर्शी इंटरफ़ेस के पीछे छुपाने के बजाय उजागर करना चाहिए।

उदाहरण

स्टार्टअप। चार लोगों की एक स्टार्टअप एक ऐसा ऐप जारी करती है जो एक थर्ड-पार्टी भुगतान प्रदाता और एक ईमेल सेवा को कॉल करता है। शुरुआत में वे एक भोली-भाली रीट्राई लूप जोड़ते हैं और तुरंत एक ग्राहक को दो बार चार्ज कर देते हैं जब एक टाइमआउट एक सफल चार्ज को छुपा देता है। यह सुधार सबक सिखाता है: वे हर write में आइडेम्पोटेंसी कुंजियाँ जोड़ते हैं, हर आउटबाउंड कॉल पर टाइमआउट लगाते हैं, और जिटर के साथ एक्सपोनेंशियल बैकऑफ़ पर स्विच करते हैं। वे मुख्य सेवा फ़ंक्शनों के लिए एक Result टाइप अपनाते हैं ताकि विफलता सिग्नेचर में दिखे, और एक लिंट नियम किसी भी उपेक्षित त्रुटि को चिह्नित करता है। जब ईमेल भेजना विफल होता है, तो चेकआउट बिक्री को रोकने के बजाय संदेश को कतार में डालकर शालीनता से डिग्रेड हो जाता है। यह अनुशासन कुछ दिनों की कीमत चुकाता है और उन्हें एक ऐसी श्रेणी की घटनाओं से बचाता है जिनकी कीमत रिफंड और भरोसे में कहीं ज़्यादा होती।

उद्यम। एक वैश्विक लॉजिस्टिक्स कंपनी सैकड़ों सेवाएँ चलाती है और उन सभी में त्रुटि प्रबंधन को मानकीकृत करती है। हर सेवा विफलताओं को एक साझा वर्गीकरण (वैलिडेशन, नॉट-फ़ाउंड, कॉन्फ़्लिक्ट, डिपेंडेंसी-अनअवेलेबल, इंटरनल) से मैप करती है, इसलिए कॉलर संदेशों को पार्स करने के बजाय श्रेणी पर शाखा लेते हैं। एक सामान्य लाइब्रेरी सर्किट ब्रेकर, बैकऑफ़ और जिटर के साथ सीमित रीट्राई, और बल्कहेडेड कनेक्शन पूल प्रदान करती है, इसलिए कोई भी इन पैटर्न को हाथ से गलत नहीं बनाता। हर त्रुटि को सहसंबंध संदर्भ के साथ लॉग किया जाता है जो अध्याय 9.2 के ऑब्ज़र्वेबिलिटी प्लेटफ़ॉर्म को फ़ीड करता है, इसलिए एक ऑन-कॉल इंजीनियर एक ही लाइन से सेवा हॉप के आर-पार किसी विफलता को ट्रेस कर सकता है। क्योंकि मानक समान है और पाइपलाइन में लागू किया गया है, इंजीनियर अपरिचित सेवाओं में आत्मविश्वास से आगे बढ़ते हैं और ऑडिटर देख सकते हैं कि हर विफलता दर्ज, वर्गीकृत, और ट्रेस-योग्य है।

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

व्यवसाय मामला: प्रेरणाएँ, ROI, और TCO

अनुशासित त्रुटि प्रबंधन का प्रतिफल कम घटनाओं, छोटी घटनाओं, और सस्ती घटनाओं के रूप में दिखता है। अधिकांश प्रोडक्शन आउटेज असाधारण नहीं होते; वे एक निगली गई एक्सेप्शन, एक गायब टाइमआउट, एक रीट्राई तूफ़ान, या एक ऐसी सीमा तक जाते हैं जिसने ऐसे डेटा पर भरोसा किया जिसे सत्यापित करना चाहिए था। इनमें से हर एक को यहाँ दिए गए पैटर्न से रोका जा सकता है, और हर रोकी गई घटना न केवल डाउनटाइम की प्रत्यक्ष लागत बचाती है बल्कि आपातकालीन प्रतिक्रिया, ग्राहक हानि (churn), और जाँच की चक्रवृद्धि लागतें भी बचाती है। क्योंकि एक रैप की गई, अच्छी तरह लॉग की गई त्रुटि को घंटों के बजाय मिनटों में निदान किया जा सकता है, औसत रिकवरी समय घटता है, और उसके साथ बदलाव-विफलता दर भी घटती है क्योंकि इंजीनियर त्रुटि पथ से डरना बंद कर देते हैं।

अपनाने की लागत मामूली और ज़्यादातर एक-बार की है। आप एक त्रुटि वर्गीकरण लिखते हैं, रीट्राई और सर्किट ब्रेकर के लिए एक साझा लाइब्रेरी प्रदान करते हैं ताकि टीमें इन्हें गलत तरीके से दोबारा न बनाएँ, उपेक्षित त्रुटियों के खिलाफ़ लिंट नियम जोड़ते हैं, और विफलता इंजेक्शन की आदत बनाते हैं। उपेक्षा की लागत चुपचाप चक्रवृद्धि होती है: निगली गई त्रुटियाँ भ्रष्ट डेटा में जमा हो जाती हैं जिसे सुलझाना महंगा है, और असंगत प्रबंधन हर इंटीग्रेशन और हर ऑडिट की लागत को कई गुना बढ़ा देता है। विनियमित और सरकारी परिवेश में, एक ऑडिट-अयोग्य विफलता केवल एक इंजीनियरिंग समस्या नहीं बल्कि एक अनुपालन और कानूनी जोखिम है। नेतृत्व के सामने मामला रखने के लिए, त्रुटि-प्रबंधन अनुशासन को उन मीट्रिक से जोड़ें जिन्हें वे पहले से देखते हैं: घटना आवृत्ति, औसत रिकवरी समय, बदलाव-विफलता दर, और ऑडिट निष्कर्ष।

एंटी-पैटर्न और नुकसान

  • साइलेंट स्वैलोइंग: खाली कैच ब्लॉक और उपेक्षित रिटर्न वैल्यू जो एक विफलता को एक विलंबित, अलग-थलग रहस्य में बदल देते हैं।
  • कैच-ऑल इरेज़र: एक व्यापक catch जो एक सामान्य संदेश लॉग करता है और मूल त्रुटि व उसके संदर्भ को त्याग देता है।
  • आइडेम्पोटेंस के बिना रीट्राई: टाइमआउट के बाद नॉन-आइडेम्पोटेंट writes को फिर से चलाना, जिससे दोहरा चार्ज या दोहराए गए रिकॉर्ड बनते हैं।
  • रीट्राई तूफ़ान: न बैकऑफ़, न जिटर, न कोई सीमा, जिससे क्लाइंट समन्वित होकर एक रिकवर हो रही डिपेंडेंसी को फिर से नीचे पटक देते हैं।
  • कोई टाइमआउट नहीं: असीमित रिमोट कॉल जो एक अटकी हुई डिपेंडेंसी को थ्रेड समाप्त करने और पूरी प्रक्रिया को जमा देने देते हैं।
  • कंट्रोल फ़्लो के रूप में एक्सेप्शन: “नहीं मिला” जैसे सामान्य परिणामों के लिए थ्रो और कैच करना, जो लॉजिक को छुपाता है और कोड को धीमा करता है।
  • डिफ़ेंसिव व्यामोह: हर पंक्ति पर जाँच जो लॉजिक को दबा देती है और असली विफलताओं को चुप डिफ़ॉल्ट में बदल देती है।
  • स्ट्रिंगली-टाइप्ड एरर: कॉलर त्रुटि संदेश टेक्स्ट को पार्स करते हैं क्योंकि शाखा लेने के लिए कोई स्थिर, वर्गीकृत वर्गीकरण नहीं है।
  • जहाँ फ़ेल-फ़ास्ट चाहिए था वहाँ फ़ेल-सेफ़: ऐसे सिस्टम में भ्रष्ट स्थिति पर आगे बढ़ना जहाँ गलत उत्तर किसी उत्तर न होने से बुरा है।

परिपक्वता मॉडल

  • स्तर 1, आरंभ (Initiate): त्रुटि प्रबंधन तदर्थ और प्रतिक्रियाशील है, हर डेवलपर द्वारा अलग तरह से तय किया जाता है। खाली कैच ब्लॉक और उपेक्षित रिटर्न आम हैं, रीट्राई भोली-भाली हैं, टाइमआउट गायब हैं, और विफलताएँ बिना किसी संगत लॉगिंग के भ्रष्ट डेटा या रहस्यमय दोषों के रूप में सामने आती हैं।
  • स्तर 2, विकास (Develop): टीमें बुनियादी प्रथाएँ अपनाती हैं, लेकिन असंगत रूप से। त्रुटियाँ कुछ संदर्भ के साथ लॉग की जाती हैं, स्पष्ट स्वैलोइंग को समीक्षा में हतोत्साहित किया जाता है, और टाइमआउट व सरल रीट्राई मौजूद हैं, फिर भी सेवाओं के बीच परंपराएँ भिन्न हैं, आइडेम्पोटेंस छिटपुट है, और त्रुटि पथ शायद ही कभी टेस्ट किया जाता है।
  • स्तर 3, मानकीकरण (Standardize): एक साझा त्रुटि वर्गीकरण और प्रबंधन परंपरा पूरे संगठन में दस्तावेज़ीकृत और लागू है। सीमा सत्यापन, बैकऑफ़ व जिटर के साथ आइडेम्पोटेंट रीट्राई, सर्किट ब्रेकर, बल्कहेड, और त्रुटि रैपिंग मानक हैं, सामान्य लाइब्रेरियों द्वारा प्रदान किए जाते हैं, और हर त्रुटि एक एकीकृत ऑब्ज़र्वेबिलिटी पाइपलाइन को फ़ीड करती है।
  • स्तर 4, प्रबंधन (Manage): त्रुटि-प्रबंधन व्यवहार को बेसलाइन के विरुद्ध मापा जाता है और डेटा से नियंत्रित किया जाता है। रीट्राई दरें, सर्किट-ब्रेकर ट्रिप, टाइमआउट गिनती, स्टैटिक विश्लेषण से मिले निगली गई त्रुटि के निष्कर्ष, औसत रिकवरी समय, और बदलाव-विफलता दर को हर सेवा के लिए ट्रैक किया जाता है; सर्किट-ब्रेकर सीमाएँ और टाइमआउट अनुमान लगाने के बजाय देखे गए लेटेंसी व विफलता डेटा से ट्यून किए जाते हैं; विफलता इंजेक्शन एक शेड्यूल पर चलता है; और टीमें प्रतिगमन पकड़ने और हर फ़ेल-फ़ास्ट या फ़ेल-सेफ़ चुनाव को प्रमाण के अधीन रखने के लिए इन मीट्रिक की समीक्षा करती हैं।
  • स्तर 5, समन्वय (Orchestrate): रेज़िलिएंस को डिलीवरी और जोखिम योजना के साथ एकीकृत किया जाता है और लगातार सुधारा जाता है। वर्गीकरण, साझा लाइब्रेरियाँ, और मानक हर घटना से विकसित होते हैं, कैओस और विफलता-इंजेक्शन प्रयोग नियमित हैं, और संगठन ट्रैफ़िक, डिपेंडेंसियों, और जोखिम परिदृश्य के बदलने के साथ टाइमआउट, ब्रेकर सीमाओं, डिग्रेडेशन रणनीतियों, और सीमा निर्णयों को अनुकूलित करता है।

चर्चा के लिए विचार

  1. आपके कोडबेस में फ़िलहाल कहाँ कोई त्रुटि निगली जाती है, और अगर आप गलत हों कि ऐसा नहीं होता तो आपको कैसे पता चलेगा?
  2. आपके कौन-से write ऑपरेशन आइडेम्पोटेंट हैं, और कौन-से दो बार निष्पादित हो जाएँगे अगर एक खोई हुई पावती के बाद रीट्राई फायर हो?
  3. क्या “उपयोगकर्ता नहीं मिला” एक एक्सेप्शन, एक त्रुटि मान, या एक सामान्य परिणाम होना चाहिए, और क्या आपकी टीम इसका जवाब लगातार एक जैसा देती है?
  4. सत्यापन कहाँ होता है इसके लिए आपका वास्तविक नियम क्या है, और क्या आप किसी ऐसी सीमा की ओर इशारा कर सकते हैं जो ऐसे डेटा पर भरोसा करती है जिस पर उसे नहीं करना चाहिए?
  5. आप किसी सर्किट ब्रेकर के लिए सीमा और कूलडाउन कैसे तय करते हैं, और आपको कैसे पता चलेगा कि वर्तमान सेटिंग गलत हैं?
  6. अगर कोई ऑडिटर पिछले महीने आपके सिस्टम में हुई हर विफलता देखना चाहे, तो क्या आप उसे वर्गीकृत और संदर्भ सहित प्रस्तुत कर सकते हैं?

मुख्य निष्कर्ष

  • दोषों, त्रुटियों, और विफलताओं में अंतर करें, और किसी आंतरिक त्रुटि के दृश्य विफलता बनने से पहले शृंखला तोड़ें।
  • हर सीमा के लिए जानबूझकर फ़ेल-फ़ास्ट या फ़ेल-सेफ़ चुनें, और हर फ़ंक्शन के त्रुटि-प्रबंधन अनुबंध को स्पष्ट बनाएँ।
  • ट्रस्ट सीमाओं पर सख्ती से सत्यापन करें और उनके भीतर भरोसा करें; ऐसी डिफ़ेंसिवनेस जो विफलताओं को छुपाती है, सुरक्षा नहीं बल्कि टालमटोल है।
  • आइडेम्पोटेंस, टाइमआउट, एक्सपोनेंशियल बैकऑफ़, और जिटर के साथ रीट्राई को सुरक्षित बनाएँ, और कोड में सर्किट ब्रेकर व ग्रेसफ़ुल डिग्रेडेशन जोड़ें।
  • त्रुटियों को संदर्भ के साथ रैप करें, उन्हें ऑब्ज़र्वेबिलिटी को फ़ीड करें, और उन्हें कभी न निगलें; हर त्रुटि को संभाला, आगे बढ़ाया, या लॉग करके सामने लाया जाना चाहिए।

संदर्भ और आगे पढ़ने के लिए

  • Michael T. Nygard, Release It! Design and Deploy Production-Ready Software
  • Andrew Hunt and David Thomas, The Pragmatic Programmer
  • Steve McConnell, Code Complete: A Practical Handbook of Software Construction
  • Betsy Beyer, Chris Jones, Jennifer Petoff, and Niall Richard Murphy (eds.), Site Reliability Engineering: How Google Runs Production Systems
  • Marc Brooker, “Timeouts, Retries, and Backoff with Jitter,” Amazon Builders’ Library
  • Martin Fowler, “CircuitBreaker,” martinfowler.com
  • Nassim Nicholas Taleb, Antifragile: Things That Gain from Disorder