நீங்கள் Node.js கொண்டு உருவாக்கும்போது, பிழை கையாளுதல் (error handling) ஆரம்பத்தில் மிகவும் எளிதாகத் தோன்றும். ஒரு ரூட்டை (route) try-catch-க்குள் சுற்றினால், ஒரு 500 status code-ஐ அனுப்பினால் போதும், அடுத்ததாக என்ன செய்ய வேண்டும் என்பதை கிளையண்ட் முடிவு செய்துவிடும். அந்த மாதிரி முறை HTTP-க்கு நன்றாக வேலை செய்யும், ஆனால் நீங்கள் பின்னணிப் பணிகளுக்கு (background jobs) மாறும்போது அது முறியடிக்கப்படுகிறது. ஒரு வரிசை அமைப்பில் (queue system), காத்திருக்கும் கிளையண்ட் யாரும் இல்லை. அங்கு ஒரு பணியாளர் (worker), ஒரு தரவுப் பகுதி (payload) மற்றும் Redis, RabbitMQ அல்லது SQS-இல் எங்கோ உயர்ந்து கொண்டே இருக்கும் ஒரு மறுமுயற்சி எண்ணி (retry counter) மட்டுமே இருக்கும். நீங்கள் தோல்வியடைந்த இணையக் கோரிக்கைகளை (web requests) கையாளும் அதே முறையைத் தோல்விகளுக்கும் பயன்படுத்தினால், ஒரு பரிவர்த்தனையை மட்டும் இழக்க மாட்டீர்கள். உங்கள் முழுத் தரவுப் பாதையையும் (pipeline) முடக்கிவிடலாம், கணினித் திறனை (compute) வீணடிக்கலாம் அல்லது அதே நச்சுத்தன்மை வாய்ந்த செய்தியின் (poisoned message) காரணமாக உங்கள் பணியாளர்களைத் திரும்பத் திரும்ப முடக்கிவிடலாம்.

HTTP மனநிலை பின்னணிப் பணிகளில் (background jobs) முறியடிக்கப்படுகிறது

ஒரு கோரிக்கை-பதில் சுழற்சியில் (request-response cycle), பின்னூட்டம் உடனடியாகக் கிடைக்கும். ஒரு பயனர் ஒரு பொத்தானைக் கிளிக் செய்கிறார், சர்வர் ஒரு பிழையைத் தூண்டுகிறது, மேலும் பயனர் ஒரு தோல்வித் திரையைப் பார்க்கிறார். சுத்தம் செய்வது எளிது. ஒரு வரிசைப் பணியாளர் (queue worker) தனிமைப்படுத்தப்பட்ட நிலையில் இயங்குகிறார். அவர் ஒரு பணியை எடுத்துக்கொண்டு, சில வினாடிகள் அல்லது நிமிடங்கள் அதில் வேலை செய்து, பின்னர் வெற்றியை உறுதிப்படுத்துகிறார். இடையில் ஏதேனும் தவறாக நடந்தால், ஏன் நடந்தது என்று வரிசை அமைப்புக்குத் தெரியாது. உறுதிப்படுத்தல் (acknowledgment) வரவில்லை என்பது மட்டுமே அதற்குத் தெரியும். உங்கள் அமைப்பைப் பொறுத்து, அது மீண்டும் மீண்டும் முயற்சிக்கும், ஒருவேளை அது முடிவில்லாமல் கூட இருக்கலாம். ஒரே ஒரு தவறான தரவுப் பகுதி (malformed payload), நூற்றுக்கணக்கான முறை பணியாளர்களுக்கு இடையே சுழன்று கொண்டே இருக்கும், இது CPU-வை வீணாக்குவதோடு, உண்மையில் செயலாக்கப்பட வேண்டிய முறையான பணிகளுக்குப் பின்னால் மறைந்துவிடும்.

இரண்டு வகையான தோல்விகள்

மீள்திறன் கொண்ட வரிசைகளின் (resilient queues) முதல் விதி என்னவென்றால், ஒவ்வொரு பிழையையும் ஒரே மாதிரியாகக் கையாளுவதை நிறுத்துவதாகும். பிழைகள் ஏற்பட்ட அடுத்த கணமே அவற்றை இரண்டு குழுக்களாகப் பிரிக்க வேண்டும்.

மீண்டும் முயற்சி செய்யக்கூடிய தோல்விகள் (Retryable failures) தற்காலிகமானவை. நெட்வொர்க் காலாவதி (network timeouts), மூன்றாம் தரப்பு API-லிருந்து வரும் விகிதக் கட்டுப்பாடுகள் (rate limits), அல்லது தரவுத்தள இணைப்பு (database connection) தற்காலிகமாகத் துண்டிக்கப்படுவது போன்றவற்றை நினைவில் கொள்ளுங்கள். இவை அழுத்தத்தில் இருக்கும் ஒரு இயங்கும் அமைப்பின் அறிகுறிகள். இவை இன்னும் இரண்டு நிமிடங்களில் அடுத்த முயற்சியில் வெற்றி பெறக்கூடும்.

நிரந்தரத் தோல்விகள் (Permanent failures) நச்சு மாத்திரைகள் (poison pills) போன்றவை. இதில் தவறான தரவுப் பகுதிகள் (malformed payloads), ஸ்கீமா சரிபார்ப்பு பிழைகள் (schema validation errors), அல்லது ஒரு முந்தைய சேவை (upstream service) அதன் ஒப்பந்தத்தை மாற்றியதால் ஒரு தேவையானத் தரவு விடுபடுவது போன்றவை அடங்கும். இவற்றை மீண்டும் முயற்சிப்பது முற்றிலும் வீணானது. நூறாவது முயற்சியிலும் இவை அதேபோலத் தோல்வியடையும்.

உங்கள் catch block-ஆல் இந்த இரண்டிற்கும் இடையிலான வித்தியாசத்தைக் கண்டறிய முடியாவிட்டால், உங்கள் வரிசை (queue) திசை தெரியாமல் பயணிக்கும்.

முறை 1: Catch Block-இல் பிழைகளை வகைப்படுத்துங்கள்

உங்கள் பணியாளரின் (worker) catch block கோப்பில் மிகவும் கவனமாக எழுதப்பட்ட குறியீடாக இருக்க வேண்டும். ஒரு பிழை எழும்போது, அதை உடனடியாக ஆய்வு செய்யுங்கள். பிழை குறியீடு ECONNRESET அல்லது timeout-ஆ? அதை மீண்டும் முயற்சி செய்ய வரிசையில் வையுங்கள். அது ஒரு SyntaxError, Joi சரிபார்ப்பு நிராகரிப்பு அல்லது ஒரு விடுபட்ட foreign key constraint-ஆ? அதை நேரடியாக ஒரு dead-letter queue அல்லது DLQ-க்கு மாற்றிவிடுங்கள், மேலும் அதை உங்கள் மறுமுயற்சி வரம்பிற்கு (retry limit) கணக்கில் எடுத்துக்கொள்ளாதீர்கள்.

BullMQ மற்றும் Bee Queue உட்பட பெரும்பாலான Node.js வரிசை நூலகங்கள் (queue libraries), தனிப்பயனாக்கப்பட்ட backoff உத்திகள் மற்றும் error hooks-களை வரையறுக்க அனுமதிக்கின்றன. அவற்றைப் பயன்படுத்துங்கள். ஒரு நிரந்தரப் பிழை இயல்பாகவே தூங்கிவிட்டு மூன்று முறை மீண்டும் முயற்சி செய்யக்கூடாது. உங்கள் மற்ற பணிகள் தடையின்றிச் செல்ல, அது முக்கிய வரிசையிலிருந்து வெளியேற்றப்பட வேண்டும். DLQ துல்லியமான தரவுப் பகுதியையும் (payload) பிழைச் சூழலையும் (error context) பாதுகாக்கிறது, இது நீங்கள் பிழையைச் சரிசெய்த பிறகு அல்லது ஸ்கீமாவை மாற்றிய பிறகு பணியை மீண்டும் இயக்க அனுமதிக்கிறது.

முறை 2: Jitter உடன் கூடிய Exponential Backoff

உடனடியாக மீண்டும் முயற்சிப்பது மிகவும் தீவிரமானது. ஒரு கீழ்நிலைத் தரவுத்தளம் (downstream database) ஏற்கனவே அதிக சுமையால் திணறிக்கொண்டிருந்தால், ஐம்பது பணியாளர்களிடமிருந்து ஒவ்வொரு இரண்டு வினாடிக்கும் மீண்டும் அதைத் தாக்குவது அதை முழுமையாக முடக்கிவிடும். நீங்கள் சற்று விலகி நின்று, அமைப்பு மீண்டு வர வழிவிட வேண்டும்.

Exponential backoff முறையைப் பயன்படுத்துங்கள். முதல் தோல்வியில், ஒரு வினாடி காத்திருக்கவும். இரண்டாவது தோல்வியில், இரண்டு வினாடிகள் காத்திருக்கவும். பிறகு நான்கு, பிறகு எட்டு, ஐந்து நிமிடங்கள் போன்ற ஒரு நியாயமான உச்ச வரம்பு வரை காத்திருக்கவும். ஆனால் கால அளவு மட்டுமே போதுமானதல்ல. ஒவ்வொரு தோல்வியடைந்த பணியும் துல்லியமாக ஒரே கால இடைவெளியைப் பயன்படுத்தினால், backoff காலாவதியாகும் போது அவை அனைத்தும் மோதிக் கொள்ளும். சில நேரங்களில் 'thundering herd' என்று அழைக்கப்படும் அந்த ஒருங்கிணைந்த அலை, மீண்டு வரும் ஒரு சேவையைத் திணறடிக்கக்கூடும்.

Jitter-ஐச் சேர்க்கவும். நீங்கள் கணக்கிட்ட கால தாமதத்தை ஒரு சீரற்ற சதவீதத்தால் (random percentage), ஒருவேளை பத்து முதல் இருபது சதவீதம் வரை மாற்றியமைக்கவும். நான்கு வினாடிகள் என்பது 4.2 அல்லது 4.7 ஆக மாறும். இந்த எளிய சீரற்ற தன்மை, மறுமுயற்சி அதிகரிப்பை (retry spike) பரவலாக்கி, உங்கள் உள்கட்டமைப்பு அலைகளாகத் தாக்கப்படுவதைத் தடுக்கிறது.

முறை 3: Idempotency-க்காக வடிவமைக்கவும்

இங்குதான் வரிசை உள்கட்டமைப்பும் (queue infrastructure) வணிகத் தர்க்கமும் (business logic) சந்திக்கின்றன. ஒரு வாடிக்கையாளரின் கட்டணத்தைச் செலுத்தும் பணியை கற்பனை செய்து பாருங்கள். பணியாளர் வெற்றிகரமாகக் கட்டணத்தைச் செலுத்துகிறார், ஆனால் உங்கள் தரவுத்தளத்தில் வெற்றியைப் பதிவு செய்வதற்கு அல்லது பணியை உறுதிப்படுத்துவதற்கு முன்பே இணைப்பு துண்டிக்கப்படுகிறது. வரிசை அமைப்பு ஒரு தோல்வியைக் காணும். அது மீண்டும் முயற்சிக்கும். வாடிக்கையாளரிடம் இருந்து இரண்டு முறை கட்டணம் வசூலிக்கப்படும்.

Node.js-இல், ஒவ்வொரு side effect-ஐயும் idempotent-ஆக மாற்றுவதன் மூலம் இதைத் தடுக்கலாம். Job ID அல்லது வணிகம் சார்ந்த ஒரு அடையாளத்திலிருந்து (business-specific identifier) ஒரு idempotency key-ஐ உருவாக்கவும். கட்டணத்தை உருவாக்குவதற்கு முன்னரோ, மின்னஞ்சல் அனுப்புவதற்கு முன்னரோ அல்லது இருப்பை (inventory) சரிசெய்வதற்கு முன்னரோ, அந்த வேலை ஏற்கனவே செய்யப்பட்டதா என்று சரிபார்க்கவும். அந்த key-ஐ உங்கள் database மற்றும் அதை ஏற்கும் மூன்றாம் தரப்பு (third-party) API-கள் வரை கொண்டு செல்லவும். ஒரே payload-ஐ பத்து முறை இயக்குவது, ஒருமுறை இயக்குவதற்கு இணையான முடிவைத் தரும் வகையில் உங்கள் jobs-களை வடிவமைக்கவும். இந்த ஒரு பழக்கம் நிதி மற்றும் தரவு ஒருமைப்பாட்டு (data-integrity) தொடர்பான பிழைகளின் ஒரு முழு வகையையும் நீக்கிவிடும்.

Pattern 4: உங்கள் Dead-Letter Queue-வை ஒரு Dashboard போலக் கருதுங்கள்

ஒரு DLQ என்பது தவறான jobs மறக்கப்படுவதற்கான ஒரு மயானம் அல்ல. இது ஒரு செயல்பாட்டு கருவி (operational tool), மேலும் இது நீங்கள் மிகவும் உன்னிப்பாகக் கவனிக்கும் தளங்களில் ஒன்றாக இருக்க வேண்டும்.

DLQ-இன் ஆழம் (depth) அதிகரிக்கும் போது எச்சரிக்கை (alerts) விடுக்கப்படும் வகையில் அமைத்திடுங்கள். DLQ-இல் ஒரு செய்தி இருந்தாலும் கூட, உங்கள் validation logic உடைந்துவிட்டதோ, upstream schema மாறியுள்ளதோ அல்லது downstream service நீங்கள் அடையாளம் காண முடியாத தரவுகளை (garbage) அனுப்புகிறதோ என்பதைக் குறிக்கலாம். வாடிக்கையாளர்கள் புகார் செய்யத் தொடங்குவதற்கு முன்பே நீங்கள் கண்டறிய வேண்டிய சிக்னல்கள் இவைதான். raw payload, stack trace மற்றும் timestamp ஆகியவற்றை ஆய்வு செய்ய உதவும் ஒரு dashboard-ஐ உருவாக்குங்கள். ஒரு runbook-ஐத் தயாராக வைத்திருங்கள்: தோல்வியைப் பரிசோதிக்கவும், குறியீட்டை (code) சரிசெய்யவும் (patch), பின்னர் சரியான வரிசையில் செய்திகளை மீண்டும் இயக்கவும் (replay). உங்கள் queue implementation ஆதரித்தால், எண்ணிக்கையைத் தவிர அதன் வளர்ச்சி விகிதத்தையும் (growth rate) கவனித்து எச்சரிக்கை செய்யவும், ஏனெனில் ஒரு தவறான deploy சில நிமிடங்களிலேயே DLQ-ஐ நிரப்பக்கூடும்.

Pattern 5: சந்தேகம் வரும்போது, Process-ஐ முறித்துவிடுங்கள் (Crash)

Node.js ஒரு V8 isolate-க்குள் இருக்கும் ஒற்றை event loop-இல் இயங்குகிறது. கையாளப்படாத promise rejection அல்லது ஒரு...