बहुतेक Node.js ट्युटोरियल्समध्ये एरर हँडलिंगकडे (error handling) केवळ एक गौण गोष्ट म्हणून पाहिले जाते. तुम्ही रूट हँडलरला try/catch ब्लॉक मध्ये गुंडाळता, स्टॅक ट्रेस (stack trace) लॉग करता आणि 500 एरर परत करता. ही मानसिकता यासाठी टिकून आहे कारण HTTP रिक्वेस्टच्या दुसऱ्या टोकाला एखादी व्यक्ती प्रत्यक्षात उत्तर मिळण्याची वाट पाहत असते. बॅकग्राउंड जॉब्स (Background jobs) वेगळे असतात. क्यू सिस्टममध्ये (queue system), उत्तर देण्यासाठी कोणताही अधीर क्लायंट नसतो किंवा ब्राउझर आपोआप रिफ्रेश होत नाही. तिथे फक्त एक वर्कर (worker), एक पेलोड (payload) आणि शांतपणे वाढत जाणारा 'रिट्राय काउंटर' (retry counter) असतो. जेव्हा गोष्टी चुकतात, तेव्हा त्या आधी हळूहळू आणि नंतर एकदम मोठ्या प्रमाणात चुकतात. चुकीच्या पद्धतीने वर्गीकृत केलेली एक एरर संपूर्ण पाइपलाइन थांबवू शकते किंवा पहाटे तीन वाजता एखाद्या इंजिनिअरला कामाला लावू शकते.
हा फरक साधा आहे. रिक्वेस्ट-रिस्पॉन्स सायकलमध्ये (Request-response cycles) चुका लवकर आणि स्पष्टपणे समजतात. पण क्यू फेल्युअर (queue failure) शांत असते. डेटाबेस कनेक्शन तुटण्यापूर्वी एखादा वर्कर शेकडो जॉब्स पूर्ण करण्याचा प्रयत्न करू शकतो. स्पष्ट हँडलिंग नियमांशिवाय, वर्कर लगेच पुन्हा प्रयत्न (retry) करतो, आधीच संघर्ष करत असलेल्या डेटाबेसवर ताण देतो आणि क्रॅश होतो. वर्करवर थेट कोणीही लक्ष ठेवत नसल्यामुळे, संकटाचे पहिले लक्षण अनेकदा कॅस्केडिंग बॅकअप (cascading backup) किंवा लॉग फाईल्सने भरलेला डिस्क असतो. तुम्हाला केवळ catch ब्लॉक्सपेक्षा जास्त काहीतरी हवे आहे. तुम्हाला अशा रणनीतीची गरज आहे जी वेगवेगळ्या प्रकारच्या फेल्युअरला (failures) वेगवेगळ्या प्रकारे हाताळेल आणि एका खराब जॉबमुळे संपूर्ण सिस्टम सुरक्षित ठेवेल.
दोन प्रकारचे फेल्युअर (Failures)
प्रत्येक एररला दोन गटांपैकी एका गटात विभागून सुरुवात करा.
'रिट्रायबल एरर्स' (Retryable errors) हे तात्पुरते असतात. एखाद्या थर्ड-पार्टी API कडे नेटवर्क टाइमआउट होणे, 429 रेट-लिमिट रिस्पॉन्स मिळणे किंवा डेटाबेस रेप्लिका (database replica) तात्पुरता मुख्य डेटाबेसपेक्षा मागे पडणे, ही याची उदाहरणे आहेत. हे ताणाचे (pressure) लक्षण आहे, बग (bug) नाही. सिस्टम तीस सेकंदात स्वतःहून पूर्वस्थितीत येऊ शकते. रिट्रायबल जॉब्सना पुन्हा संधी मिळायला हवी, पण ती केवळ नियंत्रित परिस्थितीतच.
'परमनंट एरर्स' (Permanent errors) या चुका आहेत. पेलोडमधील अवैध JSON, गहाळ युजर आयडी (user ID), किंवा स्टोरेजमध्ये नसलेली आवश्यक फाईल. हे एरर्स शंभरव्या प्रयत्नातही तसेच फेल होतील जसे ते पहिल्या प्रयत्नात झाले होते. त्यांना पुन्हा पुन्हा प्रयत्न केल्याने CPU सायकल वाया जातात, क्यू स्लॉट्स (queue slots) खर्च होतात आणि एक प्रकारचा 'टॉक्सिक बॅक-प्रेशर' (toxic back-pressure) निर्माण होतो ज्यामुळे इतर चांगल्या जॉब्सना विलंब होतो. परमनंट फेल्युअरसाठी लॉग, अलर्ट किंवा डेड-लेटर क्यू (dead-letter queue) हेच एकमेव उपयुक्त ठिकाण आहे. ते रिट्राय लूपमध्ये (retry loop) नसावे.
डिसीजन इंजिन (Decision Engine) तयार करा
त्वरित वर्गीकरण करा. हा निर्णय क्यू फ्रेमवर्कवर (queue framework) सोडू नका. ज्या क्षणी तुम्हाला एरर सापडेल, त्याच क्षणी त्याचे भविष्य ठरवा.
व्यवहारात याचा अर्थ असा आहे की, एरर वर पाठवण्यापूर्वी (bubbling up) त्यातील त्रुटी तपासण्यासाठी कस्टम एरर क्लासेस (custom error classes) किंवा रॅपर फंक्शन्स (wrapper functions) तयार करणे. जर डेटाबेस ड्रायव्हरने 'कनेक्शन रिसेट' एरर दिला, तर तुमच्या हँडलरने त्याला 'रिट्रायबल' म्हणून टॅग केले पाहिजे. जर पेलोड व्हॅलिडेटरने 'स्कीमा मिसमॅच' (schema mismatch) एरर दिला, तर त्याला 'परमनंट' म्हणून टॅग करा. अनेक जॉब प्रोसेसर्स सर्व काही रिट्राय करण्याचा प्रयत्न करतात, जो सर्वात महागडा निर्णय ठरू शकतो. परमनंट जॉब्स त्वरित नाकारून टाका. एकतर ते काढून टाका किंवा त्यांना डेड-लेटर क्यू (dead-letter queue) कडे वळवा, जेणेकरून ते मुख्य पाइपलाइन खराब करू शकणार नाहीत. ही एक सवय कोणत्याही इन्फ्रास्ट्रक्चर बदलापेक्षा अधिक विश्वासार्हतेने 'स्नोबॉल इफेक्ट' (snowball effects) रोखू शकते.
बॅक ऑफ (Back Off), पण अधिक हुशारीने
जेव्हा तुम्ही रिट्राय करता, तेव्हा ते कधीही लगेच करू नका. जर डेटाबेस डाऊन असेल, तर प्रत्येक सेकंदाला वर्करचा ताण देणे हे आतून होणाऱ्या 'डिनायल-ऑफ-सर्व्हिस' (denial-of-service) हल्ल्यासारखे वाटते. 'एक्स्पोनेंशियल बॅकऑफ' (exponential backoff) वापरा. एक मिनिट थांबा, मग पाच, मग पंधरा. अपस्ट्रीम सिस्टमला (upstream system) सावरण्यासाठी वेळ द्या.
पण केवळ एक्स्पोनेंशियल बॅकऑफ पुरेसा नाही. जर एखादी सर्विस रीस्टार्ट झाल्यामुळे हजारो जॉब्स एकाच वेळी फेल झाले, तर त्यांचे रिट्राय शेड्युल (retry schedules) एकमेकांशी जुळतील. ती सर्विस पुन्हा ऑनलाइन आल्यावर ते सर्व एकाच वेळी त्यावर हल्ला करतील, ज्यामुळे ती पुन्हा डाऊन होऊ शकते. 'जिटर' (jitter) जोडा: प्रत्येक विलंबासाठी एक छोटा रँडम ऑफसेट (random offset) द्या. रिट्राय काही सेकंदांच्या अंतराने विखुरल्यामुळे 'सिंक्रोनाइझ्ड स्टॅम्पीड्स' (synchronized stampedes) टाळता येतात. गणित सोपे आहे, पण यामुळे मिळणारी स्थिरता प्रचंड आहे.
पुरावा जतन करा (Preserve the Evidence)
डेड-लेटर क्यू (dead-letter queue) हा तुमचा ऑडिट ट्रेल (audit trail) आहे, कचरा पेटी नाही. जेव्हा एखादा जॉब त्याच्या शेवटच्या रिट्रायनंतरही फेल होतो, तेव्हा तो फक्त डिलीट करू नका. संपूर्ण पेलोड, एरर कॉन्टेक्स्ट (error context) आणि रिट्राय हिस्ट्रीसह (retry history) तो DLQ मध्ये हलवा.
यामुळे पुरावा जतन होतो. एखादी व्यक्ती तो जॉब तपासू शकते, बग फिक्स करू शकते आणि आवश्यक असल्यास तो मॅन्युअली पुन्हा प्ले (replay) करू शकते. महत्त्वाचे म्हणजे, तुमच्या DLQ च्या खोलीवर (depth) लक्ष ठेवा. डेड-लेटर झालेल्या जॉब्समध्ये अचानक झालेली वाढ ही अनेकदा चुकीच्या डिप्लॉयमेंटचे (bad deploy), चुकीच्या स्कीमा बदलाचे किंवा बाह्य व्हेंडरने (external vendor) करार मोडल्याचे पहिले लक्षण असते. DLQ मधील वाढ ही 'लीडिंग इंडिकेटर' (leading indicator) म्हणून पहा, 'ट्रेलिंग इंडिकेटर' म्हणून नाही. जर तुमचा DLQ भरत असेल, तर अपस्ट्रीममध्ये काहीतरी बदलले आहे आणि बॅकलॉग वाढण्यापूर्वी तुमच्या टीमला ते माहित असणे आवश्यक आहे.
रिट्रायसाठी डिझाइन करा (Design for the Retry)
प्रत्येक जॉब असा डिझाइन करा की तो दोनदा रन होईल, कारण तो तसा होऊ शकतो. एखादा वर्कर प्रोसेसिंगच्या मध्येच फेल होऊ शकतो, पुन्हा शेड्यूल होऊ शकतो आणि पुन्हा कार्यान्वित होऊ शकतो. जर तुमचा जॉब ग्राहकाकडून पैसे आकारत असेल, ईमेल पाठवत असेल किंवा इन्व्हेंटरी काउंट वाढवत असेल, तर साधी 'retry' प्रक्रिया डुप्लिकेट्स तयार करू शकते.
याचे निराकरण म्हणजे 'idempotency' आहे. कोणताही साइड इफेक्ट करण्यापूर्वी, तो आधीच झाला आहे का ते तपासा. जॉब पेलोडमधील युनिक आयडेंटिफायरचा वापर 'idempotency key' म्हणून करा. ती की (key) अल्पकालीन कॅशेमध्ये किंवा युनिकनेस कन्स्ट्रेंट असलेल्या डेटाबेस टेबलमध्ये साठवा. जर ती की आधीच अस्तित्वात असेल, तर काम सोडून द्या आणि 'success' रिझल्ट द्या. यामुळे 'retries' हे जोखमीचे काम न राहता एक सुरक्षित 'no-op' बनते. यासाठी कोडच्या काही अतिरिक्त ओळी लागतील, पण यामुळे रातोरात महसूल दुप्पट का झाला, हे फायनान्स विभागाला स्पष्ट करण्याची तुमची कसरत वाचते.
प्रक्रियेचे संरक्षण करा
अनबाउंड प्रॉमिस रिजेक्शन (unbounded promise rejections) आणि विखुरलेले एक्सेप्शन्स (stray exceptions) कोणत्याही पूर्वसूचनेशिवाय Node.js प्रोसेस बंद करू शकतात. वर्करच्या बाबतीत, याचा अर्थ जॉब्स गमावणे आणि ऑर्केस्ट्रेटरला कंटेनर पुन्हा सुरू करण्यासाठी धडपड करणे असा होतो.
unhandledRejection आणि uncaughtException साठी ग्लोबल हँडलर्स रजिस्टर करा. त्यांचे काम ॲप्लिकेशनला वाचवणे नाही, तर आवश्यक ते किमान क्लीनअप करणे आणि त्यानंतर बाहेर पडणे (exit) हे आहे. Docker, Kubernetes किंवा systemd ला स्वच्छ मेमरी स्टेटसह वर्कर पुन्हा सुरू करू द्या. ग्लोबल हँडलर कार्यान्वित झाल्यानंतरही अडखळत काम सुरू ठेवल्याने मेमरी लीक्स आणि करप्टेड स्टेटचा धोका वाढतो. चुकीच्या पद्धतीने जॉब्स प्रोसेस करणाऱ्या संथ 'झोम्बी प्रोसेस'पेक्षा, वेगाने आणि स्वच्छरित्या बंद होणे अधिक सुरक्षित आहे. तुम्हाला पुन्हा सुरू करण्यासाठी तुमच्या ऑर्केस्ट्रेटरवर विश्वास ठेवा; करप्टेड रनटाइमला चकवा देण्याचा प्रयत्न करू नका.
सिग्नलचा आदर करा
डिप्लॉयमेंट, स्केलिंग इव्हेंट्स आणि नोड रोटेशन्स दरम्यान वर्कर्स बंद केले जातात. जर तुमच्या प्रोसेसला SIGTERM मिळताच ती लगेच बंद झाली, तर प्रगतीपथावर असलेला (in flight) जॉब तुम्ही अर्धवट सोडता. तो जॉब कदाचित कधीच पूर्ण होणार नाही आणि त्याचा 'retry counter' देखील कदाचित अजून वाढलेला नसेल.
SIGTERM आणि SIGINT साठी लक्ष ठेवा. जेव्हा सिग्नल येतो, तेव्हा क्यूमधून (queue) नवीन जॉब्स घेणे थांबवा. शक्य असल्यास चालू असलेला जॉब पूर्ण करा. एक निश्चित 'hard timeout' सेट करा, कदाचित तीस सेकंद, ज्यानंतर तुम्ही कोणत्याही परिस्थितीत बाहेर पडाल. ही 'graceful shutdown' प्रक्रिया क्यूचा आदर करते आणि चुकीचे फेल्युअर टाळते. तुमच्या डिप्लॉयमेंट पाइपलाइनने स्वच्छरित्या बाहेर पडणाऱ्या वर्करला 'healthy' मानले पाहिजे, तर क्रॅश झालेल्या वर्करमुळे अलर्ट ट्रिगर झाला पाहिजे.
मुख्य निष्कर्ष
विश्वासार्ह क्यू हँडलिंग म्हणजे प्रत्येक एरर पकडणे नव्हे. तर प्रत्येक 'failure mode' साठी विचारपूर्वक निर्णय घेणे होय. तात्पुरत्या (transient) त्रुटी संयमाने पुन्हा प्रयत्न (retry) करा. कायमस्वरूपी त्रुटींना त्वरित हाताळा. तुमच्या वर्कर्सना अचानक येणाऱ्या प्रचंड लोडपासून (stampedes) वाचवा, आयडेम्पोटन्सी की वापरून तुमचा डेटा सुरक्षित ठेवा आणि बंद होणाऱ्या प्रोसेसना स्वच्छरित्या बाहेर पडू द्या. जेव्हा प्रत्येक फेल्युअरसाठी एक निश्चित मार्ग असतो, तेव्हा पहाटेचे तीन वाजणे हे केवळ आणखी एक सामान्य तास ठरते. तुमची पाइपलाइन सुरू राहते आणि तुमची टीम शांतपणे झोपू शकते.
