Express روٹ کے اندر امیج ریسائزنگ (image resizing) چلانا تباہی کا باعث بن سکتا ہے۔ ایک صارف دس میگا بائٹ کی تصویر اپ لوڈ کرتا ہے، آپ کا سرور پکسلز پر کام کرنا شروع کر دیتا ہے، اور تیس سیکنڈ بعد ریکویسٹ ٹائم آؤٹ ہو جاتی ہے۔ بیک گراؤنڈ جاب کیوز (background job queues) اسی طرح کی پریشانیوں سے بچنے کے لیے بنائی گئی ہیں۔ Node.js کے ایکو سسٹم میں، Redis کے ذریعے غیر ہم آہنگ (asynchronous) کاموں کو سنبھالنے کے لیے Bull اور BullMQ دو بڑے نام بن چکے ہیں۔ ان کا بنیادی ڈھانچہ ایک جیسا ہے لیکن فلسفے اور روزمرہ کے استعمال کے لحاظ سے ان میں واضح فرق ہے۔ صحیح انتخاب کرنا بہت ضروری ہے کیونکہ بعد میں تبدیلی کرنا محض ایک سادہ پیکج اپ ڈیٹ نہیں ہے۔
مشترکہ بنیاد
دونوں لائبریریاں Redis کو اپنی بنیاد کے طور پر استعمال کرتی ہیں۔ Redis ایٹمی آپریشنز (atomic operations)، تاخیر سے ہونے والی جابز کے لیے sorted sets، اور ایونٹس کے لیے pub/sub کو سنبھالتا ہے۔ اگر آپ پہلے سے ہی کیشنگ (caching) یا سیشنز کے لیے Redis استعمال کر رہے ہیں، تو جاب کیو (job queue) شامل کرنے کے لیے کسی نئے انفراسٹرکچر کی ضرورت نہیں ہے۔ Bull اور BullMQ دونوں ترجیحات (priorities)، بیک آف کے ساتھ ری ٹرائی (retries with backoff)، کنکرنسی کنٹرولز (concurrency controls)، اور بار بار دہرائی جانے والی جابز (repeatable jobs) کی حمایت کرتے ہیں۔ یہ مماثلت انتخاب کو آسان بنانے کے بجائے مزید مشکل بنا دیتی ہے۔ آپ صرف فیچرز کی فہرست دیکھ کر فیصلہ نہیں کر سکتے۔ اس کے بجائے، آپ کو یہ دیکھنا ہوگا کہ ہر لائبریری آپ سے اپنے کوڈ کی ساخت (structure) کس طرح چاہتی ہے۔
Bull: آزمودہ تجربہ کار
Bull برسوں سے موجود ہے اور ہزاروں پروڈکشن ایپلی کیشنز میں استعمال ہو رہا ہے۔ یہ بہترین کام کرتا ہے۔ اس کا API ہر چیز کو ایک ہی Queue instance میں لپیٹ دیتا ہے۔ آپ اسی ایک آبجیکٹ پر اسے انسٹینشئیٹ (instantiate) کرتے ہیں، پروسیسنگ فنکشن کی تعریف کرتے ہیں، اور ایونٹس کو سنتے ہیں۔ اگر آپ پرانے Node.js پیٹرنز سے آئے ہیں تو یہ مونو لیتھک (monolithic) ڈیزائن جانا پہچانا لگتا ہے۔ وہ کوڈ بیسز جو widespread async/await سے پہلے کے ہیں، Bull کے ساتھ قدرتی طور پر فٹ بیٹھتے ہیں کیونکہ یہ کال بیکس (callbacks) اور پرانے Redis کلائنٹس کے ساتھ ساتھ پروان چڑھا ہے۔
اس کا نقصان ٹائٹ کپلنگ (tight coupling) ہے۔ جب آپ کا API سرور ایک جاب تخلیق کرتا ہے، تو وہ اسی Queue آبجیکٹ کو امپورٹ کرتا ہے جس میں ورکر لاجک (worker logic) موجود ہوتی ہے۔ عملی طور پر، اس کا مطلب یہ ہے کہ آپ کا ویب پروسیس ایسی ڈیپینڈنسیز (dependencies) کو بھی ساتھ لے آتا ہے جنہیں وہ کبھی چلاتا ہی نہیں ہے۔ یہ کوئی جان لیوا نقص نہیں ہے، لیکن یہ کلین آرکیٹیکچر (clean architecture) میں خلل ڈالتا ہے۔ سادہ ورک لوڈز کے لیے، شاید آپ کو کبھی محسوس نہ ہو، لیکن درجنوں ماڈیولز والی بڑی ٹیموں کے لیے، یہ رکاوٹ بڑھتی جاتی ہے۔
BullMQ: مکمل طور پر نئی تعمیر
BullMQ اس کا آفیشل جانشین ہے۔ اسے پہلے دن سے ہی TypeScript میں دوبارہ لکھا گیا ہے، اس لیے ٹائپس (types) کوئی بعد میں جوڑی گئی چیز نہیں ہیں بلکہ کوڈ کا حصہ ہیں۔ اس کا API ذمہ داریوں کو الگ الگ کلاسز میں تقسیم کرتا ہے۔ Queue جابز شامل کرنے کو سنبھالتا ہے۔ Worker انہیں پروسیس کرنے کو سنبھالتا ہے۔ QueueEvents مشاہدے (observability) کو سنبھالتا ہے۔ یہ علیحدگی اس بات کی عکاسی کرتی ہے کہ جدید ڈسٹری بیوٹڈ سسٹمز (distributed systems) حقیقت میں کیسے کام کرتے ہیں۔ آپ کے API پوڈز (pods) کو صرف Queue کلاس اور Redis کنکشن کی ضرورت ہوتی ہے۔ آپ کے ورکر پوڈز Worker کلاس کو امپورٹ کرتے ہیں۔ یہ حد صرف تصوراتی نہیں بلکہ عملی ہے۔
یہ تبدیلی بڑی ٹیموں کے لیے فائدہ مند ثابت ہوتی ہے۔ ایک ڈویلپر جو نیا فیچر لانچ کر رہا ہے، وہ یہ جانے بغیر کہ پروسیسر کس فائل میں ہے، جاب کو ان کیو (enqueue) کر سکتا ہے۔ کمپائلر رن ٹائم کے بجائے جاب ڈیٹا اور ہینڈلرز کے درمیان ٹائپ کی غلطیوں کو شروع میں ہی پکڑ لیتا ہے۔ async/await API بھی جدید Node.js میں بالکل قدرتی محسوس ہوتی ہے۔ آپ کو پرانے طریقوں سے لڑنا نہیں پڑے گا۔
جاب فلو: عارضی حل سے مکمل نظام تک
ملٹی سٹیپ ورک فلو (multi-step workflows) دونوں لائبریریوں کے درمیان سب سے بڑا فرق ظاہر کرتے ہیں۔
فرض کریں کہ آپ ای کامرس انوائسنگ پائپ لائن بنا رہے ہیں۔ ایک صارف خریداری مکمل کرتا ہے۔ آپ کو انوینٹری ریزرو کرنے، کارڈ سے رقم کاٹنے، PDF بنانے اور ای میل بھیجنے کی ضرورت ہے۔ Bull کے ساتھ، ان مراحل کو آپس میں جوڑنے کا مطلب دستی حساب کتاب ہے۔ ہو سکتا ہے کہ ایک پروسیسر اگلی جاب شروع کرے، اور Redis یا بھاری ڈیٹا کے ذریعے اسٹیٹ (state) پاس کرے۔ آپ کو پیرنٹ-چائلڈ کوآرڈینیشن (parent-child coordination) خود لکھنی پڑتی ہے۔ یہ تب تک کام کرتا ہے جب تک کہ کوئی مسئلہ نہ آ جائے۔ ری ٹرائی لاجک (retry logic) الجھ جاتی ہے۔ اگر PDF والا مرحلہ ناکام ہو جائے، تو رقم کی واپسی کے لیے آپ کو ایسا کسٹم کوڈ لکھنا پڑے گا جس میں غلطی کا امکان زیادہ ہوتا ہے۔
BullMQ FlowProducer متعارف کرواتا ہے۔ آپ جابز کا ایک درخت (tree) ترتیب دیتے ہیں جہاں پیرنٹ خود بخود اپنے چائلڈ جابز کا انتظار کرتے ہیں۔ انوائسنگ کی مثال میں، آپ finalize-order نامی ایک روٹ جاب بناتے ہیں جس کے تین چائلڈ جابز ہوں: reserve-inventory، charge-payment اور generate-pdf۔ آپ ای میل نوٹیفکیشن کو PDF جاب کا چائلڈ بنا سکتے ہیں۔ Redis اس گراف اسٹرکچر کو محفوظ کرتا ہے۔ پیرنٹ جاب صرف اس وقت فعال ہوتی ہے جب تمام انحصار (dependencies) کامیاب ہو جائیں۔ اگر ایک بھی چائلڈ ناکام ہو جائے تو پوری شاخ رک جاتی ہے۔ آپ کو پولنگ لوپس (polling loops) یا ریکرسو جاب سپونرز (recursive job spawners) لکھنے کی ضرورت نہیں پڑتی۔ یہ محض کوڈ کو خوبصورت بنانے کے لیے نہیں ہے، بلکہ یہ آپ کے بزنس لاجک کو ماڈل کرنے کا طریقہ بدل دیتا ہے۔
ریٹ لمٹنگ: ایک سادہ آلہ بمقابلہ ایک باریک آلہ
دونوں لائبریریاں تھرو پٹ (throughput) کو کنٹرول کر سکتی ہیں، لیکن ان کی باریکی (granularity) میں بہت فرق ہے۔
Bull ہر کیو کے لیے ریٹ لمٹس لاگو کرتا ہے۔ اگر آپ ایک کیو کو فی سیکنڈ سو جابز پروسیس کرنے کے لیے سیٹ کرتے ہیں، تو وہ حد کیو میں موجود ہر جاب پر یکساں طور پر لاگو ہوتی ہے۔ یہ یکساں نوعیت کے کاموں (homogeneous workloads) کے لیے تو ٹھیک ہے، لیکن ملٹی ٹیننٹ SaaS پلیٹ فارمز میں یہ مسئلہ بن جاتا ہے۔ تصور کریں کہ ایک کسٹمر شیئرڈ کیو میں لاکھوں ویب ہک ڈیلیوریز ڈال رہا ہے۔ Bull کی کیو لیول کی حد کا مطلب یہ ہے کہ آپ اس مخصوص ٹیننٹ کی رفتار کم نہیں کر سکتے جب تک کہ آپ باقی سب کی رفتار بھی کم نہ کر دیں۔ آپ کے پاس آپشنز مشکل ہیں۔ یا تو ہر کسٹمر کے لیے الگ الگ Redis کیوز بنائیں اور انہیں ڈائنامک طریقے سے مینیج کریں، یا پھر اس ناانصافی کو قبول کر لیں۔
BullMQ گروپ پر مبنی ریٹ لمٹنگ کا اضافہ کرتا ہے۔ آپ ہر جاب کو ایک گروپ کی (group key) کے ساتھ ٹیگ کرتے ہیں، جو عام طور پر ایک ٹیننٹ یا یوزر آئی ڈی ہوتی ہے، اور پھر ہر گروپ کے لیے حدود متعین کرتے ہیں۔ وہی ایک کیو تمام ٹیننٹس کے لیے جابز پروسیس کرتی ہے، لیکن شیڈولر ہر گروپ کی رفتار کو آزادانہ طور پر کنٹرول کرتا ہے۔ کسٹمر A کی جانب سے اچانک آنے والا لوڈ کسٹمر B کو متاثر نہیں کرتا۔ اس طرح آپ کیوز کے پھیلاؤ سے بچتے ہیں اور اپنے Redis keyspace کو منظم رکھتے ہیں۔ جن پلیٹ فارمز پر 'noisy-neighbor' (ایک صارف کا دوسرے پر اثر) کے خدشات ہوں، ان کے لیے صرف یہی وجہ مائیگریشن کے لیے کافی ہو سکتی ہے۔
عملی طور پر بہتر آرکیٹیکچر
کیو (Queue) اور ورکر (Worker) کی علیحدگی کا فرق تب تک محسوس نہیں ہوتا جب تک آپ پروڈکشن میں کسی مسئلے کو ڈی بگ نہ کریں۔ Bull کے ساتھ، یہ عام ہے کہ جاب بنانے کا کوڈ روٹ ہینڈلرز کے اندر گہرائی میں ہو، جو بھاری پروسیسنگ ڈیپینڈنسیز کو بھی امپورٹ کر رہے ہوں۔ BullMQ آپ کو یہ فیصلہ کرنے پر مجبور کرتا ہے کہ کام کہاں ہونا چاہیے۔ آپ کے ویب سرورز ہلکے (lean) رہتے ہیں۔ آپ کے ورکر کنٹینرز بھاری لائبریریز، امیج پروسیسرز، یا ہیڈ لیس براؤزرز کو اپنے اندر رکھتے ہیں۔ اگر میموری لیک (memory leak) نظر آئے، تو آپ کو بالکل معلوم ہوتا ہے کہ کس قسم کے پروسیس کو پروفائل کرنا ہے۔ اس کا ذہنی ماڈل Celery یا Sidekiq جیسے سسٹمز کے زیادہ قریب ہے۔
انتخاب کرنا
اگر آپ بالکل نیا پروجیکٹ شروع کر رہے ہیں، تو BullMQ سے آغاز کریں۔ اس کی TypeScript ڈیفینیشنز درست اور مکمل ہیں۔ جاب فلو (job flows) آرکیسٹریشن کوڈ کی ضرورت کو ختم کر دیتے ہیں۔ گروپ ریٹ لمٹنگ انصاف کے مسائل کو شروع ہونے سے پہلے ہی حل کر دیتی ہے۔ اس کی async/await API بالکل نیٹیو محسوس ہوتی ہے۔ کسی نئے (greenfield) پروجیکٹ کے لیے پرانی لائبریری کا انتخاب کرنے کی کوئی خاص وجہ نہیں ہے۔
اگر Bull پہلے سے کام کر رہا ہے، تو اسی پر رہیں۔ مائیگریشن میں وقت لگتا ہے اور استحکام (stability) کو خطرہ ہو سکتا ہے۔ اگر آپ کی جابز سادہ اور آزادانہ ہیں، تو آپ کو ان فیچرز کی کمی محسوس نہیں ہوگی جن کی آپ کو حقیقت میں ضرورت ہے۔ ایک ایسی کیو جو پاس ورڈ ری سیٹ ای میلز بھیجتی ہے اور اوتار (avatars) کا سائز تبدیل کرتی ہے، اسے فلو گراف کی ضرورت نہیں ہے۔ صرف نظریاتی اصول پسندی کے لیے چلتے ہوئے کوڈ کو دوبارہ لکھنا انجینئرنگ نہیں ہے۔ یہ محض ایک مشغلہ ہے۔
مائیگریشن کی حقیقت
اگر آپ سوئچ کرتے ہیں، تو اسے انفراسٹرکچر کی تبدیلی کے طور پر لیں، نہ کہ کوڈ ری فیکٹر کے طور پر۔ Bull اور BullMQ مختلف Redis key schemas استعمال کرتے ہیں۔ وہ ایک دوسرے کے جاب ڈیٹا یا اسٹیٹ کو نہیں پڑھ سکتے۔ آپ صرف ایک فیچر فلیگ آن کر کے یہ امید نہیں کر سکتے کہ پرانی جابز مکمل ہو جائیں گی۔ آپ کو ہر موجودہ کیو کو مکمل طور پر خالی کرنا ہوگا، نئے ورکرز ڈیپلائے کرنے ہوں گے، اور BullMQ کے ساتھ جابز بھیجنا شروع کرنی ہوں گی۔ اس کے لیے مینٹیننس ونڈو یا بلیو-گرین ڈیپلائمنٹ کا منصوبہ بنائیں جہاں پرانے ورکرز پرانی کیو کو استعمال کریں جبکہ نئے ورکرز نئی کیو کو سنبھالیں۔
