जेव्हा तुम्ही Node.js वापरून काही तयार करत असता, तेव्हा सुरुवातीला एरर हँडलिंग (error handling) खूप सोपे वाटते. तुम्ही एका राऊटला try-catch मध्ये गुंडाळता, 500 स्टेटस कोड पाठवता आणि क्लायंट पुढे काय करायचे हे ठरवतो. हे मॉडेल HTTP साठी ठीक चालते, पण जेव्हा तुम्ही बॅकग्राउंड जॉब्सकडे (background jobs) वळता, तेव्हा ते कोलमडते. क्यू सिस्टममध्ये (queue system), वाट पाहणारा कोणताही क्लायंट नसतो. तिथे फक्त एक वर्कर (worker), एक पेलोड (payload) आणि Redis, RabbitMQ किंवा SQS मध्ये कुठेतरी वाढत जाणारा 'रिट्राय काउंटर' (retry counter) असतो. जर तुम्ही वेब रिक्वेस्ट फेल झाल्याप्रमाणेच या फेल्युअरला हाताळले, तर तुम्ही फक्त एक ट्रान्झॅक्शन गमावणार नाही, तर तुमची संपूर्ण पाइपलाइन थांबेल, कॉम्प्युट रिसोर्सेस वाया जातील किंवा एकाच 'पॉइझन्ड मेसेज'मुळे (poisoned message) तुमचे वर्कर्स वारंवार क्रॅश होतील.
बॅकग्राउंड जॉब्समध्ये HTTP मानसिकता अपयशी ठरते
रिक्वेस्ट-रिस्पॉन्स सायकलमध्ये, फीडबॅक लूप त्वरित मिळतो. वापरकर्ता बटण क्लिक करतो, सर्व्हर एरर देतो आणि वापरकर्त्याला फेल्युअर स्क्रीन दिसते. क्लिनअप (Cleanup) सोपे असते. क्यू वर्कर एकाकी (isolation) स्थितीत काम करतो. तो एक जॉब घेतो, काही सेकंद किंवा मिनिटे त्यावर काम करतो आणि नंतर यशाची पोच (acknowledgment) देतो. जर मध्येच काही चुकले, तर क्यूला त्याचे कारण माहित नसते. त्याला फक्त एवढेच माहित असते की पोच (acknowledgment) मिळाली नाही. तुमच्या कॉन्फिगरेशननुसार, ते पुन्हा प्रयत्न (retry) करेल, कदाचित कायमस्वरूपी. एक चुकीचा पेलोड (malformed payload) शेकडो वेळा वर्कर्समध्ये फिरू शकतो, ज्यामुळे CPU वाया जातो आणि खरोखर प्रक्रियेची गरज असलेल्या वैध जॉब्सच्या मागे तो लपून बसतो.
दोन प्रकारचे फेल्युअर (Failures)
लवचिक (resilient) क्यूजचा पहिला नियम म्हणजे प्रत्येक एररला सारख्याच पद्धतीने हाताळणे थांबवणे. एरर येताच तुम्हाला त्याचे दोन गटांत वर्गीकरण करणे आवश्यक आहे.
'रिट्रायबल फेल्युअर' (Retryable failures) हे तात्पुरते असतात. नेटवर्क टाइमआउट, थर्ड-पार्टी API कडून येणारे रेट लिमिट्स (rate limits), किंवा डेटाबेस कनेक्शन रिसेट होणे (कारण पूल तात्पुरता संपला होता) यांसारख्या गोष्टींचा विचार करा. ही दबावाखाली असलेल्या कार्यरत सिस्टमची लक्षणे आहेत. दोन मिनिटांनंतरच्या पुढच्या प्रयत्नात ते यशस्वी होऊ शकतात.
'परमनंट फेल्युअर' (Permanent failures) हे 'पॉइझन पिल्स' (poison pills) सारखे असतात. यामध्ये चुकीचे पेलोड, स्कीमा व्हॅलिडेशन एरर्स (schema validation errors), किंवा अपस्ट्रीम सर्व्हिसने आपला कॉन्ट्रॅक्ट बदलल्यामुळे एखादे आवश्यक फील्ड गहाळ असणे यांचा समावेश होतो. यांचा पुन्हा प्रयत्न करणे म्हणजे निव्वळ वेळ वाया घालवणे आहे. शंभरव्या प्रयत्नातही ते अगदी तसेच फेल होतील.
जर तुमचा catch block या दोघांमधील फरक ओळखू शकत नसेल, तर तुमची क्यू अंधारात काम करत आहे.
पॅटर्न १: Catch Block मध्ये एरर्सचे वर्गीकरण करा
तुमच्या वर्करचा catch block हा फाईलमधील सर्वात विचारपूर्वक लिहिलेला कोड असावा. जेव्हा एखादा एरर समोर येतो, तेव्हा लगेच त्याची तपासणी करा. एरर कोड ECONNRESET आहे की टाइमआउट? तर त्याला रिट्रायसाठी (retry) क्यूमध्ये टाका. तो SyntaxError आहे, Joi व्हॅलिडेशन रिजेक्शन आहे की फॉरेन की कन्स्ट्रेंट (foreign key constraint) गहाळ आहे? तर त्याला थेट डेड-लेटर क्यू (dead-letter queue) किंवा DLQ मध्ये हलवा आणि तो तुमच्या रिट्राय लिमिटमध्ये मोजू नका.
BullMQ आणि Bee Queue सह बहुतेक Node.js क्यू लायब्ररीज तुम्हाला कस्टम बॅकऑफ स्ट्रॅटेजीज (backoff strategies) आणि एरर हुक्स (error hooks) परिभाषित करण्याची परवानगी देतात. त्यांचा वापर करा. परमनंट एररने बाय डिफॉल्ट कधीही 'स्लीप' होऊन तीन वेळा रिट्राय करू नये. तो मुख्य क्यूमधून काढून टाकला पाहिजे जेणेकरून तुमचे इतर जॉब्स सुरळीत चालू शकतील. DLQ मूळ पेलोड आणि एरर कॉन्टेक्स्ट जतन करते, ज्यामुळे तुम्ही बग फिक्स केल्यानंतर किंवा स्कीमा दुरुस्त केल्यानंतर तो जॉब नंतर पुन्हा रन (replay) करू शकता.
पॅटर्न २: Jitter सह Exponential Backoff
त्वरित रिट्राय करणे हे आक्रमक आहे. जर एखादा डाउनस्ट्रीम डेटाबेस आधीच लोडमुळे त्रस्त असेल, तर पन्नास वर्कर्सकडून दर दोन सेकंदांनी त्यावर पुन्हा हल्ला केल्यास तो पूर्णपणे कोलमडून जाईल. तुम्हाला थोडा वेळ थांबून सिस्टमला सावरण्यासाठी जागा द्यावी लागेल.
Exponential backoff वापरा. पहिल्या फेल्युअरवर एक सेकंद थांबा. दुसऱ्यावर दोन सेकंद. मग चार, मग आठ, आणि पाच मिनिटांच्या वाजवी मर्यादेपर्यंत. पण केवळ टायमिंग पुरेसे नाही. जर प्रत्येक फेल जॉब अगदी तोच इंटरव्हल वापरत असेल, तर बॅकऑफ संपल्यावर ते सर्व एकत्र येतील (collide). ही सिंक्रोनाइझ्ड लाट, जिला कधीकधी 'थंडरिंग हर्ड' (thundering herd) म्हणतात, ती सावरत असलेल्या सर्व्हिसवर ताण आणू शकते.
Jitter जोडा. तुमचा मोजलेला विलंब (delay) घ्या आणि त्यात १० ते २० टक्के रँडम टक्केवारी जोडा. चार सेकंद म्हणजे ४.२ किंवा ४.७ सेकंद होईल. ही साधी रँडमनेस (randomness) रिट्रायचा स्पाइक पसरवून टाकते आणि तुमच्या इन्फ्रास्ट्रक्चरवर लाटांच्या स्वरूपात येणारे वार थांबवते.
पॅटर्न ३: Idempotency साठी डिझाइन करा
येथे क्यू इन्फ्रास्ट्रक्चर आणि बिझनेस लॉजिक यांचा संगम होतो. समजा एखादा जॉब पेमेंट प्रोव्हायडरद्वारे ग्राहकाकडून पैसे आकारतो. वर्कर यशस्वीरित्या चार्ज पोस्ट करतो, परंतु तुमच्या डेटाबेसमध्ये यश नोंदवण्यापूर्वी किंवा जॉबची पोच (acknowledge) देण्यापूर्वी कनेक्शन तुटते. क्यूला हे फेल्युअर वाटते. ते पुन्हा प्रयत्न करते. परिणामी ग्राहकाकडून दोनदा पैसे आकारले जातात.
Node.js मध्ये, प्रत्येक side effect ला idempotent बनवून हे टाळता येते. Job ID किंवा व्यवसायाशी संबंधित विशिष्ट आयडेंटिफायरपासून एक idempotency key तयार करा. पेमेंट (charge) करणे, ईमेल पाठवणे किंवा इन्व्हेंटरी (inventory) समायोजित करण्यापूर्वी, ते काम आधीच झाले आहे का ते तपासा. ती key तुमच्या डेटाबेसमध्ये आणि idempotency key स्वीकारणाऱ्या कोणत्याही third-party APIs मध्ये पाठवा. तुमच्या jobs ची रचना अशी करा की एकाच payload ला दहा वेळा चालवल्यास ते एकदा चालवल्यासारखेच निकाल देईल. ही एक सवय आर्थिक आणि डेटा-इंटिग्रिटी (data-integrity) संबंधित बग्सची संपूर्ण श्रेणी काढून टाकते.
पॅटर्न ४: तुमच्या Dead-Letter Queue ला डॅशबोर्डप्रमाणे हाताळा
DLQ हे असे स्मशान नाही जिथे खराब jobs विसरण्यासाठी पाठवले जातात. हे एक operational tool आहे आणि ते तुमच्या सर्वात जास्त लक्ष देण्यायोग्य भागांपैकी एक असावे.
जेव्हा DLQ ची depth वाढते, तेव्हा अलर्ट येतील अशी व्यवस्था करा. DLQ मध्ये एक संदेश असणे देखील याचा अर्थ असू शकतो की तुमचे validation logic बिघडले आहे, upstream schema बदलला आहे, किंवा downstream service असा कचरा (garbage) पाठवत आहे जो तुम्हाला आता ओळखता येत नाही. ग्राहक तक्रार करण्यास सुरुवात करण्यापूर्वी तुम्हाला हेच संकेत पकडायचे असतात. एक डॅशबोर्ड तयार करा जो तुम्हाला raw payload, stack trace आणि timestamp तपासण्याची परवानगी देईल. एक runbook तयार ठेवा: त्रुटी तपासा, कोड पॅच करा आणि नंतर योग्य क्रमाने संदेश पुन्हा पाठवा (replay). जर तुमच्या queue implementation मध्ये सुविधा असेल, तर केवळ संख्येवरच नाही तर वाढीच्या दरावरही (growth rate) अलर्ट सेट करा, कारण एक चुकीचा deploy काही मिनिटांतच DLQ मध्ये मेसेजचा पूर आणू शकतो.
पॅटर्न ५: शंका असल्यास, प्रोसेस क्रॅश करा
Node.js एका V8 isolate च्या आत single event loop वर चालते. एक unhandled promise rejection किंवा...
