প্রতিটি Node.js ডেভেলপার আজ হোক বা কাল একই সমস্যার সম্মুখীন হন। একজন ব্যবহারকারী একটি বাটনে ক্লিক করেন, আপনার route handler একটি ভারী কাজ করতে শুরু করে, আর HTTP request-টি সেখানেই আটকে থাকে। হতে পারে আপনি ব্যাচ আকারে ইমেল পাঠাচ্ছেন, কোনো থার্ড-পার্টি CRM-এ রেকর্ড সিঙ্ক করছেন, অথবা একটি PDF রিপোর্ট তৈরি করছেন। ব্রাউজার লোডিং দেখায়, মোবাইল অ্যাপ টাইম-আউট হয়ে যায়। আপনার ব্যবহারকারীরা অসন্তুষ্ট হন এবং আপনার সার্ভার এমন সব কানেকশন স্লট খরচ করে ফেলে যা হারানো আপনার পক্ষে সম্ভব নয়। এর সমাধান হলো সেই কাজটিকে request path থেকে সরিয়ে Redis-এর মাধ্যমে পরিচালিত একটি background job queue-তে নিয়ে যাওয়া। Node.js ইকোসিস্টেমে এই ক্ষেত্রে দুটি লাইব্রেরি প্রধান: Bull এবং BullMQ। এদের মধ্যে বেছে নেওয়া মানে কোনো বিজয়ীকে বেছে নেওয়া নয়, বরং আপনার প্রজেক্ট বর্তমানে কোথায় আছে এবং ভবিষ্যতে কোন দিকে যাচ্ছে তা বোঝা।
মূল নির্ভরযোগ্য হাতিয়ার
Bull বছরের পর বছর ধরে Node.js background processing-এর জন্য স্ট্যান্ডার্ড হিসেবে পরিচিত। এটি স্থিতিশীল, পরীক্ষিত এবং অসংখ্য প্রোডাকশন অ্যাপ্লিকেশনে ব্যবহৃত হচ্ছে। আপনার যদি কোনো কাজ পরে করার জন্য শিডিউল করতে হয়, কোনো ব্যর্থ ইমপোর্ট স্বয়ংক্রিয়ভাবে পুনরায় চেষ্টা (retry) করতে হয়, অথবা কঠোর অগ্রাধিকার (priority) নির্ধারণ করতে হয় যাতে নিউজলেটার পাঠানোর আগে পেমেন্ট webhook-গুলো সম্পন্ন হয়, তবে Bull কোনো ঝামেলা ছাড়াই তা সামলাতে পারে। এর API হলো callback-oriented, যার মানে এটি পুরনো কোডবেসের সাথে সহজেই মানিয়ে যায় যেখানে promise ছিল একটি নতুন বিষয়। যে দলগুলো দীর্ঘ সময় ধরে Bull ব্যবহার করছে তারা জানে ঠিক কী আশা করতে হবে। এই লাইব্রেরি Redis-এ স্টেট (state) সংরক্ষণ করে, তাই আপনার Node প্রসেস রিস্টার্ট হলেও কাজগুলো হারিয়ে যায় না। এই নির্ভরযোগ্যতার কারণেই অনেক ব্যবসা তাদের ইতিমধ্যে কাজ করা সিস্টেম পরিবর্তন করার প্রয়োজন বোধ করেনি।
BullMQ যা পরিবর্তন করে
BullMQ হলো এর উত্তরসূরি। এটি TypeScript দিয়ে একদম নতুনভাবে তৈরি করা হয়েছে এবং এর পুরো কাঠামো async/await-এর ওপর ভিত্তি করে নির্মিত। আপনি যদি গত কয়েক বছর ধরে আধুনিক Node.js কোড লিখে থাকেন, তবে এর সিনট্যাক্স আপনার কাছে পরিচিত মনে হবে। কিন্তু পার্থক্যটি কেবল টাইপ ডেফিনিশন বা promise চেইনের চেয়েও গভীর। BullMQ কিউ (queue) এবং ওয়ার্কারের (worker) মধ্যে একটি পরিষ্কার বিভাজন নিশ্চিত করে। Bull-এ, কিউ প্রায়ই ওয়ার্কার রানার হিসেবেও কাজ করে। BullMQ-তে, আপনি একটি ফাইলে কিউ এবং অন্য একটি ফাইলে ওয়ার্কার সংজ্ঞায়িত করেন। এই বিভাজনটি প্রোডাকশন সিস্টেমগুলো যেভাবে স্কেল করে তার প্রতিফলন ঘটায়। আপনি ওয়ার্কার কন্টেইনারের একটি বহর (fleet) ব্যবহার করতে পারেন যা শুধুমাত্র কাজ প্রসেস করবে, আর আপনার API সার্ভারগুলো শুধুমাত্র কিউতে কাজ যোগ করবে। সিস্টেম বড় হওয়ার সাথে সাথে আর্কিটেকচারটি সহজবোধ্য থাকে।
যেসব ফিচার পাল্লা ভারী করে দেয়
BullMQ যেখানে সত্যিকার অর্থে এগিয়ে থাকে তা হলো এমন কিছু ফাংশনালিটি যা Bull-এ নেই। বাস্তব অ্যাপ্লিকেশনগুলোতে তিনটি সংযোজন সবচেয়ে গুরুত্বপূর্ণ।
Job Flows
জটিল ওয়ার্কফ্লো খুব কমই একটি একক ব্যাকগ্রাউন্ড ফাংশনে সীমাবদ্ধ থাকে। কল্পনা করুন আপনি একটি ইমেজ প্রসেসিং পাইপলাইন তৈরি করছেন। একজন ব্যবহারকারী একটি র (raw) ছবি আপলোড করলেন, এবং আপনার ব্যাকএন্ডকে একটি থাম্বনেইল তৈরি করতে হবে, একটি কম্প্রেসড প্রিভিউ তৈরি করতে হবে, একটি OCR স্ক্যান চালাতে হবে এবং তারপর ফ্রন্টএন্ডকে জানাতে হবে যে সবকিছু প্রস্তুত। Bull-এর ক্ষেত্রে, আপনি সম্ভবত এই সমস্ত ধাপ একটি বড় এবং ভঙ্গুর হ্যান্ডলারে ঢুকিয়ে দিতেন। BullMQ 'job flows' প্রবর্তন করে, যা আপনাকে স্পষ্টভাবে প্যারেন্ট এবং চাইল্ড জবগুলোকে চেইন করতে দেয়। আপনি ডিপেন্ডেন্সি (dependency) নির্ধারণ করতে পারেন যাতে থাম্বনেইল এবং OCR জব দুটি সফল হওয়ার পরেই কেবল নোটিফিকেশন ধাপটি কার্যকর হয়। যদি OCR ব্যর্থ হয়, তবে থাম্বনেইলটি পুনরায় প্রসেস না করেই আপনি কেবল সেই অংশটি পুনরায় চেষ্টা করতে পারেন। এর ফলে লজিকটি মডুলার, পর্যবেক্ষণযোগ্য এবং রাত তিনটের সময় কিছু ভেঙে গেলে ডিবাগ করা অনেক সহজ হয়ে যায়।
Group Rate Limiting
আপনি যদি একটি মাল্টি-টেন্যান্ট SaaS অ্যাপ্লিকেশন চালান, তবে আপনি হয়তো চিন্তিত থাকেন যে কোনো একজন গ্রাহক আপনার ওয়ার্কারগুলোকে অতিরিক্ত কাজের চাপে ভাসিয়ে দিতে পারে। একজন একক টেন্যান্ট দশ হাজার এক্সপোর্ট জব কিউতে জমা দিয়ে অন্যদের কাজ ব্যাহত করতে পারে। BullMQ 'group rate limiting' যোগ করে, যা আপনাকে প্রতি টেন্যান্ট বা প্রতি API key অনুযায়ী প্রসেসিং নিয়ন্ত্রণ (throttle) করতে দেয়। উদাহরণস্বরূপ, আপনি টেন্যান্ট A-কে প্রতি মিনিটে পঞ্চাশটি এক্সটার্নাল API কল করার অনুমতি দিতে পারেন, যেখানে টেন্যান্ট B স্বাধীনভাবে একই সুবিধা পাবে। কিউটি শুধুমাত্র একটি মেশিনে লোকালি নয়, বরং সমস্ত ওয়ার্কার ইনস্ট্যান্স জুড়ে গ্লোবালি এই সীমা মেনে চলে। এটি এমন একটি সুরক্ষা ব্যবস্থা যা হঠাৎ প্রয়োজন না হওয়া পর্যন্ত আপনি উপলব্ধি করতে পারবেন না।
একটি আধুনিক ইন্টারফেস
BullMQ পুরনো কলব্যাক সিগনেচার বাদ দিয়ে একটি আধুনিক API গ্রহণ করেছে। এর এরর হ্যান্ডলিং স্ট্যান্ডার্ড প্রমিস প্যাটার্ন অনুসরণ করে। TypeScript ডেফিনিশনগুলো এখানে অত্যন্ত উন্নত মানের, যা আলাদা কোনো কমিউনিটি প্যাকেজ থেকে afterthought হিসেবে আসেনি। আপনি যদি কোনো নতুন প্রজেক্ট (greenfield project) শুরু করেন, তবে ডেভেলপার এক্সপেরিয়েন্স বা অভিজ্ঞতাটি লক্ষণীয়ভাবে মসৃণ হবে। আপনার এডিটর কিউ অপশনগুলো অটো-কমপ্লিট করবে। আপনার লিন্টার (linter) মিসিং জব নেমগুলো ধরে ফেলবে। এর ফলে মানসিক চাপ বা ওভারহেড অনেক কমে যায়।
Redis-এর অবিচলতা
এই সিদ্ধান্তের একটি ব্যবহারিক স্বস্তি হলো ইনফ্রাস্ট্রাকচার। Bull এবং BullMQ উভয়ই Redis-এ জবের স্টেট (state), মেটাডেটা (metadata) এবং শিডিউল (schedules) সংরক্ষণ করে। তাদের অভ্যন্তরীণ কী (key) স্ট্রাকচার ভিন্ন হলেও, মূল প্রযুক্তিটি একই। আপনি যদি ইতিমধ্যে Bull-এর জন্য Redis ব্যবহার করে থাকেন, তবে BullMQ গ্রহণ করার জন্য আপনাকে নতুন কোনো ডাটাবেস পরিবর্তন করতে হবে না বা আপনার ডিপ্লয়মেন্ট টপোলজি (deployment topology) নিয়ে নতুন করে ভাবতে হবে না। মাইগ্রেশনের চ্যালেঞ্জটি আপনার অ্যাপ্লিকেশন কোডে, আপনার সার্ভার বিলের মধ্যে নয়।
মাইগ্রেশনের বাস্তবতা
তা সত্ত্বেও, Bull থেকে BullMQ-তে যাওয়া কোনো সরাসরি প্রতিস্থাপন (drop-in replacement) নয়। API কলগুলো পরিবর্তিত হয়। ইভেন্টের নামগুলো ভিন্ন হয়। আপনি যেভাবে প্রসেসর (processors) সংজ্ঞায়িত করেন এবং কনকারেন্সি (concurrency) হ্যান্ডেল করেন, তা এতটাই পুনর্লিখন করতে হবে যে কিউ (queue)-এর সাথে যোগাযোগ করে এমন প্রতিটি ফাইল আপনাকে পরিবর্তন করতে হবে। আরও গুরুত্বপূর্ণ বিষয় হলো, আপনি কেবল একটি সুইচ টিপে আশা করতে পারেন না যে পুরনো জবগুলো নতুন সিস্টেমে শেষ হয়ে যাবে। একই Redis ইনস্ট্যান্সের বিপরীতে BullMQ ওয়ার্কার (workers) চালু করার আগে আপনাকে আপনার বিদ্যমান Bull কিউগুলো সম্পূর্ণভাবে খালি (drain) করতে হবে। অন্যথায়, একই কীস্পেসে (keyspace) দুটি ভিন্ন ফরম্যাটের সংঘর্ষ হওয়ার ঝুঁকি থাকে। একটি মেইনটেন্যান্স উইন্ডো (maintenance window) বা ব্লু-গ্রিন কাটওভারের (blue-green cutover) পরিকল্পনা করুন। এতে প্রকৃত পরিশ্রমের প্রয়োজন হয়, এবং সেই পরিশ্রমের সার্থকতা থাকতে হবে।
কোথায় সিদ্ধান্ত নেবেন
যদি আপনার বর্তমান Bull সেটআপ কোনো সমস্যা ছাড়াই ঠিকঠাক চলে, তবে এটিকে পরিবর্তন করবেন না। স্থিতিশীলতার গুরুত্ব আছে। একটি ব্যাকগ্রাউন্ড কিউ হলো ইনফ্রাস্ট্রাকচার, কোনো ফ্যাশন স্টেটমেন্ট নয়। যদি আপনার টিম আর্কিটেকচারের বিরুদ্ধে লড়াই করে কারণ আপনার জরুরিভাবে প্যারেন্ট-চাইল্ড ওয়ার্কফ্লো (parent-child workflows) বা পার-টেন্যান্ট রেট লিমিট (per-tenant rate limits) প্রয়োজন হয়, তবে মাইগ্রেশন করা যুক্তিযুক্ত। কনসার্নগুলোর (concerns) আরও পরিষ্কার বিভাজন এবং আধুনিক API সময়ের সাথে সাথে আপনার এই প্রচেষ্টার ফল দেবে।
যেকোনো নতুন প্রজেক্টের জন্য সিদ্ধান্ত নেওয়া সহজ। BullMQ দিয়ে শুরু করুন। এটি নিয়মিত আপডেট পায়, সরাসরি বর্তমান JavaScript স্ট্যান্ডার্ড সমর্থন করে এবং আপনাকে এমন সুযোগ দেয় যাতে ছয় মাসের মধ্যে লাইব্রেরিটি ছোট মনে না হয়েও আপনি জটিল জব ফ্লো (job flows) তৈরি করতে পারেন। আপনি এমন একটি API-এর ওপর টেকনিক্যাল ডেট (technical debt) তৈরি করা এড়াতে পারেন যা মেইনটেইনাররা ইতিমধ্যে ছাড়িয়ে গেছেন।
মূল কথা
একটি জব কিউ থাকে আপনার HTTP রেসপন্স দ্রুত রাখতে এবং ব্যবহারকারীদের ধৈর্যশীল রাখতে। Bull এখনও সেই কাজটি চমৎকারভাবে করে। BullMQ এটি এমন একটি কাঠামো দিয়ে করে যা আধুনিক Node.js অ্যাপ্লিকেশন কীভাবে তৈরি এবং স্কেল করা হয় তার সাথে সামঞ্জস্যপূর্ণ। প্রশ্নটি এটি নয় যে কোন লাইব্রেরিটি তাত্ত্বিকভাবে বা বিচ্ছিন্নভাবে ভালো। প্রশ্নটি হলো আপনার বর্তমান সমস্যাটি কি মাইগ্রেশনের যোগ্য, এবং আপনার পরবর্তী প্রজেক্টটি কি এমন একটি ভিত্তি পাওয়ার যোগ্য যা আপনার পরবর্তী ফান্ডিং রাউন্ড বা প্রোডাক্ট লঞ্চের আগে পরিবর্তন করার প্রয়োজন হবে না।
