பெரும்பாலான Node.js பயிற்சிகள் பிழை கையாளுதலை (error handling) ஒரு இரண்டாம் நிலை விஷயமாகவே கருதுகின்றன. நீங்கள் ஒரு route handler-ஐ try/catch பிளாக்கிற்குள் வைத்து, stack trace-ஐ லாக் செய்து, 500 பிழையைத் திருப்பி அனுப்புகிறீர்கள். ஒரு HTTP கோரிக்கையின் மறுமுனையில் ஒரு உண்மையான நபர் காத்திருப்பதால் இத்தகைய மனநிலை நீடிக்கிறது. பின்னணி வேலைகள் (Background jobs) வேறுபட்டவை. ஒரு வரிசை அமைப்பில் (queue system), பதிலளிக்கத் துடிக்கும் வாடிக்கையாளர்களோ அல்லது தானியங்கி பிரவுசர் புதுப்பிப்புகளோ (browser refresh) இல்லை. அங்கே ஒரு worker, ஒரு payload மற்றும் அமைதியாக உயர்ந்து கொண்டே இருக்கும் ஒரு retry counter மட்டுமே உள்ளன. விஷயங்கள் தவறாக நடக்கும்போது, அவை மெதுவாகத் தொடங்கி, பிறகு அனைத்தும் ஒரே நேரத்தில் நடக்கும். தவறாக வகைப்படுத்தப்பட்ட ஒரு பிழை முழுமையான pipeline-ஐ முடக்கலாம் அல்லது அதிகாலை மூன்று மணிக்கு ஒரு பொறியாளரைத் தூக்கிக்கொண்டு வரலாம்.

இந்தத் தொடர்பின்மை எளிமையானது. Request-response சுழற்சிகள் விரைவாகவும் சத்தமாகவும் தோல்வியடையும். ஆனால் ஒரு வரிசைத் தோல்வி (queue failure) அமைதியானது. ஒரு தரவுத்தள இணைப்பு (database connection) துண்டிக்கப்படுவதற்கு முன்பு, ஒரு worker நூற்றுக்கணக்கான வேலைகளைச் செய்து முடிக்கலாம். தெளிவான கையாளுதல் விதிகள் இல்லையென்றால், worker உடனடியாக மீண்டும் முயற்சிக்கும், ஏற்கனவே போராடிக்கொண்டிருக்கும் தரவுத்தளத்தை மேலும் அழுத்தமடையச் செய்து, இறுதியில் செயலிழந்துவிடும் (crash). யாரும் worker-ஐ நேரடியாகக் கவனிப்பதில்லை என்பதால், சிக்கலின் முதல் அறிகுறி பெரும்பாலும் ஒரு தொடர்ச்சியான தேக்கம் (cascading backup) அல்லது லாக் கோப்புகளால் (log files) நிரம்பிய வட்டு (disk) ஆகும். உங்களுக்கு catch பிளாக்குகளை விட மேலான ஒன்று தேவை. வெவ்வேறு தோல்விகளை வெவ்வேறு விதமாக கையாளும் மற்றும் ஒரு மோசமான வேலையிலிருந்து மற்ற அமைப்பைப் பாதுகாக்கும் ஒரு உத்தி உங்களுக்குத் தேவை.

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

ஒவ்வொரு பிழையையும் இரண்டு வகைகளாகப் பிரிப்பதன் மூலம் தொடங்குங்கள்.

மீண்டும் முயற்சி செய்யக்கூடிய பிழைகள் (Retryable errors) தற்காலிகமானவை. ஒரு மூன்றாம் தரப்பு API-க்கான நெட்வொர்க் timeout, ஒரு 429 rate-limit பதில் அல்லது அதன் முதன்மைத் தரவுத்தளத்தைத் தொடர்ந்து செல்லாமல் போகும் தற்காலிக தரவுத்தள பிரதி (database replica). இவை அழுத்தத்தின் அறிகுறிகளே தவிர, பிழைகள் (bugs) அல்ல. அமைப்பு முப்பது வினாடிகளில் தானாகவே சரியாகிவிடக்கூடும். மீண்டும் முயற்சி செய்யக்கூடிய வேலைகளுக்கு மற்றொரு வாய்ப்பு அளிக்கப்படலாம், ஆனால் கட்டுப்படுத்தப்பட்ட சூழலில் மட்டுமே.

நிரந்தர பிழைகள் (Permanent errors) தவறுகளாகும். payload-ல் உள்ள தவறான JSON, விடுபட்ட user ID, அல்லது சேமிப்பகத்தில் இல்லாத ஒரு தேவையான கோப்பு. இவை முதல் முயற்சியில் தோல்வியடைந்த அதேபோல நூறாவது முயற்சியிலும் தோல்வியடையும். அவற்றை மீண்டும் முயற்சிப்பது CPU சுழற்சிகளைச் செலவழிக்கும், வரிசை இடங்களை வீணடிக்கும் மற்றும் ஆரோக்கியமான வேலைகளைத் தாமதப்படுத்தும் நச்சுத்தன்மை வாய்ந்த பின் அழுத்தத்தை (back-pressure) உருவாக்கும். ஒரு நிரந்தரத் தோல்விக்கு பயனுள்ள ஒரே இடம் ஒரு log, ஒரு alert அல்லது ஒரு dead-letter queue மட்டுமே. அது retry loop-ல் இருக்கக்கூடாது.

ஒரு முடிவெடுக்கும் இயந்திரத்தை உருவாக்குங்கள் (Decision Engine)

உடனடியாக வகைப்படுத்துங்கள். இந்த முடிவை queue framework-இடம் விட்டுவிடாதீர்கள். ஒரு பிழையைப் பிடித்தவுடன், அதன் விதியைத் தீர்மானியுங்கள்.

நடைமுறையில், பிழையை மேலே கொண்டு செல்லும் முன் (bubbling up), அந்தத் தோல்வியைப் பரிசோதிக்கும் தனிப்பயனாக்கப்பட்ட error classes அல்லது wrapper functions-களை உருவாக்குவதைக் குறிக்கிறது. ஒரு database driver 'connection reset' பிழையைத் தந்தால், உங்கள் handler அதை 'retryable' என்று குறிக்க வேண்டும். ஒரு payload validator 'schema mismatch' பிழையைத் தந்தால், அதை 'permanent' என்று குறிக்க வேண்டும். பல job processors இயல்பாகவே அனைத்தையும் மீண்டும் முயற்சிக்கும், இது நீங்கள் எடுக்கும் மிகவும் செலவு மிகுந்த முடிவாகும். நிரந்தர வேலைகளை உடனடியாக நிராகரியுங்கள். அவற்றை நீக்கிவிடலாம் அல்லது அவை முக்கிய pipeline-ஐ பாதிக்காதவாறு ஒரு dead-letter queue-க்கு மாற்றலாம். இந்த ஒரு பழக்கம் எந்தவொரு உள்கட்டமைப்பு மாற்றத்தையும் விட பனிப்பந்து விளைவுகளை (snowball effects) மிகவும் நம்பகமான முறையில் தடுக்கிறது.

பின்வாங்குங்கள், ஆனால் புத்திசாலித்தனமாக

நீங்கள் மீண்டும் முயற்சிக்கும்போது, அதை ஒருபோதும் உடனடியாகச் செய்யாதீர்கள். ஒரு தரவுத்தளம் முடங்கியிருந்தால், ஒவ்வொரு வினாடியும் வேலை செய்யும் workers அதைத் தாக்குவது உள்ளிருந்து நடக்கும் denial-of-service தாக்குதல் போலத் தோன்றும். exponential backoff முறையைப் பயன்படுத்துங்கள். ஒரு நிமிடம் காத்திருங்கள், பிறகு ஐந்து, பிறகு பதினைந்து. மேல்நிலை அமைப்பு (upstream system) மீண்டு வர அவற்றுக்கு இடம் கொடுங்கள்.

ஆனால் exponential backoff மட்டும் போதுமானதல்ல. ஒரு சேவை மறுதொடக்கம் செய்யப்பட்டதால் ஆயிரம் வேலைகள் ஒரே நேரத்தில் தோல்வியடைந்தால், அவற்றின் retry கால அட்டவணைகள் ஒன்றாக அமையும். அந்தச் சேவை மீண்டும் ஆன்லைனுக்கு வரும்போது, அவை அனைத்தும் ஒரே நேரத்தில் அந்தச் சேவையைத் தாக்கும், இது மீண்டும் அதை முடக்கிவிடக்கூடும். jitter-ஐச் சேர்க்கவும்: ஒவ்வொரு தாமதத்திற்கும் ஒரு சிறிய சீரற்ற மாற்றத்தைச் (random offset) சேர்க்கவும். சில வினாடிகளுக்குப் பரப்பப்பட்ட மறுமுயற்சிகள் ஒருங்கிணைந்த கூட்ட நெரிசல்களைத் (synchronized stampedes) தடுக்கின்றன. கணிதம் எளிமையானது, ஆனால் அது வழங்கும் நிலைத்தன்மை மிகப்பெரியது.

ஆதாரங்களைப் பாதுகாக்கவும்

ஒரு dead-letter queue என்பது உங்கள் audit trail, குப்பைத் தொட்டி அல்ல. ஒரு வேலை அதன் இறுதி மறுமுயற்சியையும் முடித்துவிட்டால், அதை அப்படியே நீக்கிவிடாதீர்கள். முழு payload-ஐயும், பிழை சூழல் (error context) மற்றும் மறுமுயற்சி வரலாறு (retry history) ஆகியவற்றுடன் ஒரு DLQ-க்கு மாற்றவும்.

இது ஆதாரங்களைப் பாதுகாக்கிறது. ஒரு மனிதன் அந்த வேலையை ஆய்வு செய்து, பிழையைச் சரிசெய்து, தேவைப்பட்டால் அதைத் தானாகவே மீண்டும் இயக்க முடியும். மிக முக்கியமாக, உங்கள் DLQ-ன் ஆழத்தைக் கண்காணிக்கவும். dead-lettered வேலைகளில் ஏற்படும் திடீர் அதிகரிப்பு என்பது பெரும்பாலும் ஒரு தவறான deploy, தவறான schema மாற்றம் அல்லது ஒரு வெளிப்படையான விற்பனையாளர் ஒப்பந்தத்தை மீறுவதன் ஆரம்ப எச்சரிக்கையாகும். DLQ வளர்ச்சியைக் ஒரு trailing indicator ஆகக் கருதாமல், ஒரு leading indicator ஆகக் கருதுங்கள். உங்கள் DLQ நிரம்பிக்கொண்டிருந்தால், மேல்நிலையில் ஏதோ மாறியுள்ளது என்று அர்த்தம், அது பின்னடைவாகப் பரவுவதற்கு முன்பே உங்கள் குழு அதைத் தெரிந்து கொள்ள வேண்டும்.

மறுமுயற்சிக்காக வடிவமைக்கவும்

ஒவ்வொரு வேலையையும் அது இரண்டு முறை இயங்கும் என்பது போல வடிவமைக்கவும், ஏனெனில் அது நடக்கக்கூடும். ஒரு Worker செயலாக்கத்தின் பாதியில் தோல்வியடையலாம், மீண்டும் திட்டமிடப்படலாம் மற்றும் மீண்டும் இயக்கப்படலாம். உங்கள் வேலை ஒரு வாடிக்கையாளரிடம் பணம் வசூலித்தால், மின்னஞ்சல் அனுப்பினால் அல்லது இருப்பைக் (inventory) கூட்டினால், ஒரு சாதாரண மறுமுயற்சி (retry) நகல்களை உருவாக்கும்.

இதற்கான தீர்வு idempotency ஆகும். ஒரு side effect-ஐச் செய்வதற்கு முன், அது ஏற்கனவே நடந்துவிட்டதா என்று சரிபார்க்கவும். Job payload-லிருந்து ஒரு தனித்துவமான அடையாளங்காட்டியைப் (unique identifier) idempotency key ஆகப் பயன்படுத்தவும். அந்த key-ஐ ஒரு குறுகிய கால cache அல்லது uniqueness constraint கொண்ட ஒரு database table-இல் சேமிக்கவும். அந்த key ஏற்கனவே இருந்தால், வேலையைத் தவிர்த்துவிட்டு வெற்றியைத் தெரிவிக்கவும். இது மறுமுயற்சிகளையும் (retries) ஒரு ஆபத்திலிருந்து பாதிப்பற்ற no-op ஆக மாற்றுகிறது. இதற்குச் சில கூடுதல் வரிகள் தேவைப்படலாம், ஆனால் ஒரே இரவில் வருவாய் ஏன் இரட்டிப்பானது என்று நிதித் துறைக்கு விளக்கம் அளிக்க வேண்டிய அவசியத்திலிருந்து இது உங்களைக் காப்பாற்றும்.

செயல்முறையைப் பாதுகாக்கவும்

Unbounded promise rejections மற்றும் stray exceptions ஆகியவை எச்சரிக்கையின்றி ஒரு Node.js process-ஐ முடக்கிவிடக்கூடும். ஒரு Worker-இல், இது வேலைகள் விடுபடுவதையும், ஒரு orchestrator container-ஐ மீண்டும் தொடங்கத் திணறுவதையும் குறிக்கும்.

unhandledRejection மற்றும் uncaughtException ஆகியவற்றிற்கான global handlers-களைப் பதிவு செய்யவும். அவற்றின் வேலை பயன்பாட்டை (application) மீட்பது அல்ல. தேவையான குறைந்தபட்ச cleanup-ஐச் செய்துவிட்டு வெளியேறுவதே அவற்றின் வேலை. Docker, Kubernetes அல்லது systemd ஆகியவை சுத்தமான memory state-உடன் Worker-ஐ மீண்டும் தொடங்க அனுமதிக்கவும். ஒரு global handler இயங்கிய பிறகு, பாதிப்படைந்த நிலையில் தொடர்ந்து இயங்குவது memory leaks மற்றும் corrupted state போன்ற சிக்கல்களை உருவாக்கும். தவறான முறையில் வேலைகளைச் செய்யும் ஒரு மெதுவான zombie process-ஐ விட, வேகமான மற்றும் சுத்தமான முடிவு (clean death) பாதுகாப்பானது. உங்களை மீண்டும் கொண்டு வர உங்கள் orchestrator-ஐ நம்புங்கள்; சிதைந்த runtime-ஐத் தந்திரமாக கையாள முயற்சிக்காதீர்கள்.

சிக்னல்களை மதியுங்கள்

Deployments, scaling events மற்றும் node rotations ஆகியவற்றின் போது Workers நிறுத்தப்படும். உங்கள் process SIGTERM-ஐப் பெற்ற அடுத்த கணமே நின்றுவிட்டால், தற்போது நடந்து கொண்டிருக்கும் வேலையை நீங்கள் பாதியிலேயே நிறுத்திவிடுகிறீர்கள். அந்த வேலை ஒருபோதும் முடிவடையாமல் போகலாம், மேலும் அதன் retry counter கூட இன்னும் அதிகரிக்காமல் இருக்கலாம்.

SIGTERM மற்றும் SIGINT ஆகியவற்றைக் கவனியுங்கள். ஒரு சிக்னல் வரும்போது, queue-விலிருந்து புதிய வேலைகளை எடுப்பதைத் தவிர்க்கவும். முடிந்தால் தற்போதைய வேலையை முடிக்கவும். ஒரு குறிப்பிட்ட காலக்கெடுவை (உதாரணமாக முப்பது வினாடிகள்) நிர்ணயிக்கவும், அதன் பிறகு எதுவாக இருந்தாலும் வெளியேறவும். இந்த graceful shutdown, queue-வை மதிக்கிறது மற்றும் தவறான தோல்விகளைத் தவிர்க்கிறது. சுத்தமாக வெளியேறும் ஒரு Worker-ஐ உங்கள் deployment pipeline ஆரோக்கியமானதாகக் கருத வேண்டும், ஆனால் செயலிழந்த (crashed) Worker ஒரு எச்சரிக்கையை (alert) உருவாக்க வேண்டும்.

உண்மையான கருத்து

நம்பகமான queue handling என்பது ஒவ்வொரு பிழையையும் பிடிப்பது பற்றியது அல்ல. அது ஒவ்வொரு failure mode-க்கும் திட்டமிட்ட முடிவுகளை எடுப்பதைப் பற்றியது. தற்காலிகமான (transient) பிழைகளை பொறுமையுடன் மறுமுயற்சி செய்யவும். நிரந்தரமான (permanent) பிழைகளை விரைவாகக் கையாளவும். உங்கள் Workers-களை stampedes-லிருந்து பாதுகாக்கவும், idempotency keys மூலம் உங்கள் தரவைப் பாதுகாக்கவும், மற்றும் செயலிழக்கும் செயல்முறைகளை (dying processes) சுத்தமாக வெளியேற விடவும். ஒவ்வொரு தோல்விக்கும் ஒரு வரையறுக்கப்பட்ட பாதை இருக்கும்போது, அதிகாலை மூன்று மணி என்பது வெறும் மற்றொரு மணிநேரமாக மட்டுமே இருக்கும். உங்கள் pipeline தொடர்ந்து இயங்கும், உங்கள் குழுவினர் நிம்மதியாகத் தூங்குவார்கள்.