ஒரு லோடிங் ஸ்பின்னர் உங்களுக்கு எதையும் சொல்லாது. ஒரு AI பணி பல நிமிடங்கள் நீடித்தாலோ அல்லது மூன்றாவது முறை மீண்டும் வரிசையில் (queue) திரும்பினாலோ, அதன் நிலையை (state) நீங்கள் பார்க்க வேண்டியிருக்கும். WebSockets-இன் ஹேண்ட்ஷேக் ஓவர்ஹெட் (handshake overhead) அல்லது long polling-இன் சிக்கல்கள் இன்றி, Server-Sent Events உங்களுக்கு அந்தத் தெளிவைத் தருகிறது. சர்வர் ஒரு ஒற்றை HTTP பதிலைத் திறந்து வைத்துக்கொண்டு, மாற்றங்கள் நிகழும்போது பிளைன்-டெக்ஸ்ட் (plain-text) அப்டேட்களைத் தள்ளுகிறது (pushes). கிளையண்ட் அவற்றை வந்தடைந்தவுடன் வாசிக்கிறது.
இணைப்பு துண்டிக்கப்பட்டால், நீங்கள் மீண்டும் ஆரம்பத்திலிருந்து தொடங்க விரும்ப மாட்டீர்கள். நன்கு கட்டமைக்கப்பட்ட ஒரு SSE ஸ்ட்ரீம் நீங்கள் எங்கு இருந்தீர்கள் என்பதை நினைவில் வைத்திருக்கும். Node.js 20 மற்றும் அதன் ஸ்டாண்டர்ட் லைப்ரரியை (standard library) மட்டுமே பயன்படுத்தி இதைச் செய்ய முடியும். இதற்கு எந்த வெளிப்புற பேக்கேஜ்களும் (external packages) தேவையில்லை.
வயர் ஃபார்மேட் எப்படி இருக்கும்
ஒரு SSE செய்தி என்பது எளிய உரை (text). சர்வர் மூன்று விஷயங்களை எழுதுகிறது: ஒரு விருப்பத்தேர்வு நிகழ்வின் பெயர் (optional event name), ஒரு கட்டாய data புலம், மற்றும் உங்கள் சேமிப்புப் புள்ளியாக (save point) மாறும் ஒரு id புலம். ஒவ்வொரு பதிவும் இரண்டு புதிய வரி எழுத்துக்களுடன் (newline characters) முடிகிறது—இது ஒரு வெற்று வரியாக இருந்து எல்லையைக் குறிக்கிறது.
ஒரு ஆரோக்கியமான ஸ்ட்ரீம் வயரில் இவ்வாறு இருக்கலாம்:
id: 14
event: status
data: {"phase":"testing","progress":43}
id: 15
event: status
data: {"phase":"retrying","attempt":2}
பிரவுசரின் EventSource கிளையண்ட் இந்த வரிகளைத் தானாகவே வாசிக்கும். இது ஒவ்வொரு பிளாக்கிற்கும் ஒரு நிகழ்வை (event) உருவாக்கும் மற்றும் சமீபத்திய id-ஐ உள்நாட்டிலேயே சேமித்து வைக்கும். TCP இணைப்பு துண்டிக்கப்பட்டால், கிளையண்ட் காத்திருந்து, மீண்டும் இணைப்பை ஏற்படுத்தி, சேமிக்கப்பட்ட அடையாளத்தை Last-Event-ID ஹெடராக சர்வருக்கு அனுப்பும். இந்த ஹெடர் தான் இந்த முறையின் முழுமையான வெற்றிக்குக் காரணம். இது இல்லையென்றால், உங்களிடம் நிலையான கர்சர் (durable cursor) இருக்காது.
Node.js-இல் சர்வரை இணைத்தல்
Node-இன் உள்ளமைக்கப்பட்ட http மாட்யூல் இதை நேரடியாகக் கையாள முடியும். ஒரு கோரிக்கை (request) வரும்போது, இது ஒரு பக்கம் (page) அல்ல, ஒரு ஸ்ட்ரீம் என்பதை கிளையண்ட் அறிய சரியான ஹெடர்களை அமைக்கவும்:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
பஃபரிங்கை (buffering) நீக்கவும். ப்ராக்ஸிகள் (Proxies) மற்றும் ஃபிரேம்வொர்க்குகள் (frameworks) சில நேரங்களில் பதில்களைத் தொகுத்து (batch) அனுப்பும், இது நிகழ்நேர உணர்வைக் (real-time feel) கெடுக்கும், எனவே ஒவ்வொரு துண்டிற்குப் பிறகும் (chunk) ஃபிளஷ் (flush) செய்யவும்.
முதலில் ID-யையும், பிறகு நிகழ்வு வகையையும் (event type), பின்னர் பேலோட் தரவையும் (payload data), இறுதியில் முடிக்கும் வெற்று வரியையும் அனுப்பவும். வரிசை முறை முக்கியமானது, ஏனெனில் கிளையண்ட் அதைப் பிடிப்பதற்கு முன் ID வர வேண்டும். நீங்கள் நேட்டிவ் response.write() பயன்படுத்துகிறீர்கள் என்றால், வெளியீடு அப்படியே இவ்வாறு இருக்கும்:
response.write(`id: ${cursor}\n`);
response.write(`event: ${eventName}\n`);
response.write(`data: ${JSON.stringify(payload)}\n\n`);
அந்த இறுதியில் உள்ள \n\n வெறும் அலங்காரம் அல்ல. SSE பார்ஸர்கள் (parsers) அதை பதிவின் முடிவாகக் கருதுகின்றன. அதைத் தவறவிட்டால், கிளையண்ட் கூடுதல் தரவுக்காகக் காத்திருக்கும் நிலையில் அப்படியே நின்றுவிடும்.
கர்சர் தான் எல்லாவற்றையும் தீர்மானிக்கிறது
ஒரு புதிய HTTP இணைப்பு புதிய நிலையை (state) உறுதி செய்யாது. ஒரு கிளையண்ட் மீண்டும் இணைக்கும்போது, Last-Event-ID ஹெடர் அவர்கள் கடைசியாகப் பெற்ற செய்தியை உங்களுக்குத் தெரிவிக்கும். உங்கள் வேலை ஆரம்பத்திலிருந்து தொடங்குவதல்ல, அடுத்த செய்தியிலிருந்து தொடங்குவதாகும்.
இதன் பொருள் சர்வர் பக்கத்தில் நிகழ்வுகளின் வரிசைப்படுத்தப்பட்ட பதிவை (ordered log or journal) பராமரிப்பதாகும். ஒரு டெமோவிற்கு இன்-மெமரி அரே (in-memory array) போதுமானது. ஆனால் தயாரிப்பு நிலையில் (production), நீங்கள் நிலையான ஒன்றைப் பயன்படுத்த வேண்டும்—ஒரு டேட்டாபேஸ் லாக் (database log), Redis ஸ்ட்ரீம் அல்லது write-ahead journal ஆகியவற்றில் பதிவுகளைச் சேர்க்கவும்—ஏனெனில் சர்வர் மறுதொடக்கம் செய்யப்பட்டால் வரலாறு அழிந்துவிடக்கூடாது, மேலும் ஒவ்வொரு கிளையண்ட்டும் பூஜ்ஜியத்திலிருந்து தொடங்க வேண்டிய அவசியமும் இருக்கக்கூடாது.
உங்கள் நிகழ்வுகளை ஏறுவரிசையில் அதிகரிக்கும் ஒரு முழு எண் (monotonically increasing integer) அல்லது ULID மூலம் குறியீடாக்கம் (index) செய்யவும். மீண்டும் இணைப்பு வரும்போது, id > lastEventId என்று இருக்கும் நிகழ்வுகளுக்குக் கோரிக்கை விடுத்து, அவற்றை வரிசைப்படி மீண்டும் இயக்கவும் (replay). உங்களிடம் நூற்றுக்கணக்கான நிலுவையில் உள்ள செய்திகள் இருந்தால், சிறிய செயற்கை தாமதத்தை (artificial delay) அல்லது பேட்ச் முறையைப் பயன்படுத்தலாம், ஆனால் கிளையண்ட் காலவரிசைப்படி நிலையை மீண்டும் உருவாக்க ஏதுவாக, பழைய செய்திகளை முதலில் அனுப்பவும்.
நகல் செய்திகளை எதிர்பார்க்கவும்
நெட்வொர்க்குகள் எப்போதும் நம்பகமானவை அல்ல. சர்வர் ஒரு நிகழ்வை அனுப்பிய பிறகு, TCP ஒப்புதலை (acknowledgment) இழந்துவிட்டு, காலாவதியான பிறகு அதை மீண்டும் அனுப்பலாம். ஆரம்பத்திலிருந்தே "குறைந்தபட்சம் ஒரு முறை டெலிவரி" (at-least-once delivery) முறைக்கு ஏற்ப வடிவமைக்கவும்.
கிளையண்டில், நகல் நீக்கம் (deduplication) எளிதானது. நிகழ்வு ID-யைக் கொண்டு ஒரு Map-ஐ வைத்திருக்கவும். ஒரு புதிய நிகழ்வு வரும்போது, மேப்பைச் சரிபார்க்கவும். ID ஏற்கனவே இருந்தால், அந்த நகலை அமைதியாகத் தவிர்த்துவிடவும். உங்கள் சர்வர் தீர்மானிக்கப்பட்ட (deterministic) ID-களை வழங்குவதால், நகல்கள் எந்தப் பாதிப்பையும் ஏற்படுத்தாது. மேப் எப்போதும் வளர்ந்து கொண்டே இருக்க வேண்டிய அவசியமில்லை. ஒரு நிகழ்வு பாதுகாப்பாகச் செயலாக்கப்பட்டுவிட்டதை உறுதி செய்தவுடன், பழைய ID-களை நீக்கிவிடலாம். பிரவுசர் கிளையண்ட்களுக்கு சில நூறு என்ட்ரிகிகளைக் கொண்ட ஸ்லைடிங் விண்டோ (sliding window) போதுமானதாக இருக்கும்.
கர்சர் காலாவதியாகும் போது
காலப்போக்கில், ஒரு கிளையண்ட் பல மணிநேரம் அல்லது நாட்களுக்குப் பிறகு மீண்டும் இணைக்கப்படலாம். உங்கள் ஹிஸ்டரி பஃபர் (history buffer) கடைசி ஆயிரம் நிகழ்வுகளை மட்டுமே கொண்டிருந்தால் மற்றும் கிளையண்ட் இரண்டாயிரம் நிகழ்வுகள் பின்னால் இருந்தால், விடுபட்டவற்றை மீண்டும் இயக்குவது சாத்தியமற்றது.
பகுதியளவு வரலாற்றை (partial history) ஸ்ட்ரீம் செய்ய வேண்டாம். அது கிளையண்ட்டை ஒரு நிலையற்ற நிலையில் (inconsistent state) விட்டுவிடும். அதற்குப் பதிலாக, காலாவதியான கர்சரைத் (expired cursor) கண்டறிந்து, அடுத்த நிகழ்வாக ஒரு முழு ஸ்னாப்ஷாட்டை (full snapshot) அனுப்பவும். அந்த ஸ்னாப்ஷாட் தற்போதைய நிலைக்கு கிளையண்ட்டை இணைக்கும் ஒரு புதிய கர்சரைத் தாங்கியிருக்க வேண்டும். அங்கிருந்து, நேரடி டெல்டாக்கள் (live deltas) இயல்பாகத் தொடங்கும். கிளையண்ட் கோட் எப்போது தனது உள்ளூர் மாதிரியை (local model) ரீசெட் செய்ய வேண்டும் என்பதைத் தெரிந்துகொள்ள, உங்கள் புரோட்டோகாலில் (protocol) இந்த எல்லையைத் தெளிவாக ஆவணப்படுத்தவும்.
ஸ்ட்ரீமைப் பாதுகாக்கவும்
திறந்த நிலையில் உள்ள SSE எண்ட்பாயிண்ட்கள் (endpoints) ஈர்க்கக்கூடிய இலக்குகளாக இருக்கலாம். யாராலும் ஒரு இணைப்பைத் தக்கவைத்துக் கொள்ள முடியும், மேலும் ரீப்ளே கோரிக்கைகள் (replay requests) உங்கள் ஸ்டோரேஜில் உள்ள ரீட் லோடை (read load) அதிகரிக்கக்கூடும்.
முறையான அங்கீகாரத்துடன் (authorization) endpoint-ஐப் பாதுகாக்கவும். பிரவுசரின் EventSource தனிப்பயன் தலைப்புகளை (custom headers) ஆதரிக்காததால், டோக்கனை (token) query string-இல் அனுப்பவும் அல்லது கடுமையான SameSite கொள்கைகளைக் கொண்ட குக்கீகளைப் (cookies) பயன்படுத்தவும். ஸ்ட்ரீம் வளங்களை (stream resources) ஒதுக்குவதற்கு முன் டோக்கனைச் சரிபார்க்கவும்.
வரலாறு வரம்புகள் (history limits) மற்றும் பயனர் வாரியான ஒதுக்கீடுகளை (per-user quotas) அமைக்கவும். ஒரு பணிக்குச் சேமிக்கப்படும் நிகழ்வுகளின் (events) எண்ணிக்கையையும், ஒரு கிளையன்ட்டிற்கான ஒரே நேரத்தில் இணைக்கப்படும் இணைப்புகளின் (concurrent connections) எண்ணிக்கையையும் கட்டுப்படுத்தவும். துண்டிப்புகள் (disconnects) மற்றும் மறுபதிவுகளை (replays) பதிவு செய்யவும், இதன் மூலம் உங்கள் cursor endpoint-ஐத் தொடர்ந்து தாக்கும் ஒரு தவறான கிளையன்ட்டை நீங்கள் கண்டறிய முடியும்.
இந்த முறை எல்லா இடங்களிலும் பொருந்தும்
இந்த அணுகுமுறை HTTP-க்குள் மட்டும் முடங்கிவிடவில்லை. நீங்கள் WebSockets, message queues அல்லது agent-to-agent இடைமுகங்களுக்கு மாறும்போது இதே விதிகள் பொருந்தும். கடத்தும் முறை (transport) மாறலாம்—நீங்கள் binary frames அல்லது topic subscriptions-ஐப் பயன்படுத்தலாம்—ஆனால் அடிப்படைப் பிரச்சனை அப்படியேதான் இருக்கும். உங்களுக்கு ஒரு cursor, ஒரு நிலையான பதிவு (durable log), at-least-once semantics, கிளையன்ட் நகல் நீக்கம் (client deduplication) மற்றும் cursor காலாவதியாகும் போது முழுமையான ஸ்னாப்ஷாட்களுக்கு (full snapshots) மாறும் வசதி ஆகியவை தேவைப்படும். நிலை ஒருங்கிணைப்பை (state convergence) ஒருமுறை தீர்த்துவிட்டால், அதன் மையத் தர்க்கத்தை (core logic) மீண்டும் வடிவமைக்காமலேயே TCP, WebSocket அல்லது RabbitMQ போன்ற ஒரு புரோக்கர் வழியாக அதை அனுப்ப முடியும்.
எளிமையாக வைத்திருங்கள்
Server-Sent Events சாதாரண HTTP-இல் இயங்குவதால் சிறப்பாகச் செயல்படுகின்றன. Proxies அவற்றைப் புரிந்துகொள்கின்றன. Load balancers அவற்றின் ஆரோக்கியத்தைச் சரிபார்க்க (health-check) முடியும். curl மூலம் பிழைத்திருத்தம் (debugging) செய்வது மிகவும் எளிது. ஆனால் நீங்கள் விளிம்புநிலைச் சூழல்களை (edge cases) புறக்கணித்தால் அந்த எளிமை மறைந்துவிடும். cursor-ஐ உருவாக்குங்கள். மறுபதிப்புகளை (replays) எதிர்பாருங்கள். கிளையன்ட்டில் நகல்களை நீக்குங்கள் (deduplicate). வரலாறு முடிவடையும் போது ஸ்னாப்ஷாட் (snapshot) எடுங்கள். அவ்வாறு செய்தால், உங்கள் நீண்ட நேரம் இயங்கும் AI பணிகள், நிலையற்ற Wi-Fi, சர்வர் மறுதொடக்கம் மற்றும் அவ்வப்போது நிகழும் பிரவுசர் தூக்க நிலைகள் (browser sleep) ஆகியவற்றின் இடையிலும் தங்களின் முன்னேற்றத்தைத் துல்லியமாகத் தெரிவிக்கும்.
ஆதாரம்: Build a Reconnecting SSE Task Stream with Node.js
விவாதத்தில் இணையுங்கள்: GyaanSetu AI Community
