বেশিরভাগ Node.js টিউটোরিয়ালে এরর হ্যান্ডলিংকে (error handling) একটি গৌণ বিষয় হিসেবে দেখা হয়। আপনি একটি রুট হ্যান্ডলারকে try/catch ব্লকের মধ্যে রাখেন, স্ট্যাক ট্রেস (stack trace) লগ করেন এবং একটি 500 রেসপন্স পাঠান। এই মানসিকতা টিকে থাকে কারণ একটি HTTP রিকোয়েস্টের অন্য প্রান্তে একজন প্রকৃত মানুষ অপেক্ষা করছে। ব্যাকগ্রাউন্ড জবগুলো (background jobs) ভিন্ন। একটি কিউ সিস্টেমে (queue system), উত্তর দেওয়ার জন্য কোনো অধৈর্য ক্লায়েন্ট নেই, নেই কোনো স্বয়ংক্রিয় ব্রাউজার রিফ্রেশ। সেখানে আছে শুধু একটি ওয়ার্কার (worker), একটি পেলোড (payload), এবং নিঃশব্দে বাড়তে থাকা একটি রিট্রাই কাউন্টার (retry counter)। যখন সমস্যা হয়, তা প্রথমে ধীরে ধীরে শুরু হয়, তারপর হঠাৎ করেই সব একসাথে ঘটে। একটি ভুলভাবে শ্রেণীবদ্ধ করা এরর (error) পুরো পাইপলাইনকে স্থবির করে দিতে পারে অথবা রাত তিনটের সময় একজন ইঞ্জিনিয়ারকে জাগিয়ে তুলতে পারে।

এই পার্থক্যের কারণটি সহজ। রিকোয়েস্ট-রেসপন্স সাইকেলগুলো দ্রুত এবং স্পষ্টভাবে ব্যর্থ হয়। একটি কিউ ফেইলর (queue failure) ঘটে নিঃশব্দে। একটি ডাটাবেস কানেকশন বিচ্ছিন্ন হওয়ার আগে একটি ওয়ার্কার শত শত জব সম্পন্ন করতে পারে। স্পষ্ট হ্যান্ডলিং রুলস না থাকলে, ওয়ার্কারটি তাৎক্ষণিকভাবে রিট্রাই করতে থাকে, যা ইতিমধ্যে হিমশিম খাওয়া ডাটাবেসটির ওপর আরও চাপ সৃষ্টি করে এবং শেষ পর্যন্ত ক্র্যাশ করে। যেহেতু কেউ সরাসরি ওয়ার্কারটিকে পর্যবেক্ষণ করছে না, তাই সমস্যার প্রথম লক্ষণটি প্রায়ই হয় একটি ক্যাস্কেডিং ব্যাকআপ (cascading backup) অথবা লগ ফাইল দিয়ে পূর্ণ ডিস্ক। আপনার শুধু catch ব্লকের চেয়ে বেশি কিছু প্রয়োজন। আপনার এমন একটি কৌশল প্রয়োজন যা বিভিন্ন ধরনের ব্যর্থতাকে ভিন্নভাবে বিবেচনা করে এবং একটি খারাপ জবের কারণে পুরো সিস্টেমকে রক্ষা করে।

দুই ধরনের ব্যর্থতা

প্রতিটি এররকে দুটি ভাগে ভাগ করার মাধ্যমে শুরু করুন।

রিট্রাইয়েবল এরর (Retryable errors) হলো সাময়িক। থার্ড-পার্টি এপিআই-তে নেটওয়ার্ক টাইমআউট, একটি 429 রেট-লিমিট রেসপন্স, অথবা প্রাইমারি ডাটাবেসের তুলনায় একটি টেম্পোরারি ডাটাবেস রেপ্লিকার ল্যাগিং (lagging)। এগুলো চাপের লক্ষণ, বাগ (bug) নয়। সিস্টেমটি ত্রিশ সেকেন্ডের মধ্যে নিজেই ঠিক হয়ে যেতে পারে। রিট্রাইয়েবল জবগুলো পুনরায় চেষ্টা করার সুযোগ পাওয়ার যোগ্য, তবে তা কেবল নিয়ন্ত্রিত শর্তের অধীনে।

পার্মানেন্ট এরর (Permanent errors) হলো ভুল। পেলোডে ইনভ্যালিড JSON, একটি মিসিং ইউজার আইডি, অথবা স্টোরেজে থাকা কোনো প্রয়োজনীয় ফাইল খুঁজে না পাওয়া। এগুলো প্রথমবার যেভাবে ব্যর্থ হয়েছিল, ১০০তম বারও ঠিক একইভাবে ব্যর্থ হবে। এগুলো রিট্রাই করা মানে সিপিইউ সাইকেল (CPU cycles) নষ্ট করা, কিউ স্লট (queue slots) অপচয় করা এবং একটি টক্সিক ব্যাক-প্রেশার (toxic back-pressure) তৈরি করা যা সুস্থ জবগুলোকে বিলম্বিত করে। একটি পার্মানেন্ট ফেইলরের একমাত্র কার্যকর জায়গা হলো লগ, একটি অ্যালার্ট, অথবা একটি ডেড-লেটার কিউ (dead-letter queue)। এটি রিট্রাই লুপের (retry loop) অংশ হওয়া উচিত নয়।

একটি ডিসিশন ইঞ্জিন তৈরি করুন

তাৎক্ষণিকভাবে শ্রেণীবদ্ধ করুন। এই সিদ্ধান্তটি কিউ ফ্রেমওয়ার্কের (queue framework) ওপর ছেড়ে দেবেন না। আপনি যখনই একটি এরর ধরতে পারবেন, তখনই এর ভাগ্য নির্ধারণ করুন।

বাস্তবে এর অর্থ হলো কাস্টম এরর ক্লাস (custom error classes) বা র‍্যাপার ফাংশন (wrapper functions) তৈরি করা যা এররটি উপরে পাঠানোর (bubbling up) আগে ব্যর্থতার কারণটি পরীক্ষা করে দেখে। যদি একটি ডাটাবেস ড্রাইভার 'connection reset' এরর দেয়, তবে আপনার হ্যান্ডলারের উচিত সেটিকে 'retryable' হিসেবে ট্যাগ করা। যদি একটি পেলোড ভ্যালিডেটর 'schema mismatch' এরর দেয়, তবে সেটিকে 'permanent' হিসেবে ট্যাগ করুন। অনেক জব প্রসেসর ডিফল্টভাবে সবকিছু রিট্রাই করে, যা আপনার করা সবচেয়ে ব্যয়বহুল সিদ্ধান্ত হতে পারে। পার্মানেন্ট জবগুলোকে তাৎক্ষণিকভাবে প্রত্যাখ্যান করুন। হয় সেগুলোকে বাদ দিন অথবা একটি ডেড-লেটার কিউতে (dead-letter queue) পাঠিয়ে দিন যাতে সেগুলো মূল পাইপলাইনকে ক্ষতিগ্রস্ত করতে না পারে। এই একটি অভ্যাস যেকোনো ইনফ্রাস্ট্রাকচার পরিবর্তনের চেয়েও বেশি নির্ভরযোগ্যভাবে স্নোবল ইফেক্ট (snowball effects) প্রতিরোধ করে।

ব্যাক অফ (Back Off), তবে আরও বুদ্ধিমত্তার সাথে

যখন আপনি রিট্রাই করবেন, কখনোই তা তাৎক্ষণিকভাবে করবেন না। যদি একটি ডাটাবেস ডাউন থাকে, তবে প্রতি সেকেন্ডে অনেকগুলো ওয়ার্কারের আক্রমণ একটি অভ্যন্তরীণ 'denial-of-service' অ্যাটাকের মতো মনে হতে পারে। এক্সপোনেনশিয়াল ব্যাকঅফ (exponential backoff) ব্যবহার করুন। এক মিনিট অপেক্ষা করুন, তারপর পাঁচ মিনিট, তারপর পনেরো মিনিট। আপস্ট্রিম সিস্টেমটিকে (upstream system) সেরে ওঠার সুযোগ দিন।

কিন্তু শুধুমাত্র এক্সপোনেনশিয়াল ব্যাকঅফ যথেষ্ট নয়। যদি কোনো সার্ভিস রিস্টার্ট হওয়ার কারণে একসাথে হাজার হাজার জব ব্যর্থ হয়, তবে তাদের রিট্রাই শিডিউলগুলো একই সময়ে চলে আসবে। সার্ভিসটি যখন অনলাইনে ফিরে আসবে, তখন তারা সবাই একসাথে সেখানে আঘাত করবে, যা পুনরায় সার্ভিসটিকে ডাউন করে দিতে পারে। এখানে 'jitter' যোগ করুন: প্রতিটি বিলম্বের (delay) সাথে একটি ছোট র‍্যান্ডম অফসেট (random offset)। রিট্রাইগুলোকে কয়েক সেকেন্ডের ব্যবধানে ছড়িয়ে দিলে সিনক্রোনাইজড স্ট্যাম্পিড (synchronized stampedes) প্রতিরোধ করা যায়। গণিতটি সহজ, কিন্তু এটি যে স্থিতিশীলতা প্রদান করে তা বিশাল।

প্রমাণ সংরক্ষণ করুন

একটি ডেড-লেটার কিউ (dead-letter queue) হলো আপনার অডিট ট্রেইল (audit trail), কোনো আবর্জনার ঝুড়ি নয়। যখন একটি জব তার শেষ রিট্রাই করার সুযোগও হারিয়ে ফেলে, তখন সেটি কেবল মুছে ফেলবেন না। এরর কনটেক্সট (error context) এবং রিট্রাই হিস্ট্রি (retry history)-সহ পুরো পেলোডটি একটি DLQ-তে সরিয়ে নিন।

এটি প্রমাণ সংরক্ষণ করে। একজন মানুষ জবটি পরীক্ষা করতে পারেন, বাগটি ঠিক করতে পারেন এবং প্রয়োজনে এটি ম্যানুয়ালি পুনরায় চালাতে (replay) পারেন। আরও গুরুত্বপূর্ণ বিষয় হলো, আপনার DLQ-এর গভীরতা (depth) পর্যবেক্ষণ করুন। ডেড-লেটার করা জবের সংখ্যা হঠাৎ বেড়ে যাওয়া প্রায়শই একটি ভুল ডিপ্লয়মেন্ট (bad deploy), ভুল স্কিমা পরিবর্তন, অথবা কোনো এক্সটার্নাল ভেন্ডরের চুক্তি ভঙ্গের প্রাথমিক সতর্কতা। DLQ-এর বৃদ্ধিকে একটি 'leading indicator' হিসেবে বিবেচনা করুন, 'trailing indicator' হিসেবে নয়। যদি আপনার DLQ পূর্ণ হতে থাকে, তবে বুঝতে হবে আপস্ট্রিম-এ কিছু পরিবর্তন হয়েছে এবং ব্যাকলগ ছড়িয়ে পড়ার আগেই আপনার টিমের তা জানা প্রয়োজন।

রিট্রাই-এর জন্য ডিজাইন করুন

প্রতিটি জব এমনভাবে ডিজাইন করুন যেন এটি দুবার চলবে, কারণ এটি হওয়ার সম্ভাবনা আছে। একটি ওয়ার্কার প্রসেসিংয়ের মাঝপথে ব্যর্থ হতে পারে, পুনরায় শিডিউল হতে পারে এবং আবার চলতে পারে। যদি আপনার জব কোনো গ্রাহকের কাছ থেকে টাকা কেটে নেয়, ইমেল পাঠায় বা ইনভেন্টরি কাউন্ট বৃদ্ধি করে, তবে একটি সাধারণ রিট্রাই (retry) ডুপ্লিকেট তৈরি করতে পারে।

এর সমাধান হলো idempotency। কোনো সাইড ইফেক্ট (side effect) করার আগে পরীক্ষা করে দেখুন সেটি ইতিমধ্যে সম্পন্ন হয়েছে কি না। জব পেলোড (job payload) থেকে একটি ইউনিক আইডেন্টিফায়ারকে idempotency key হিসেবে ব্যবহার করুন। সেই কী-টি একটি স্বল্পস্থায়ী ক্যাশ (cache) বা ইউনিকনেস কনস্ট্রেইন্ট (uniqueness constraint) সহ একটি ডাটাবেস টেবিলে সংরক্ষণ করুন। যদি কী-টি আগে থেকেই থাকে, তবে কাজটি বাদ দিন এবং সফলতার বার্তা প্রদান করুন। এটি রিট্রাই-কে একটি ঝুঁকি থেকে একটি ক্ষতিকারক নয় এমন (no-op) কাজে পরিণত করে। এতে কোডের কয়েক লাইন বেশি লাগে, কিন্তু এটি আপনাকে ফিন্যান্স টিমকে ব্যাখ্যা করা থেকে বাঁচাবে যে কেন রাতারাতি রেভিনিউ দ্বিগুণ হয়ে গেল।

প্রসেস সুরক্ষিত রাখুন

আনবাউন্ড প্রমিস রিজেকশন (unbounded promise rejections) এবং অনাকাঙ্ক্ষিত এক্সেপশন (stray exceptions) কোনো সতর্কতা ছাড়াই একটি Node.js প্রসেস বন্ধ করে দিতে পারে। একটি ওয়ার্কারের ক্ষেত্রে এর অর্থ হলো জব হারিয়ে যাওয়া এবং একটি অর্কেস্ট্রেটর (orchestrator) কন্টেইনারটি পুনরায় চালু করার জন্য হিমশিম খাওয়া।

unhandledRejection এবং uncaughtException-এর জন্য গ্লোবাল হ্যান্ডলার রেজিস্টার করুন। তাদের কাজ অ্যাপ্লিকেশনকে রক্ষা করা নয়। বরং প্রয়োজনীয় ন্যূনতম ক্লিনআপ (cleanup) সম্পন্ন করে প্রসেসটি বন্ধ করে দেওয়া। Docker, Kubernetes, বা systemd-কে একটি পরিষ্কার মেমরি স্টেটসহ ওয়ার্কারটি পুনরায় চালু করতে দিন। একটি গ্লোবাল হ্যান্ডলার চলার পর কোনোমতে চলতে থাকা মেমরি লিক (memory leak) এবং করাপ্টেড স্টেট (corrupted state)-এর ঝুঁকি বাড়ায়। ভুলভাবে জব প্রসেস করা একটি ধীরগতির জম্বি প্রসেসের চেয়ে দ্রুত এবং পরিচ্ছন্নভাবে বন্ধ হয়ে যাওয়া অনেক বেশি নিরাপদ। আপনার অর্কেস্ট্রেটরের ওপর ভরসা রাখুন যাতে সে আপনাকে পুনরায় চালু করতে পারে; একটি করাপ্টেড রানটাইমকে (corrupted runtime) ঠকানোর চেষ্টা করবেন না।

সিগন্যালকে সম্মান জানান

ডেপ্লয়মেন্ট, স্কেলিং ইভেন্ট এবং নোড রোটেশনের সময় ওয়ার্কারগুলো বন্ধ হয়ে যায়। যদি আপনার প্রসেস SIGTERM পাওয়ার সাথে সাথেই বন্ধ হয়ে যায়, তবে বর্তমানে চলমান জবটি আপনি মাঝপথে থামিয়ে দিচ্ছেন। সেই জবটি হয়তো কখনোই শেষ হবে না, এমনকি এর রিট্রাই কাউন্টারও হয়তো এখনো বৃদ্ধি পায়নি।

SIGTERM এবং SIGINT-এর জন্য লিসেন (listen) করুন। যখন কোনো সিগন্যাল আসবে, কিউ (queue) থেকে নতুন জব নেওয়া বন্ধ করে দিন। সম্ভব হলে বর্তমান জবটি শেষ করুন। একটি নির্দিষ্ট টাইমআউট সেট করুন, হয়তো ৩০ সেকেন্ড, যার পরে আপনি নির্বিশেষে প্রসেসটি বন্ধ করে দেবেন। এই 'গ্রেসফুল শাটডাউন' (graceful shutdown) কিউ-এর প্রতি সম্মান প্রদর্শন করে এবং ভুল ব্যর্থতা (false failures) এড়ায়। আপনার ডেপ্লয়মেন্ট পাইপলাইন একটি পরিচ্ছন্নভাবে বন্ধ হওয়া ওয়ার্কারকে 'হেলদি' (healthy) হিসেবে গণ্য করা উচিত, যেখানে একটি ক্র্যাশ করা ওয়ার্কার অ্যালার্ট ট্রিগার করবে।

মূল শিক্ষা

নির্ভরযোগ্য কিউ হ্যান্ডলিং মানে প্রতিটি এরর (error) ধরা নয়। এটি হলো প্রতিটি ফেইলর মোড (failure mode)-এর জন্য সুচিন্তিত সিদ্ধান্ত নেওয়া। সাময়িক সমস্যাগুলোর (transient ones) ক্ষেত্রে ধৈর্যের সাথে রিট্রাই করুন। স্থায়ী সমস্যাগুলো দ্রুত সমাধান বা বাদ দিন। আপনার ওয়ার্কারগুলোকে অতিরিক্ত চাপের (stampedes) হাত থেকে রক্ষা করুন, idempotency key দিয়ে আপনার ডেটা সুরক্ষিত রাখুন এবং মৃতপ্রায় প্রসেসগুলোকে পরিচ্ছন্নভাবে বন্ধ হতে দিন। যখন প্রতিটি ব্যর্থতার একটি নির্দিষ্ট পথ থাকবে, তখন রাত তিনটার সময়টিও সাধারণ সময়ের মতোই মনে হবে। আপনার পাইপলাইন চলতে থাকবে এবং আপনার টিম নিশ্চিন্তে ঘুমাতে পারবে।