रिअल-टाइम कोलाबरेशन (Real-time collaboration) पडद्यामागची प्रक्रिया न पाहता अगदी सहज वाटते. एक व्यक्ती टाईप करते. दुसरी व्यक्ती तीन परिच्छेद वरची एक ओळ डिलीट करते. तिसरी व्यक्ती Stack Overflow वरून एखादा स्निपेट पेस्ट करते. तरीही, काहीतरी जादू होऊन तो दस्तऐवज (document) एका सुसंगत स्थितीत स्थिरावतो. WebSockets किंवा डिस्ट्रिब्युटेड स्टेट (distributed state) चा कोणताही पूर्व अनुभव नसताना, शून्यातून ही लवचिकता निर्माण करणे बेजबाबदार वाटू शकते. पण, खऱ्या अर्थाने शिकण्यासाठी हाच योग्य मार्ग वाटतो.

हा प्रकल्प शून्यापासून सुरू होतो. कोणताही तयार कोड (boilerplate) वापरलेला नाही. कोणतेही असे पॉलिश केलेले YouTube वॉकथ्रू नाहीत, जिथे कठीण भाग तीस सेकंदांच्या मोंटाजमध्ये वगळले जातात. आमचे ध्येय एक कोलाबोरेटिव्ह कोड एडिटर (collaborative code editor) तयार करणे आहे, जिथे अनेक वापरकर्ते एकाच वेळी एकाच फाईलमध्ये बदल करू शकतील आणि एकमेकांचे बदल—आणि एकमेकांचे कर्सर्स—त्यांच्या घडामोडींसह पाहू शकतील. तिथे पोहोचण्यासाठी ट्रान्सपोर्ट लेयर्स (transport layers), कन्सिस्टन्सी मॉडेल्स (consistency models) आणि दस्तऐवज खराब न करता एकाच वेळी होणारे बदल (concurrent edits) एकत्र करण्याची कठीण समस्या सोडवावी लागेल.

"रिअल-टाइम" (Real-Time) चा नेमका अर्थ काय?

बहुतेक वेब ॲप्लिकेशन्स 'रिक्वेस्ट-रिस्पॉन्स' (request-response) सायकलवर चालतात. तुम्ही एखादा फॉर्म सबमिट करता, सर्व्हर तो सेव्ह करतो आणि तुम्ही पेज रिफ्रेश करता. रिअल-टाइम कोलाबरेशन हा करार पूर्णपणे मोडीत काढते. प्रत्येक कीस्ट्रोक (keystroke) ही एक घटना (event) असते, जी सहसा मिलिसेकंदात इतर सर्व कनेक्टेड क्लायंट्सपर्यंत पोहोचली पाहिजे आणि त्याचा अर्थ कायम राहील अशा क्रमाने ती पोहोचली पाहिजे.

येथे WebSockets हा ट्रान्सपोर्टसाठी स्पष्ट पर्याय आहे, कारण ते क्लायंट आणि सर्व्हरमध्ये एक कायमस्वरूपी, फुल-ड्युप्लेक्स (full-duplex) कनेक्शन राखतात. HTTP polling प्रमाणे नाही, जे दर काही सेकंदांनी "काही नवीन आहे का?" असे विचारून बँडविड्थ वाया घालवते, त्याऐवजी WebSocket उघडे राहते. जेव्हा वापरकर्ता A सेमीकोलन (semicolon) टाईप करतो, तेव्हा तो कॅरेक्टर एक मेसेज बनतो जो सॉकेटद्वारे मध्यवर्ती सर्व्हरकडे जातो आणि नंतर वापरकर्ता B आणि C कडे पोहोचतो. हा भाग तुलनेने सोपा आहे.

कठीण भाग म्हणजे जेव्हा B आणि C अगदी एकाच वेळी टाईप करतात तेव्हा काय होते. जर दोन्ही बदल जवळजवळ एकाच वेळी सर्व्हरवर पोहोचले, तर कोणता बदल ग्राह्य धरला जाईल? जर तुम्ही केवळ येणाऱ्या क्रमानुसार मेसेज ब्रॉडकास्ट केले, तर कॅरेक्टर्स गहाळ होण्याचा किंवा मजकूर विस्कळीत होण्याचा धोका असतो. 'लास्ट-राईट-विन्स' (last-write-wins) सारख्या साध्या धोरणांना अपयश येते कारण ते वापरकर्त्याचा हेतू (intent) दुर्लक्षित करतात. जर मी ओळ एक च्या सुरुवातीला "hello" टाईप केले आणि तुम्ही त्याच ओळीच्या सुरुवातीला "world" टाईप केले, तर त्याचा परिणाम असा नसावा की त्यातील एक शब्द पुसला गेला. त्याऐवजी ते "helloworld" किंवा "worldhello" असायला हवे, जे निश्चितपणे (deterministically) निवडले गेले असेल. हे साध्य करण्यासाठी अशा सिंक्रोनाइझेशन स्ट्रॅटेजीची गरज आहे जी दस्तऐवजाची रचना समजून घेऊ शकेल.

शून्यापासून सुरुवात करणे का महत्त्वाचे आहे?

ही गुंतागुंत लपवणारे उत्कृष्ट फ्रेमवर्क्स उपलब्ध आहेत. Yjs, Automerge आणि Socket.IO यांसारखी साधने ही कसरत सोपी करू शकतात आणि एका दुपारीच एक कार्यरत प्रोटोटाइप तयार करून देऊ शकतात. परंतु, त्यामागील मूलभूत घटक (primitives) न समजून त्यांचा वापर करणे म्हणजे उपकरणांची (instruments) माहिती नसताना विमान ऑटोपायलटवर उडवण्यासारखे आहे. जेव्हा वादळ येते—आणि डिस्ट्रिब्युटेड सिस्टम्समध्ये ते नेहमीच येते—तेव्हा समस्या तुमच्या नेटवर्क लेयरमध्ये आहे, तुमच्या कॉन्फ्लिक्ट रिझोल्यूशनमध्ये (conflict resolution) आहे की तुमच्या डेटा मॉडेलमध्ये, हे तुम्हाला माहित असणे आवश्यक आहे.

येथे वचनबद्धता ही लायब्ररीजवर अवलंबून राहण्यापूर्वी संकल्पना शिकण्याची आहे. याचा अर्थ असा की खालील गोष्टी घडल्यावर काय होईल, याचा स्वतः विचार करणे:

  • एखादा क्लायंट कीस्ट्रोकच्या मध्येच डिस्कनेक्ट होतो आणि दहा सेकंदांनंतर पुन्हा कनेक्ट होतो
  • दोन वापरकर्ते एकाच वेळी एकाच कर्सर पोझिशनवर मजकूर इन्सर्ट करतात
  • एक वापरकर्ता असा ब्लॉक डिलीट करतो ज्यावर दुसरा वापरकर्ता सक्रियपणे काम करत आहे
  • सर्व्हर क्रॅश होतो आणि एका नवीन नोडला (node) शून्यापासून दस्तऐवजाची स्थिती पुन्हा तयार करावी लागते

या समस्यांसाठी Operational Transformation (OT) आणि Conflict-free Replicated Data Types (CRDTs) हे दोन प्रमुख उपाय आहेत. Google Docs ने आपले सुरुवातीचे आर्किटेक्चर OT वर आधारित ठेवले होते, ज्यामध्ये ऑपरेशन्स लागू करण्यापूर्वी त्यांना एकमेकांविरुद्ध ट्रान्सफॉर्म करण्यासाठी एका मध्यवर्ती सर्व्हरची आवश्यकता असते. याउलट, CRDTs अशा प्रकारे डिझाइन केलेले आहेत की एकाच वेळी होणारे अपडेट्स कोणत्याही समन्वयाशिवाय (coordination) स्थानिक पातळीवर मर्ज केले जाऊ शकतात, ज्यामुळे ते peer-to-peer किंवा edge-based सेटअपसाठी आकर्षक ठरतात. या दोघांपैकी एक निवडणे—किंवा हायब्रिड दृष्टिकोन वापरणे—यासाठी मेमरी वापर, कन्वर्जन्स गॅरंटीज (convergence guarantees) आणि अंमलबजावणीतील गुंतागुंत यांमधील तडजोडी (trade-offs) समजून घेणे आवश्यक आहे. केवळ या तडजोडींबद्दल वाचणे पुरेसे नाही; ते कुठे चुकतात हे पाहण्यासाठी साध्या (naive) आणि सुधारित (refined) अशा दोन्ही आवृत्त्यांची अंमलबजावणी करण्याचा आमचा प्लॅन आहे.

पुनर्रचना, चुका आणि मृत मार्ग (Dead Ends)

अपेक्षांचे वास्तववादी नियोजन केले आहे. असे काही काळ येतील जेव्हा काहीच काम करणार नाही. पहिला प्रयत्न मजकूर बदलांचे प्रतिनिधित्व करण्यासाठी साध्या JSON patches चा वापर करू शकतो, पण नंतर असे लक्षात येईल की JSON मध्ये "एका परिच्छेदातील इंडेक्स ५" ही संकल्पनाच नाही, त्यामुळे एकाच इंडेक्सवर होणारे दोन एकाच वेळी केलेले इन्सर्शन (insertions) एकमेकांना ओव्हरराईट करतील, मर्ज होणार नाहीत. दुसरा प्रयत्न एक कस्टम लिनियर हिस्ट्री लॉग (linear history log) तयार करू शकतो, पण दस्तऐवज (document) जसजसा वाढेल, तसतसा तो लॉग पुन्हा प्ले करणे (replaying) हे Big O च्या दृष्टीने एक भयानक आव्हान ठरेल. तिसरा प्रयत्न स्थानिक पातळीवर (locally) WebSockets कार्यान्वित करू शकतो, पण प्रत्यक्ष नेटवर्कवर पॅकेट लॉस (packet loss) आणि बदलत्या लॅटन्सीमुळे (latency) सर्व नियम बदलून जातील.

तो संघर्षच मुख्य उद्देश आहे. एखादे कार्यरत रिपॉझिटरी (repository) कॉपी केल्यामुळे क्यू (queue) विशिष्ट क्रमाने का फ्लश होते किंवा सर्व्हर व्हर्जन वेक्टर (version vector) का राखतो, याच्या संशोधनाचा भाग सुटून जाईल. तोच घटक तीन वेळा पुन्हा तयार करणे संथ आहे, परंतु यामुळे फ्रेमवर्क काय करते आणि तुमच्या स्वतःच्या लॉजिकला काय हाताळावे लागेल, यातील सीमा समजण्यास मदत होते.

या प्रक्रियेचे दस्तऐवजीकरण हे केवळ यशाची गाथा नसेल. त्यामध्ये चुकलेल्या वाटांचाही समावेश असेल. उदाहरणार्थ, प्रेझन्स अवेअरनेस (presence awareness) तयार करणे—कोण ऑनलाइन आहे आणि त्यांचा कर्सर कुठे आहे हे जाणून घेणे—हे केवळ एक सजावटीचे वैशिष्ट्य वाटते, जोपर्यंत तुम्हाला हे लक्षात येत नाही की ते मजकुराप्रमाणेच त्याच कन्सिस्टन्सी मॉडेलवर (consistency model) अवलंबून आहे. जर युजर A ला युजर B चा कर्सर १० व्या कॉलमवर दिसत असेल आणि युजर B चार अक्षरे इन्सर्ट करत असेल, तर तो कर्सर कुठे हलणार? डॉक्युमेंट टोपोलॉजीच्या (document topology) सामायिक आकलनाशिवाय, प्रेझन्स डेटा वास्तवापासून विचलित होतो. ते सोडवण्यासाठी कर्सरची स्थिती केवळ त्याच्या संख्यात्मक इंडेक्सशी नाही, तर मूळ डेटा स्ट्रक्चरच्या आयडेंटिटीशी (identity) जोडणे आवश्यक आहे. अशा प्रकारचे तपशील ट्युटोरियल्समध्ये वरवरच सांगितले जातात, कारण ते कंटाळवाणे असतात, महत्त्वाचे नसतील म्हणून नाही.

पुढे काय?

तात्काळ रोडमॅप हेतुपुरस्सर मर्यादित ठेवला आहे. पहिले टप्पे (milestones) खालीलप्रमाणे असतील:

  • एक रॉ WebSocket सर्व्हर जो कॅरेक्टर इव्हेंट्सचे प्रतिध्वनी (echo) देईल, जेणेकरून लॅटन्सी आणि कनेक्शन लाइफसायकलचा प्रत्यक्ष अनुभव घेता येईल
  • क्लायंटवर एक साधा स्ट्रिंग बफर, जेणेकरून कन्करन्सीमध्ये साधे इन्सर्शन ऑर्डरिंग का अपयशी ठरते हे समजून घेता येईल
  • क्रमित सिक्वेन्ससाठी (ordered sequences) शून्यापासून तयार केलेले CRDT, ते कितीही अकार्यक्षम असले तरी, कम्युटेटिव्ह प्रॉपर्टी (commutative property) प्रत्यक्ष पाहण्यासाठी
  • प्रत्यक्ष कोड एडिटरच्या पृष्ठभागाशी (surface) हळूहळू एकत्रीकरण, बहुधा CodeMirror किंवा Monaco सारखे काहीतरी, जेणेकरून एडिटरच्या इम्परेटिव्ह API आणि ऑपरेशनल हिस्ट्रीच्या फंक्शनल स्वरूपातील तफावत हाताळता येईल

प्रत्येक टप्प्यासोबत एक लिखित तर्क (rationale) असेल. हा दृष्टिकोन का आणि दुसरा का नाही? कोणते गृहितक चुकीचे ठरले? कोणते ॲब्स्ट्रॅक्शन (abstraction) लीक झाले?

एक महत्त्वाचा धडा

WebSockets किंवा CRDTs मधील अनुभवाशिवाय असा प्रकल्प सुरू करणे भीतीदायक वाटते, परंतु तज्ज्ञता म्हणजे अनेकदा केवळ चांगल्या नावांसह केलेली वारंवारची गोंधळलेली प्रक्रिया असते. उद्दिष्ट वेगाने काम पूर्ण करणे हे नाही. तर असे एक सिस्टम तयार करणे ज्याचे वर्तन वर्तनायोग्य (predictable) असेल, कारण प्रत्येक स्तर केवळ आशेने नव्हे तर हेतूने तयार केला गेला आहे.

जर तुम्ही यापूर्वी कोलबोरेटिव्ह सॉफ्टवेअर (collaborative software) तयार केले असेल—मग ते टेक्स्ट एडिटर असो, डिझाइन टूल असो किंवा गेम स्टेट सिंक इंजिन असो—तुम्हाला ज्या त्रुटींचा सामना करावा लागला, त्या शेअर करा. जर तुम्ही देखील ही प्रणाली शिकत असाल, तर सोबत राहा. कोड हळूहळू येईल आणि तो वारंवार पुन्हा लिहिला जाईल. Day 0 आता सुरू होत आहे.