Express route-க்குள் படங்களை மறுஅளவிடுவது (image resizing) ஒரு பேரழிவிற்கு வழிவகுக்கும். ஒரு பயனர் பத்து மெகாபைட் புகைப்படத்தைப் பதிவேற்றும்போது, உங்கள் சர்வர் பிக்சல்களைச் செயலாக்கத் தொடங்கும், முப்பது வினாடிகளுக்குப் பிறகு அந்தத் கோரிக்கை (request) காலாவதியாகிவிடும் (timeout). இத்தகைய சிக்கல்களைத் தவிர்க்கவே பின்னணி பணி வரிசைகள் (Background job queues) உள்ளன. Node.js சூழலில், Redis மூலம் ஒத்திசைவற்ற பணிகளைக் (asynchronous work) கையாளுவதற்கு Bull மற்றும் BullMQ ஆகிய இரண்டும் முக்கியமானத் தேர்வுகளாக உள்ளன. இவை இரண்டும் ஒரே அடிப்படையைக் கொண்டிருந்தாலும், அவற்றின் தத்துவம் மற்றும் அன்றாடப் பயன்பாட்டு முறையில் (ergonomics) பெரிதும் வேறுபடுகின்றன. சரியானதைத் தேர்ந்தெடுப்பது மிகவும் முக்கியம், ஏனெனில் பின்னர் மாற்றிக்கொள்வது என்பது ஒரு சாதாரண பேக்கேஜ் அப்டேட் (package update) போன்றது அல்ல.

பொதுவான அடிப்படை

இரண்டு நூலகங்களும் (libraries) Redis-ஐத் தங்கள் முதுகெலும்பாகப் பயன்படுத்துகின்றன. Redis அணுக்கச் செயல்பாடுகள் (atomic operations), தாமதப்படுத்தப்பட்ட பணிகளுக்கான வரிசைப்படுத்தப்பட்ட தொகுப்புகள் (sorted sets) மற்றும் நிகழ்வுகளுக்கான pub/sub ஆகியவற்றைச் கையாள்கிறது. நீங்கள் ஏற்கனவே கேச்சிங் (caching) அல்லது செஷன்களுக்காக (sessions) Redis-ஐப் பயன்படுத்தினால், ஒரு பணி வரிசையைச் சேர்ப்பதற்குப் புதிய உள்கட்டமைப்பு (infrastructure) தேவையில்லை. Bull மற்றும் BullMQ ஆகிய இரண்டும் முன்னுரிமைகள் (priorities), backoff முறையுடன் கூடிய மறுமுயற்சிகள் (retries), ஒரே நேரத்தில் பல பணிகளைச் செய்யும் கட்டுப்பாடு (concurrency controls) மற்றும் மீண்டும் மீண்டும் செய்யக்கூடிய பணிகள் (repeatable jobs) ஆகியவற்றை ஆதரிக்கின்றன. இந்த ஒற்றுமைகள் தேர்வை எளிதாக்குவதற்குப் பதிலாகக் கடினமாக்குகின்றன. நீங்கள் வெறும் அம்சங்களின் பட்டியலை (features checklist) மட்டும் வைத்துத் தீர்மானிக்க முடியாது. மாறாக, ஒவ்வொரு நூலகமும் உங்கள் குறியீட்டை (code) எவ்வாறு கட்டமைக்க விரும்புகிறது என்பதை நீங்கள் பார்க்க வேண்டும்.

Bull: களத்தில் நிரூபிக்கப்பட்ட அனுபவமிக்கத் தேர்வாக

Bull பல ஆண்டுகளாகப் பயன்பாட்டில் உள்ளது மற்றும் ஆயிரக்கணக்கான உற்பத்திச் சூழல்களில் (production applications) இயங்குகிறது. இது சிறப்பாகச் செயல்படுகிறது. இதன் API அனைத்தையும் ஒரு ஒற்றை Queue instance-க்குள் அடக்கிவிடுகிறது. நீங்கள் ஒரே பொருளில் (object) அதைத் தொடங்குவது (instantiate), ஒரு செயலாக்கச் செயல்பாட்டை (processing function) வரையறுப்பது மற்றும் நிகழ்வுகளைக் கவனிப்பது (listen for events) ஆகிய அனைத்தையும் செய்யலாம். பழைய Node.js முறைகளிலிருந்து நீங்கள் வந்தால், இந்த ஒற்றைத்தன்மை கொண்ட (monolithic) வடிவமைப்பு உங்களுக்குப் பரிச்சயமானதாக இருக்கும். பரவலான async/await பயன்பாட்டிற்கு முந்தைய குறியீட்டுத் தொகுப்புகள் (codebases) Bull-க்கு இயல்பாகவே பொருந்தும், ஏனெனில் இது callbacks மற்றும் முந்தைய Redis கிளையண்டுகளுடன் இணைந்து வளர்ந்தது.

இதன் குறைபாடு என்பது நெருக்கமான இணைப்பு (tight coupling) ஆகும். உங்கள் API சர்வர் ஒரு பணியை உருவாக்கும்போது, அது worker logic-ஐக் கொண்ட அதே Queue object-ஐ இறக்குமதி (import) செய்கிறது. நடைமுறையில், இதன் பொருள் உங்கள் web process தான் ஒருபோதும் செயல்படுத்தாத சார்புகளை (dependencies) இழுத்து வருகிறது என்பதாகும். இது ஒரு பெரிய குறைபாடு அல்ல, ஆனால் இது தூய்மையான கட்டமைப்பிற்கு (clean architecture) இடையூறாக அமைகிறது. எளிய பணிகளுக்கு நீங்கள் இதைக் கவனிக்காமல் போகலாம். ஆனால் டஜன் கணக்கான மாட்யூல்களைக் கொண்ட பெரிய குழுக்களுக்கு, இந்தச் சிரமம் காலப்போக்கில் அதிகரிக்கும்.

BullMQ: ஆரம்பத்திலிருந்து மறுசீரமைக்கப்பட்டது

BullMQ இதன் அதிகாரப்பூர்வத் தொடர்ச்சியாகும். இது முதல் நாளிலிருந்தே TypeScript-இல் மறுசீரமைக்கப்பட்டது, எனவே 'types' என்பது JavaScript மூலக்குறியீட்டில் பிறகு சேர்க்கப்பட்ட ஒன்றாக இருக்காது. இதன் API பொறுப்புகளைத் தனித்தனி வகுப்புகளாகப் (classes) பிரிக்கிறது. Queue பணிகளைச் சேர்ப்பதைக் கையாள்கிறது. Worker அவற்றைச் செயலாக்குவதைக் கையாள்கிறது. QueueEvents கண்காணிப்பைக் (observability) கையாள்கிறது. இந்தத் தனிப்பயனாக்கம் நவீன விநியோக அமைப்புகள் (distributed systems) எவ்வாறு இயங்குகின்றன என்பதைப் பிரதிபலிக்கிறது. உங்கள் API pods-க்கு Queue class மற்றும் Redis இணைப்பு மட்டுமே தேவைப்படும். உங்கள் worker pods Worker class-ஐ இறக்குமதி செய்யும். இந்த எல்லை வெறும் கருத்தியல் சார்ந்தது மட்டுமல்ல, அது இயற்பியல் ரீதியானது (physical).

இந்த மாற்றம் பெரிய குழுக்களுக்குப் பயனுள்ளதாக இருக்கும். ஒரு புதிய அம்சத்தை உருவாக்கும் டெவலப்பர், எந்தக் கோப்பில் processor உள்ளது என்று தெரியாமலேயே ஒரு பணியைச் சேர்க்க (enqueue) முடியும். runtime-இல் சிக்கல்களைச் சந்திப்பதற்குப் பதிலாக, job data மற்றும் handlers ஆகியவற்றிற்கு இடையிலான வகை முரண்பாடுகளை (type mismatches) கம்பைலர் (compiler) முன்கூட்டியே கண்டறிந்துவிடும். நவீன Node.js-இல் async/await API மிகவும் இயல்பாக இருக்கும். நீங்கள் பழைய முறைகளுடன் போராட வேண்டியிருக்காது.

பணி ஓட்டங்கள் (Job Flows): தற்காலிகத் தீர்வுகளிலிருந்து முதன்மையான அங்கங்களாக

பல படிகளைக் கொண்ட பணி ஓட்டங்கள் (Multi-step workflows) இந்த இரண்டு நூலகங்களுக்கும் இடையிலான மிகப்பெரிய இடைவெளியைக் காட்டுகின்றன.

உதாரணமாக, நீங்கள் ஒரு இ-காமர்ஸ் இன்வாய்ஸ் (invoicing) முறையை உருவாக்குகிறீர்கள் என்று வைத்துக்கொள்வோம். ஒரு வாடிக்கையாளர் பொருட்களை வாங்கும்போது, நீங்கள் இருப்புகளை (inventory) ஒதுக்கீடு செய்ய வேண்டும், கார்டில் பணம் வசூலிக்க வேண்டும், ஒரு PDF-ஐ உருவாக்க வேண்டும் மற்றும் மின்னஞ்சல் அனுப்ப வேண்டும். Bull-இல், இந்தப் படிகளை இணைப்பது என்பது கைமுறையாகப் பதிவேடுகளைப் பராமரிப்பதைப் போன்றது. ஒரு processor அடுத்த பணியைத் தொடங்கும் வகையில், Redis மூலமாகவோ அல்லது பெரிய தரவுப் பொதி (data payloads) மூலமாகவோ நிலையை (state) நீங்கள் கடத்த வேண்டியிருக்கும். பெற்றோர்-பிள்ளை (parent-child) ஒருங்கிணைப்பை நீங்களே எழுத வேண்டும். இது சரியாகச் செயல்படும் வரை நன்றாக இருக்கும், ஆனால் சிக்கல்கள் வரும்போது கடினமாகிவிடும். மறுமுயற்சித் தர்க்கம் (Retry logic) குழப்பமடையும். PDF படிநிலை தோல்வியடைந்தால், பண வசூலைத் திரும்பப் பெறத் தனிப்பயனாக்கப்பட்ட இழப்பீட்டு குறியீடு (compensation code) தேவைப்படும், இது தவறாகச் செய்யப்பட வாய்ப்புள்ளது.

BullMQ FlowProducer-ஐ அறிமுகப்படுத்துகிறது. இதில் நீங்கள் பணிகளின் ஒரு மர அமைப்பை (tree of jobs) வரையறுக்கலாம், அங்கு பெற்றோர் பணிகள் தானாகவே அவற்றின் பிள்ளை பணிகளுக்காகக் காத்திருக்கும். இன்வாய்ஸ் உதாரணத்தில், finalize-order என்ற முதன்மைப் பணியை உருவாக்கி, அதற்கு reserve-inventory, charge-payment, மற்றும் generate-pdf ஆகிய மூன்று பிள்ளைப் பணிகளை உருவாக்கலாம். மின்னஞ்சல் அறிவிப்பை PDF பணியின் ஒரு பிள்ளைப் பணியாக மாற்றலாம். Redis இந்த வரைபட அமைப்பை (graph structure) சேமித்து வைக்கும். அனைத்துச் சார்புகளும் (dependencies) வெற்றியடைந்தால் மட்டுமே முதன்மைப் பணி செயல்படும். ஒரு பிள்ளைப் பணி தோல்வியடைந்தால், அந்த முழு கிளையும் நின்றுவிடும். நீங்கள் polling loops அல்லது recursive job spawners போன்றவற்றை எழுதத் தேவையில்லை. இது வெறும் குறியீட்டு வசதி (syntactic sugar) மட்டுமல்ல; இது உங்கள் வணிகத் தர்க்கத்தை (business logic) நீங்கள் வடிவமைக்கும் முறையையே மாற்றுகிறது.

விகிதக் கட்டுப்பாடு (Rate Limiting): ஒரு மழுங்கிய கருவி vs. ஒரு நுணுக்கமான அறுவை சிகிச்சை கருவி

இரண்டு நூலகங்களும் தரவுப் பரிமாற்ற வேகத்தைக் (throughput) குறைக்க முடியும், ஆனால் அவற்றின் நுணுக்கமான கட்டுப்பாடு (granularity) ஆகியவற்றில் மிகப்பெரிய வேறுபாடு உள்ளது.

Bull ஒவ்வொரு வரிசைக்கும் (queue) விகிதக் கட்டுப்பாடுகளை (rate limits) விதிக்கிறது. ஒரு வரிசையை ஒரு வினாடிக்கு நூறு வேலைகளைச் செய்யுமாறு அமைத்தால், அந்த உச்சவரம்பு வரிசையில் உள்ள அனைத்து வேலைகளுக்கும் சமமாகப் பொருந்தும். இது ஒரே மாதிரியான வேலைப்பளுவிற்கு (homogeneous workloads) சரியாக இருக்கும். ஆனால் மல்டிடென்ட் (multitenant) SaaS தளங்களில் இது தோல்வியடையும். ஒரு அதிகப்படியான சுமையை உருவாக்கும் வாடிக்கையாளர் (noisy customer) ஒரு பொதுவான வரிசையில் ஒரு மில்லியன் webhook விநியோகங்களை கொட்டுகிறார் என்று கற்பனை செய்து பாருங்கள். Bull-ன் வரிசை அளவிலான கட்டுப்பாடு என்பது, மற்ற அனைவரையும் மெதுவாக்காமல் அந்த ஒரு வாடிக்கையாளரை மட்டும் மெதுவாக்க முடியாது என்பதைக் குறிக்கிறது. உங்கள் விருப்பங்கள் கடினமானவை. ஒவ்வொரு வாடிக்கையாளருக்கும் தனித்தனி Redis வரிசைகளை உருவாக்கி அவற்றை இயக்கவியல் முறையில் (dynamically) நிர்வகிக்க வேண்டும், அல்லது இந்த அநீதியை ஏற்றுக்கொண்டு போக வேண்டும்.

BullMQ குழு அடிப்படையிலான விகிதக் கட்டுப்பாட்டைச் (group-based rate limiting) சேர்க்கிறது. ஒவ்வொரு வேலையையும் ஒரு குழுத் திறவுகோலுடன் (group key - பொதுவாக ஒரு வாடிக்கையாளர் அல்லது பயனர் ID), ஒவ்வொரு குழுவிற்கும் கட்டுப்பாடுகளை வரையறுக்கலாம். ஒரே வரிசை அனைத்து வாடிக்கையாளர்களுக்கும் வேலைகளைச் செய்யும், ஆனால் scheduler ஒவ்வொரு குழுவையும் தனித்தனியாகக் கட்டுப்படுத்தும். வாடிக்கையாளர் A-விலிருந்து வரும் அதிகப்படியான வேலைகள், வாடிக்கையாளர் B-க்கு பணி இல்லா நிலையை (starvation) ஏற்படுத்தாது. நீங்கள் வரிசைப் பெருக்கத்தைத் (queue sprawl) தவிர்த்து, உங்கள் Redis keyspace-ஐ சுத்தமாக வைத்திருக்கலாம். 'noisy-neighbor' சிக்கல்கள் உள்ள தளங்களுக்கு, இது மட்டுமே இடமாற்றத்தை (migration) நியாயப்படுத்த போதுமானது.

நடைமுறையில் சுத்தமான கட்டமைப்பு (Cleaner Architecture)

ஒரு தயாரிப்புச் சிக்கலை (production incident) சரிசெய்யும் வரை, Queue மற்றும் Worker-ன் பிரிவினை நுட்பமானது. Bull-ல், கனமான செயலாக்கத் தேவைகளையும் (heavy processing dependencies) இறக்குமதி செய்யும் ரூட் ஹேண்ட்லர்களுக்குள் (route handlers) வேலை உருவாக்கும் குறியீட்டைப் பார்ப்பது இயல்பானது. BullMQ வேலை எங்கே நடக்க வேண்டும் என்பதை நீங்கள் தீர்மானிக்கத் தூண்டுகிறது. உங்கள் வெப் சர்வர்கள் லேசாக (lean) இருக்கும். உங்கள் worker கன்டெய்னர்கள் கனமான லைப்ரரிகள், இமேஜ் ப்ராசஸர்கள் அல்லது headless பிரவுசர்களைக் கொண்டிருக்கும். மெமரி லீக் (memory leak) ஏற்பட்டால், எந்த வகையான செயல்முறையை (process type) ஆய்வு செய்ய வேண்டும் என்பதை நீங்கள் துல்லியமாகத் தெரிந்து கொள்ளலாம். இதன் செயல்பாட்டு முறை Celery அல்லது Sidekiq போன்ற அமைப்புகளைப் போன்றது.

சரியானதைத் தேர்ந்தெடுத்தல்

நீங்கள் புதிய திட்டத்தைத் தொடங்குகிறீர்கள் என்றால் BullMQ-உடன் தொடங்குங்கள். TypeScript வரையறைகள் துல்லியமானவை மற்றும் முழுமையானவை. Job flows அதிகப்படியான orchestration குறியீடுகளைத் தவிர்க்க உதவுகிறது. குழு விகிதக் கட்டுப்பாடு, நியாயமற்ற சிக்கல்கள் தொடங்குவதற்கு முன்பே அவற்றைத் தீர்க்கிறது. async/await API இயல்பாகவே இருப்பது போன்ற உணவைத் தரும். ஒரு புதிய திட்டத்திற்கு (greenfield project) பழைய லைப்ரரியைத் தேர்ந்தெடுக்கக் காரணம் ஏதுமில்லை.

ஏற்கனவே Bull சிறப்பாகச் செயல்பட்டு வந்தால், அதிலேயே தொடருங்கள். இடமாற்றங்கள் நேரத்தை எடுத்துக்கொள்ளும் மற்றும் நிலைத்தன்மைக்கு (stability) ஆபத்தை ஏற்படுத்தலாம். உங்கள் வேலைகள் எளிமையானவை மற்றும் ஒன்றையொன்று சார்ந்து இல்லாதவை என்றால், உங்களுக்குத் தேவையான அம்சங்கள் இதில் இல்லை என்று அர்த்தமில்லை. கடவுச்சொல் மறுசீரமைப்பு மின்னஞ்சல்களை அனுப்பும் மற்றும் அவதாரங்களை மாற்றியமைக்கும் ஒரு வரிசைக்கு flow graphs தேவையில்லை. கோட்பாட்டு ரீதியான தூய்மைக்காக (theoretical purity) ஏற்கனவே இயங்கும் குறியீட்டை மீண்டும் எழுதுவது பொறியியல் அல்ல; அது ஒரு பொழுதுபோக்கு மட்டுமே.

இடமாற்றத்தின் யதார்த்தம் (Migration Reality Check)

நீங்கள் மாற முடிவு செய்தால், அதை ஒரு குறியீடு மாற்றமாக (code refactor) கருதாமல், ஒரு உள்கட்டமைப்பு மாற்றமாக (infrastructure change) கருதுங்கள். Bull மற்றும் BullMQ வெவ்வேறு Redis key schemas-களைப் பயன்படுத்துகின்றன. அவைகளால் ஒன்றையொன்று சார்ந்திருக்கும் வேலைத் தரவையோ அல்லது நிலையையோ (state) படிக்க முடியாது. ஒரு feature flag-ஐ மாற்றிவிட்டு, பழைய வேலைகள் தானாகவே முடிந்துவிடும் என்று நீங்கள் எதிர்பார்க்க முடியாது. நீங்கள் தற்போதுள்ள ஒவ்வொரு வரிசையையும் முழுமையாகத் தீர்த்த பிறகு (drain to zero), புதிய workers-களைப் பதிவேற்றி, BullMQ மூலம் வேலைகளைச் சேர்க்கத் தொடங்க வேண்டும். பராமரிப்பு நேரத்திற்காகவோ (maintenance window) அல்லது பழைய workers பழைய வரிசையையும், புதிய workers புதிய வரிசையையும் கையாளும் வகையில் ஒரு blue-green deployment-க்காகவோ திட்டமிடுங்கள்.

உண்மையான முடிவு