திரை விலக்கப்படும் வரை நிகழ்நேர ஒத்துழைப்பு (Real-time collaboration) மிக எளிதானது போலத் தோன்றும். ஒருவர் தட்டச்சு செய்கிறார். மற்றொருவர் மூன்று பத்திகள் மேலே உள்ள ஒரு வரியை நீக்குகிறார். மூன்றாவது நபர் Stack Overflow-லிருந்து ஒரு சிறு பகுதியை (snippet) நகலெடுத்து ஒட்டுகிறார். எப்படியோ அந்த ஆவணம் ஒரு ஒற்றை, சீரான நிலையை அடைகிறது. WebSockets அல்லது distributed state பற்றிய முன் அனுபவம் இல்லாமல், அந்தத் தடையற்ற தன்மையை ஆரம்பத்திலிருந்து உருவாக்குவது ஆபத்தானதாகத் தோன்றலாம். ஆனால், உண்மையாகக் கற்றுக்கொள்வதற்கு அதுவே சரியான வழி என்றும் தோன்றுகிறது.

இந்தத் திட்டம் பூஜ்ஜியத்திலிருந்து தொடங்குகிறது. கடன் வாங்கப்பட்ட boilerplate எதுவும் இல்லை. கடினமான பகுதிகளை முப்பது வினாடி தொகுப்பில் தவிர்த்துவிடும் மெருகூட்டப்பட்ட YouTube விளக்கங்களும் இல்லை. பல பயனர்கள் ஒரே கோப்பை ஒரே நேரத்தில் திருத்தக்கூடிய, ஒருவருக்கொருவர் மாற்றங்களையும்—மற்றும் ஒருவருக்கொருவர் கர்சர்களையும் (cursors)—நேரடியாகக் காணக்கூடிய ஒரு கூட்டுறவு குறியீட்டுத் தொகுப்பியை (collaborative code editor) உருவாக்குவதே இதன் இலக்கு. அங்கு செல்வதற்கு transport layers, consistency models மற்றும் ஆவணத்தை சிதைக்காமல் ஒரே நேரத்தில் செய்யப்படும் மாற்றங்களை (concurrent edits) இணைக்கும் சிக்கலான பிரச்சனையைத் தீர்ப்பது அவசியம்.

“Real-Time” என்பது உண்மையில் என்ன?

பெரும்பாலான இணைய பயன்பாடுகள் கோரிக்கை-பதில் (request-response) சுழற்சிகளைப் பின்பற்றுவதில் இயல்பானவை. நீங்கள் ஒரு படிவத்தைச் (form) சமர்ப்பிக்கிறீர்கள், சர்வர் அதைச் சேமிக்கிறது, நீங்கள் பக்கத்தைப் புதுப்பிக்கிறீர்கள். நிகழ்நேர ஒத்துழைப்பு அந்த ஒப்பந்தத்தை முற்றிலும் உடைக்கிறது. ஒவ்வொரு தட்டச்சுச் செயலும் (keystroke) ஒரு நிகழ்வாகும்; அது மற்ற அனைத்து இணைக்கப்பட்ட கிளையன்ட்களுக்கும் (clients), பொதுவாக மில்லி விநாடிகளில், அர்த்தம் மாறாத வரிசையில் சென்றடைய வேண்டும்.

WebSockets இங்கு தெளிவான transport தேர்வாகும், ஏனெனில் அவை கிளையன்ட் மற்றும் சர்வருக்கு இடையே ஒரு நிலையான, full-duplex இணைப்பைப் பராமரிக்கின்றன. ஒவ்வொரு சில விநாடிகளுக்கும் “ஏதாவது புதியது உள்ளதா?” என்று கேட்டு அலைவரிசையை (bandwidth) வீணடிக்கும் HTTP polling போலல்லாமல், ஒரு WebSocket திறந்தே இருக்கும். பயனர் A ஒரு அரைப்புள்ளி (semicolon) தட்டச்சு செய்யும் போது, அந்த எழுத்து ஒரு செய்தியாக மாறி சாக்ஸெட் (socket) வழியாக மையச் சர்வருக்குச் சென்று, பின்னர் பயனர் B மற்றும் C ஆகியோருக்கும் பரவுகிறது. அந்தப் பகுதி ஒப்பீட்டளவில் எளிதானது.

கடினமான பகுதி என்னவென்றால், B மற்றும் C ஆகிய இருவரும் ஒரே நேரத்தில் தட்டச்சு செய்யும் போது என்ன நடக்கும் என்பதுதான். இரண்டு மாற்றங்களும் கிட்டத்தட்ட ஒரே நேரத்தில் சர்வரைத் தாக்கினால், எது வெற்றி பெறும்? நீங்கள் செய்திகளை அவை வந்த வரிசையிலேயே ஒளிபரப்பினால், எழுத்துக்கள் விடுபடவோ அல்லது உரை குழப்பமடையவோ வாய்ப்புள்ளது. 'கடைசியாக எழுதியதுதான் வெல்லும்' (last-write-wins) போன்ற எளிய உத்திகள் தோல்வியடைகின்றன, ஏனெனில் அவை பயனரின் நோக்கத்தைப் புறக்கணிக்கின்றன. நான் முதல் வரியின் தொடக்கத்தில் “hello” என்று தட்டச்சு செய்யும் போது, நீங்கள் அதே வரியின் தொடக்கத்தில் “world” என்று தட்டச்சு செய்தால், முடிவு ஒரு மோதலாக மாறி ஒருவரை அழித்துவிடக் கூடாது. அது “helloworld” அல்லது “worldhello” என்று தீர்மானிக்கப்பட்ட முறையில் இருக்க வேண்டும். அதைச் சாத்தியமாக்க ஆவணத்தின் கட்டமைப்பைப் புரிந்துகொள்ளும் ஒரு synchronization strategy தேவை.

பூஜ்ஜியத்திலிருந்து தொடங்குவது ஏன் முக்கியம்?

இந்தச் சிக்கலை மறைக்கும் சிறந்த கட்டமைப்புகள் (frameworks) உள்ளன. Yjs, Automerge மற்றும் Socket.IO ஆகியவை இந்தச் சிரமங்களைத் தவிர்த்து, ஒரு மதியத்திற்குள் ஒரு வேலை செய்யும் முன்மாதிரியை (prototype) உருவாக்கிவிடும். ஆனால் அவற்றின் அடிப்படையிலுள்ள primitives-களைப் புரிந்து கொள்ளாமல் அவற்றைப் பயன்படுத்துவது, கருவிகளைப் படிக்கத் தெரியாமல் ஒரு விமானத்தை autopilot-இல் பறத்துவதற்குச் சமம். கொந்தளிப்பு (turbulence) ஏற்படும் போது—மற்றும் distributed systems-களில் அது எப்போதும் ஏற்படும்—பிரச்சனை உங்கள் network layer-ல் உள்ளதா, உங்கள் conflict resolution-ல் உள்ளதா அல்லது உங்கள் data model-ல் உள்ளதா என்பதை நீங்கள் அறிய வேண்டும்.

நூலகங்களை (libraries) சார்ந்துகொள்வதற்கு முன்பு கருத்துக்களைக் கற்றுக்கொள்வதே இங்குள்ள உறுதிமொழி. அதாவது, பின்வரும் சூழல்களில் என்ன நடக்கும் என்பதைத் தானாகவே சிந்தித்துப் பார்க்க வேண்டும்:

  • ஒரு கிளையன்ட் தட்டச்சு செய்யும் இடையில் துண்டிக்கப்பட்டு, பத்து விநாடிகளுக்குப் பிறகு மீண்டும் இணைக்கப்பட்டால்
  • இரண்டு பயனர்கள் ஒரே கர்சர் நிலையில் ஒரே நேரத்தில் உரையைச் சேர்த்தால்
  • ஒரு பயனர் மற்றொரு பயனர் தீவிரமாகத் திருத்தி வரும் ஒரு பகுதியை நீக்கினால்
  • சர்வர் செயலிழந்து, ஒரு புதிய நோட் (node) ஆவண நிலையை ஆரம்பத்திலிருந்து மீண்டும் உருவாக்க வேண்டியிருந்தால்

Operational Transformation (OT) மற்றும் Conflict-free Replicated Data Types (CRDTs) ஆகிய இரண்டும் இந்தத் தீர்வுகளுக்கான இரண்டு முக்கிய குடும்பங்களாகும். Google Docs தனது ஆரம்பகால கட்டமைப்பை OT-யைக் கொண்டு உருவாக்கியது என்பது குறிப்பிடத்தக்கது; இது மாற்றங்களைச் செயல்படுத்துவதற்கு முன்பு அவற்றை ஒன்றுடன் ஒன்று ஒப்பிட்டு மாற்றியமைக்க ஒரு மையச் சர்வர் தேவைப்படுகிறது. இதற்கு நேர்மாறாக, CRDTs ஒருங்கிணைப்பு (coordination) இல்லாமலேயே ஒரே நேரத்தில் செய்யப்படும் மாற்றங்களை உள்ளூர் ரீதியாக (locally) இணைக்கும் வகையில் வடிவமைக்கப்பட்டுள்ளன, இது அவற்றை peer-to-peer அல்லது edge-based அமைப்புகளுக்குச் சிறந்ததாக மாற்றுகிறது. இவற்றுக்கிடையே அல்லது கலப்பு அணுகுமுறைகளைத் தேர்ந்தெடுப்பதற்கு, அவற்றின் memory use, convergence guarantees மற்றும் implementation complexity ஆகியவற்றில் உள்ள சமநிலைகளைப் (trade-offs) புரிந்துகொள்வது அவசியம். அவற்றைப் பற்றிப் படிப்பது மட்டும் போதாது; அவை எங்கு தோல்வியடைகின்றன என்பதைக் காண, எளிய மற்றும் மேம்படுத்தப்பட்ட இரண்டு பதிப்புகளையும் செயல்படுத்தத் திட்டமிட்டுள்ளோம்.

மறுசீரமைப்புகள், தவறுகள் மற்றும் முட்டுக்கட்டைகள்

எதிர்பார்ப்புகள் நேர்மையாகவே கணக்கிடப்படுகின்றன. எதுவும் வேலை செய்யாத காலக்கட்டங்கள் இருக்கும். முதல் முயற்சியானது உரை மாற்றங்களைக் குறிக்க எளிய JSON patches-களைப் பயன்படுத்தலாம், ஆனால் ஒரு பத்தியில் "index 5" என்ற கருத்தாக்கம் JSON-இல் இல்லை என்பதைத் தெரிந்துகொள்ள நேரிடும். இதனால் ஒரே குறியீட்டில் நடக்கும் இரண்டு ஒரே நேரத் தொகுப்புகள் (concurrent insertions) ஒன்றையொன்று இணைப்பதற்குப் பதிலாக, ஒன்றைத் தவிடுபொடியாக்கிவிடும் (overwrite). இரண்டாவது முயற்சியானது ஒரு தனிப்பயனாக்கப்பட்ட நேரியல் வரலாற்றுப் பதிவை (custom linear history log) உருவாக்கலாம், ஆனால் ஆவணம் வளரும்போது அந்தப் பதிவை மீண்டும் இயக்குவது (replaying) ஒரு Big O nightmare ஆகிவிடும் என்பதை உணரலாம். மூன்றாவது முயற்சியானது WebSockets-ஐ உள்ளூர் அளவில் (locally) இயங்கச் செய்யலாம், ஆனால் பாக்கெட் இழப்பு (packet loss) மற்றும் மாறுபடும் தாமதம் (variable latency) போன்ற காரணங்களால் உண்மையான நெட்வொர்க்கில் அது தோல்வியடையலாம்.

அந்தத் தடைகளே இதன் நோக்கம். ஏற்கனவே இயங்கும் ஒரு களஞ்சியத்தை (repository) நகலெடுப்பது, ஏன் வரிசை (queue) அந்த குறிப்பிட்ட வரிசையில் வெளியேறுகிறது அல்லது ஏன் சர்வர் ஒரு version vector-ஐப் பராமரிக்கிறது போன்ற ஆய்வுகளைத் தவிர்த்துவிடும். ஒரே கூறினை (component) மூன்று முறை மீண்டும் உருவாக்குவது மெதுவானதுதான், ஆனால் அது ஒரு கட்டமைப்பானது (framework) என்ன செய்கிறது மற்றும் உங்கள் சொந்த தர்க்கம் (logic) எதைக் கையாள வேண்டும் என்பதற்கிடையேயான எல்லையைப் புரிந்துகொள்ளத் தூண்டுகிறது.

இந்தச் செயல்பாட்டின் ஆவணப்படுத்தல் ஒரு சிறப்பம்சங்களின் தொகுப்பாக இருக்காது. அதில் தவறான பாதைகளும் இருக்கும். உதாரணமாக, presence awareness-ஐ உருவாக்குவது—யார் ஆன்லைனில் உள்ளனர் மற்றும் அவர்களின் கர்சர் எங்குள்ளது என்பதை அறிவது—ஒரு அலங்கார அம்சம் போலத் தோன்றலாம், ஆனால் அது உரையைப் போலவே அதே நிலைத்தன்மை மாதிரியைப் (consistency model) பொறுத்தே உள்ளது என்பதை நீங்கள் உணரும் வரை. பயனர் A, பயனர் B-இன் கர்சரை 10-வது நிரலில் (column 10) பார்த்தால், பின்னர் பயனர் B நான்கு எழுத்துக்களைச் சேர்த்தால், அந்த கர்சர் எங்கு நகரும்? ஆவணத்தின் அமைப்பைப் (document topology) பற்றிய பொதுவான புரிதல் இல்லையென்றால், presence தரவுகள் யதார்த்தத்திலிருந்து விலகிவிடும். அதைத் தீர்க்க, கர்சர் நிலையை அதன் எண் குறியீட்டுடன் (numerical index) மட்டும் இணைக்காமல், அடிப்படையான தரவு அமைப்பின் அடையாளத்துடன் (underlying data structure’s identity) இணைக்க வேண்டும். இத்தகைய விவரங்களை பயிற்சிகள் (tutorials) மேலோட்டமாகத் தாண்டிச் செல்கின்றன, ஏனெனில் அவை சலிப்பூட்டுபவை, முக்கியமற்றவை அல்ல.

அடுத்து என்ன

உடனடித் திட்டவரைவு (roadmap) திட்டமிட்டே எளிமையாக வடிவமைக்கப்பட்டுள்ளது. முதல் மைல்கற்கள் இவைதான்:

  • எழுத்து நிகழ்வுகளைப் பிரதிபலிக்கும் (echoes) ஒரு மூல WebSocket server, இதன் மூலம் தாமதம் (latency) மற்றும் இணைப்புச் சுழற்சியை (connection lifecycle) நேரடியாக உணரலாம்
  • ஒரே நேரத்தில் பல மாற்றங்கள் நடக்கும்போது (concurrency) எளிய முறைப்படி எழுத்துக்களைச் சேர்ப்பது ஏன் தோல்வியடைகிறது என்பதைப் புரிந்துகொள்ள கிளையண்டில் (client) ஒரு எளிய string buffer
  • வரிசைப்படுத்தப்பட்ட தொடர்ச்சல்களுக்கான (ordered sequences) ஒரு புதிய CRDT, அது எவ்வளவு திறமையற்றதாக இருந்தாலும், அதன் பரிமாற்றப் பண்பை (commutative property)ச் செயல்பாட்டில் காண உதவும்
  • ஒரு உண்மையான குறியீட்டுத் தொகுப்பான CodeMirror அல்லது Monaco போன்ற ஒன்றுடன் படிப்படியாக ஒருங்கிணைத்தல், இதன் மூலம் எடிட்டரின் imperative API மற்றும் செயல்பாட்டுத் தன்மை கொண்ட operational history ஆகியவற்றிற்கு இடையேயான முரண்பாட்டை எதிர்கொள்ளலாம்

ஒவ்வொரு படிநிலையும் ஒரு எழுத்துப்பூர்வமான விளக்கத்துடன் வரும். ஏன் இந்த அணுகுமுறை, மற்றொன்று ஏன் இல்லை? எந்தக் கற்பனைகள் பொய்யானவை? எந்த abstraction leak ஏற்பட்டது?

ஒரு உண்மையான பாடம்

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

நீங்கள் இதற்கு முன் ஒரு உரைத் தொகுப்பான் (text editor), வடிவமைப்பு கருவி (design tool) அல்லது கேம் ஸ்டேட் சிங்க் என்ஜின் (game state sync engine) போன்ற கூட்டுப்பணி மென்பொருள்களை உருவாக்கியிருந்தால், உங்களை நிலைகுலையச் செய்த தோல்விகளைப் பகிர்ந்து கொள்ளுங்கள். நீங்களும் இந்த அமைப்புகளைக் கற்றுக் கொண்டிருப்பவர் என்றால், என்னுடன் இணைந்து பயணிங்கள். குறியீடு மெதுவாக வரும், மேலும் அது அடிக்கடி மாற்றியமைக்கப்படும். நாள் 0 இப்போதே தொடங்குகிறது.