अधिकांश Node.js ट्यूटोरियल एरर हैंडलिंग (error handling) को एक गौण विचार (afterthought) के रूप में देखते हैं। आप एक रूट हैंडलर को try/catch ब्लॉक में लपेटते हैं, स्टैक ट्रेस (stack trace) लॉग करते हैं, और 500 रिटर्न कर देते हैं। यह मानसिकता इसलिए बनी हुई है क्योंकि HTTP रिक्वेस्ट के दूसरे छोर पर एक वास्तविक व्यक्ति इंतज़ार कर रहा होता है। बैकग्राउंड जॉब्स (Background jobs) अलग होते हैं। एक क्यू सिस्टम (queue system) में, जवाब देने के लिए कोई अधीर क्लाइंट नहीं होता, और न ही कोई ऑटोमैटिक ब्राउज़र रिफ्रेश। वहाँ केवल एक वर्कर (worker), एक पेलोड (payload), और खामोशी से बढ़ता हुआ एक रिट्राई काउंटर (retry counter) होता है। जब चीजें गलत होती हैं, तो वे पहले धीरे-धीरे, फिर अचानक से गलत होती हैं। एक गलत तरीके से वर्गीकृत एरर (misclassified error) पूरे पाइपलाइन को रोक सकता है या सुबह तीन बजे किसी इंजीनियर को जगा सकता है।
यह अंतर सीधा है। रिक्वेस्ट-रिस्पॉन्स साइकिल जल्दी और शोर के साथ विफल होते हैं। क्यू फेलियर (queue failure) शांत होता है। डेटाबेस कनेक्शन टूटने से पहले एक वर्कर सैकड़ों जॉब्स को प्रोसेस कर सकता है। स्पष्ट हैंडलिंग नियमों के बिना, वर्कर तुरंत रिट्राई करता है, पहले से ही संघर्ष कर रहे डेटाबेस पर दबाव डालता है, और क्रैश हो जाता है। क्योंकि कोई सीधे वर्कर को नहीं देख रहा होता, इसलिए परेशानी का पहला संकेत अक्सर कैस्केडिंग बैकअप (cascading backup) या लॉग फाइलों से भरा हुआ डिस्क होता है। आपको केवल catch ब्लॉक्स से कहीं अधिक की आवश्यकता है। आपको एक ऐसी रणनीति की आवश्यकता है जो विभिन्न विफलताओं के साथ अलग-अलग व्यवहार करे और बाकी सिस्टम को एक खराब जॉब से बचाए।
विफलता के दो प्रकार
हर एरर को दो श्रेणियों (buckets) में विभाजित करके शुरुआत करें।
रिट्रिएबल एरर्स (Retryable errors) क्षणिक (transient) होते हैं। किसी थर्ड-पार्टी API का नेटवर्क टाइमआउट, 429 रेट-लिमिट रिस्पॉन्स, या किसी अस्थायी डेटाबेस रेप्लिका (database replica) का अपने प्राइमरी से पीछे रह जाना। ये दबाव के लक्षण हैं, बग्स नहीं। सिस्टम तीस सेकंड में खुद को ठीक कर सकता है। रिट्रिएबल जॉब्स एक और मौका पाने के लायक हैं, लेकिन केवल नियंत्रित परिस्थितियों में।
परमानेंट एरर्स (Permanent errors) गलतियाँ हैं। पेलोड में अमान्य JSON, एक गायब यूजर आईडी, या स्टोरेज में मौजूद न होने वाली कोई आवश्यक फ़ाइल। ये सौवें प्रयास में भी ठीक वैसे ही विफल होंगे जैसे वे पहले प्रयास में हुए थे। उन्हें रिट्राई करने से CPU साइकिल बर्बाद होती है, क्यू स्लॉट्स (queue slots) व्यर्थ जाते हैं, और एक टॉक्सिक बैक-प्रेशर (toxic back-pressure) पैदा होता है जो स्वस्थ जॉब्स में देरी करता है। परमानेंट फेलियर के लिए एकमात्र उपयोगी स्थान लॉग, अलर्ट, या डेड-लेटर क्यू (dead-letter queue) है। यह रिट्राई लूप का हिस्सा नहीं होना चाहिए।
एक डिसीजन इंजन बनाएँ
तुरंत वर्गीकृत करें। इस निर्णय को क्यू फ्रेमवर्क (queue framework) पर न छोड़ें। जैसे ही आप कोई एरर पकड़ते हैं, उसके भाग्य का निर्णय लें।
व्यवहार में, इसका अर्थ है कस्टम एरर क्लासेस (custom error classes) या रैपर फंक्शन बनाना जो एरर को ऊपर भेजने (bubbling up) से पहले विफलता का निरीक्षण करें। यदि कोई डेटाबेस ड्राइवर 'connection reset' थ्रो करता है, तो आपके हैंडलर को उसे 'retryable' के रूप में टैग करना चाहिए। यदि कोई पेलोड वैलिडेटर 'schema mismatch' थ्रो करता है, तो उसे 'permanent' के रूप में टैग करें। कई जॉब प्रोसेसर डिफ़ॉल्ट रूप से सब कुछ रिट्राई करते हैं, जो कि आपका सबसे महंगा निर्णय हो सकता है। परमानेंट जॉब्स को तुरंत रिजेक्ट करें। या तो उन्हें छोड़ दें या उन्हें डेड-लेटर क्यू (dead-letter queue) में भेज दें जहाँ वे मुख्य पाइपलाइन को खराब (poison) न कर सकें। यह एक आदत किसी भी इंफ्रास्ट्रक्चर बदलाव की तुलना में स्नोबॉल इफेक्ट (snowball effects) को अधिक विश्वसनीय रूप से रोकती है।
पीछे हटें, लेकिन समझदारी से
जब आप रिट्राई करें, तो इसे कभी भी तुरंत न करें। यदि डेटाबेस डाउन है, तो हर सेकंड उस पर हमला करने वाले वर्कर का एक समूह अंदर से 'denial-of-service' हमले जैसा दिखता है। एक्सपोनेंशियल बैकऑफ़ (exponential backoff) का उपयोग करें। एक मिनट प्रतीक्षा करें, फिर पाँच, फिर पंद्रह। अपस्ट्रीम सिस्टम (upstream system) को रिकवर होने के लिए जगह दें।
लेकिन केवल एक्सपोनेंशियल बैकऑफ़ ही पर्याप्त नहीं है। यदि कोई सर्विस रीस्टार्ट होने के कारण एक ही समय में एक हज़ार जॉब्स विफल हो जाते हैं, तो उनके रिट्राई शेड्यूल एक जैसे हो जाएंगे। जब वह सर्विस वापस ऑनलाइन आएगी, तो वे एक साथ उस पर हमला करेंगे, जिससे वह फिर से डाउन हो सकती है। इसमें 'jitter' जोड़ें: प्रत्येक देरी (delay) में एक छोटा रैंडम ऑफसेट (random offset)। रिट्राई को कुछ सेकंड के अंतराल पर फैलाने से सिंक्रोनाइज्ड स्टैम्पीड (synchronized stampedes) से बचा जा सकता है। गणित सरल है, लेकिन इससे मिलने वाली स्थिरता बहुत अधिक है।
सबूत सुरक्षित रखें
डेड-लेटर क्यू (dead-letter queue) आपका ऑडिट ट्रेल (audit trail) है, कचरे का डिब्बा नहीं। जब कोई जॉब अपना अंतिम रिट्राई पूरा कर ले, तो उसे बस डिलीट न करें। पूरे पेलोड को एरर कॉन्टेक्स्ट (error context) और रिट्राई हिस्ट्री के साथ DLQ में ले जाएँ।
यह सबूतों को सुरक्षित रखता है। एक इंसान जॉब का निरीक्षण कर सकता है, बग को ठीक कर सकता है, और यदि आवश्यक हो तो इसे मैन्युअल रूप से फिर से चला (replay) सकता है। इससे भी महत्वपूर्ण बात यह है कि अपने DLQ की गहराई (depth) की निगरानी करें। डेड-लेटर जॉब्स में अचानक उछाल अक्सर एक खराब डिप्लॉय (bad deploy), गलत स्कीमा परिवर्तन, या किसी बाहरी वेंडर द्वारा अपने अनुबंध को तोड़ने की शुरुआती चेतावनी होती है। DLQ की वृद्धि को एक 'leading indicator' के रूप में मानें, न कि 'trailing indicator' के रूप में। यदि आपका DLQ भर रहा है, तो इसका मतलब है कि अपस्ट्रीम में कुछ बदला है और बैकलॉग फैलने से पहले आपकी टीम को इसके बारे में पता होना चाहिए।
रिट्राई के लिए डिज़ाइन करें
Design every job as if it will run twice, because it might. A worker can fail halfway through processing, get rescheduled, and execute again. If your job charges a customer, sends an email, or increments an inventory count, a naive retry creates duplicates.
The fix is idempotency. Before you perform a side effect, check whether it already happened. Use a unique identifier from the job payload as an idempotency key. Store that key in a short-lived cache or a database table with a uniqueness constraint. If the key exists, skip the work and return success. This turns retries from a risk into a harmless no-op. It takes a few extra lines of code, but it saves you from explaining to finance why revenue doubled overnight.
Protect the Process
Unbounded promise rejections and stray exceptions can kill a Node.js process without warning. In a worker, that means dropped jobs and an orchestrator scrambling to restart the container.
Register global handlers for unhandledRejection and uncaughtException. Their job is not to rescue the application. It is to perform the minimum cleanup necessary, then exit. Let Docker, Kubernetes, or systemd restart the worker with a clean memory state. Limping along after a global handler fires invites memory leaks and corrupted state. A fast, clean death is safer than a slow zombie process that processes jobs incorrectly. Trust your orchestrator to bring you back; do not try to outsmart a corrupted runtime.
Respect the Signal
Workers get shut down during deployments, scaling events, and node rotations. If your process dies the instant it receives SIGTERM, you abort whatever job is currently in flight. That job may never finish, and its retry counter might not even have incremented yet.
Listen for SIGTERM and SIGINT. When a signal arrives, stop pulling new jobs from the queue. Finish the current job if you can. Set a hard timeout, perhaps thirty seconds, after which you exit regardless. This graceful shutdown respects the queue and avoids false failures. Your deployment pipeline should treat a worker that exits cleanly as healthy, while a crashed worker should trigger an alert.
The Real Takeaway
Reliable queue handling is not about catching every error. It is about making deliberate decisions for each failure mode. Retry the transient ones with patience. Bury the permanent ones quickly. Shield your workers from stampedes, protect your data with idempotency keys, and let dying processes exit cleanly. When every failure has a defined path, three in the morning becomes just another hour. Your pipeline keeps moving, and your team keeps sleeping.
