আপনি যখন Node.js দিয়ে কাজ করছেন, তখন শুরুতে এরর হ্যান্ডলিং (error handling) খুব সহজ মনে হতে পারে। আপনি একটি রুটকে try-catch দিয়ে মুড়িয়ে দেন, একটি 500 স্ট্যাটাস কোড পাঠান, এবং ক্লায়েন্ট পরবর্তী পদক্ষেপ কী নেবে তা ঠিক করে। এই মডেলটি HTTP-এর জন্য ঠিক আছে, কিন্তু ব্যাকগ্রাউন্ড জব (background jobs)-এর ক্ষেত্রে এটি কাজ করে না। একটি কিউ সিস্টেমে (queue system) অপেক্ষা করার মতো কোনো ক্লায়েন্ট থাকে না। সেখানে থাকে শুধু একটি ওয়ার্কার (worker), একটি পেলোড (payload), এবং Redis, RabbitMQ বা SQS-এর কোথাও ক্রমবর্ধমান একটি রিট্রাই কাউন্টার (retry counter)। আপনি যদি ওয়েব রিকোয়েস্ট ব্যর্থ হওয়ার মতো একইভাবে এই ব্যর্থতাগুলো হ্যান্ডেল করেন, তবে আপনি শুধু একটি ট্রানজ্যাকশন হারাবেন না; বরং আপনার পুরো পাইপলাইন স্থবির করে দেবেন, কম্পিউট রিসোর্স নষ্ট করবেন অথবা একই 'পয়জন্ড মেসেজ' (poisoned message)-এর কারণে বারবার আপনার ওয়ার্কার ক্র্যাশ করাবেন।
ব্যাকগ্রাউন্ড জবে HTTP মাইন্ডসেট কাজ করে না
রিকোয়েস্ট-রেসপন্স সাইকেলে ফিডব্যাক লুপ তাৎক্ষণিক হয়। একজন ব্যবহারকারী একটি বাটনে ক্লিক করেন, সার্ভার একটি এরর থ্রো করে এবং ব্যবহারকারী একটি ফেইলর স্ক্রিন দেখেন। এখানে ক্লিনআপ করা সহজ। কিন্তু একটি কিউ ওয়ার্কার বিচ্ছিন্নভাবে কাজ করে। এটি একটি জব টানে, কয়েক সেকেন্ড বা মিনিট ধরে কাজ করে এবং তারপর সফলতার স্বীকৃতি (acknowledgment) প্রদান করে। মাঝপথে কিছু ভুল হলে কিউ জানে না কেন হয়েছে। এটি শুধু জানে যে স্বীকৃতিটি পৌঁছায়নি। আপনার কনফিগারেশনের ওপর ভিত্তি করে, এটি বারবার রিট্রাই করতে পারে, এমনকি চিরকাল। একটি মাত্র ভুল ফরম্যাটের পেলোড শত শত বার ওয়ার্কারদের মধ্যে ঘুরপাক খেতে পারে, যা CPU নষ্ট করে এবং প্রকৃতপক্ষে প্রসেসিং প্রয়োজন এমন বৈধ জবগুলোর আড়ালে লুকিয়ে থাকে।
দুই ধরনের ব্যর্থতা
সহনশীল (resilient) কিউ তৈরির প্রথম নিয়ম হলো প্রতিটি এররকে একইভাবে দেখা বন্ধ করা। এরর ঘটার সাথে সাথে আপনাকে সেগুলোকে দুটি গ্রুপে ভাগ করতে হবে।
Retryable failures বা পুনরায় চেষ্টাযোগ্য ব্যর্থতাগুলো হলো ট্রানজিয়েন্ট (transient) বা সাময়িক। যেমন নেটওয়ার্ক টাইমআউট, থার্ড-পার্টি API থেকে রেট লিমিট, অথবা ডেটাবেস কানেকশন রিসেট হওয়া কারণ কানেকশন পুল সাময়িকভাবে শেষ হয়ে গিয়েছিল। এগুলো একটি জীবন্ত সিস্টেমের চাপের লক্ষণ মাত্র। দুই মিনিট পর পরবর্তী প্রচেষ্টায় এগুলো সফল হতে পারে।
Permanent failures বা স্থায়ী ব্যর্থতাগুলো হলো 'পয়জন পিল' (poison pills)। এর মধ্যে রয়েছে ভুল ফরম্যাটের পেলোড, স্কিমা ভ্যালিডেশন এরর, অথবা কোনো প্রয়োজনীয় ফিল্ড অনুপস্থিত থাকা কারণ আপস্ট্রিম সার্ভিস তার কন্ট্রাক্ট পরিবর্তন করেছে। এগুলো রিট্রাই করা মানেই শুধু সময়ের অপচয়। শততম প্রচেষ্টাতেও এগুলো একইভাবে ব্যর্থ হবে।
আপনার catch ব্লক যদি এই দুটির মধ্যে পার্থক্য করতে না পারে, তবে আপনার কিউ অন্ধভাবে কাজ করছে।
প্যাটার্ন ১: Catch ব্লকে এরর ক্লাসিফাই করা
আপনার ওয়ার্কারের catch ব্লকটি ফাইলের সবচেয়ে সুচিন্তিত কোড হওয়া উচিত। যখন কোনো এরর দেখা দেয়, সাথে সাথে সেটি পরীক্ষা করুন। এরর কোড কি ECONNRESET বা একটি টাইমআউট? তবে এটিকে রিট্রাই করার জন্য কিউতে রাখুন। এটি কি একটি SyntaxError, Joi ভ্যালিডেশন রিজেকশন, নাকি কোনো ফরেন কি কনস্ট্রেইন্ট (foreign key constraint) মিসিং? এটিকে সরাসরি একটি dead-letter queue বা DLQ-তে পাঠিয়ে দিন এবং এটিকে আপনার রিট্রাই লিমিটের মধ্যে গণনা করবেন না।
BullMQ এবং Bee Queue সহ বেশিরভাগ Node.js কিউ লাইব্রেরি আপনাকে কাস্টম ব্যাকঅফ স্ট্র্যাটেজি (backoff strategies) এবং এরর হুক (error hooks) সংজ্ঞায়িত করার সুযোগ দেয়। সেগুলো ব্যবহার করুন। একটি স্থায়ী এরর ডিফল্টভাবে কখনোই তিনবার ঘুমানো বা রিট্রাই করা উচিত নয়। এটিকে মূল কিউ থেকে সরিয়ে নেওয়া উচিত যাতে আপনার বাকি জবগুলো চলতে পারে। DLQ সঠিক পেলোড এবং এরর কনটেক্সট সংরক্ষণ করে, যা আপনাকে বাগ ফিক্স বা স্কিমা ঠিক করার পরে পরে জবটি পুনরায় চালানোর সুযোগ দেয়।
প্যাটার্ন ২: Jitter সহ Exponential Backoff
তাৎক্ষণিকভাবে রিট্রাই করা বেশ আক্রমণাত্মক। যদি একটি ডাউনস্ট্রিম ডেটাবেস ইতিমধ্যে লোডের চাপে হিমশিম খাচ্ছে, তবে পঞ্চাশটি ওয়ার্কার থেকে প্রতি দুই সেকেন্ড অন্তর সেখানে হিট করা মানে সেটিকে পুরোপুরি ধ্বংস করে দেওয়া। আপনাকে কিছুটা সময় দিতে হবে যাতে সিস্টেমটি পুনরুদ্ধার করতে পারে।
Exponential backoff ব্যবহার করুন। প্রথম ব্যর্থতায় এক সেকেন্ড অপেক্ষা করুন। দ্বিতীয়বার দুই সেকেন্ড, তারপর চার, তারপর আট—সর্বোচ্চ পাঁচ মিনিটের মতো একটি যুক্তিসঙ্গত সীমা পর্যন্ত। কিন্তু শুধু টাইমিং যথেষ্ট নয়। যদি প্রতিটি ব্যর্থ জব ঠিক একই ব্যবধানে রিট্রাই করে, তবে ব্যাকঅফ শেষ হওয়ার সাথে সাথে সবগুলো একসাথে সংঘর্ষ ঘটাবে। এই সিনক্রোনাইজড ঢেউকে, যা কখনও কখনও 'থান্ডারিং হার্ড' (thundering herd) বলা হয়, তা একটি পুনরুদ্ধার হতে থাকা সার্ভিসকে বিপর্যস্ত করে দিতে পারে।
Jitter যোগ করুন। আপনার গণনাকৃত বিলম্বের (delay) সাথে একটি র্যান্ডম শতাংশ (হয়তো ১০ থেকে ২০ শতাংশ) যোগ করুন। ফলে ৪ সেকেন্ড হয়ে যাবে ৪.২ বা ৪.৭ সেকেন্ড। এই সামান্য র্যান্ডমনেস রিট্রাই স্পাইক বা ঢেউকে ছড়িয়ে দেয় এবং আপনার ইনফ্রাস্ট্রাকচারকে ঢেউয়ের মতো আঘাত থেকে রক্ষা করে।
প্যাটার্ন ৩: Idempotency-এর জন্য ডিজাইন করা
এখানেই কিউ ইনফ্রাস্ট্রাকচার এবং বিজনেস লজিকের মিলন ঘটে। কল্পনা করুন একটি জব যা একটি পেমেন্ট প্রোভাইডারের মাধ্যমে গ্রাহককে চার্জ করে। ওয়ার্কার সফলভাবে চার্জটি সম্পন্ন করে, কিন্তু ডেটাবেসে সফলতার রেকর্ড রাখা বা জবের স্বীকৃতি দেওয়ার আগেই কানেকশন বিচ্ছিন্ন হয়ে যায়। কিউ তখন একটি ব্যর্থতা দেখে এবং এটি রিট্রাই করে। ফলে গ্রাহকের কাছ থেকে দুইবার টাকা কেটে নেওয়া হয়।
In Node.js, prevent this by making every side effect idempotent. Generate an idempotency key from the job ID or a business-specific identifier. Before you create the charge or send the email or adjust inventory, check whether the work was already done. Pass that key all the way through to your database and to any third-party APIs that accept one. Structure your jobs so that running the same payload ten times produces the same outcome as running it once. This single habit eliminates an entire category of financial and data-integrity bugs.
Pattern 4: Treat Your Dead-Letter Queue Like a Dashboard
A DLQ is not a graveyard where bad jobs go to be forgotten. It is an operational tool, and it should be one of your most watched surfaces.
Set up alerts that fire when the DLQ depth increases. Even a single message in the DLQ often means your validation logic is broken, an upstream schema changed, or a downstream service is sending garbage you no longer recognize. These are exactly the signals you want to catch before customers start complaining. Build a dashboard that lets you inspect the raw payload, the stack trace, and the timestamp. Have a runbook ready: inspect the failure, patch the code, then replay the messages in the correct order. If your queue implementation supports it, alert on growth rate as well as absolute count, because one bad deploy can flood the DLQ within minutes.
Pattern 5: When in Doubt, Crash the Process
Node.js runs on a single event loop inside a V8 isolate. An unhandled promise rejection or an
