रियल-टाइम कोलैबोरेशन तब तक सहज लगता है जब तक आप पर्दे के पीछे की सच्चाई नहीं देख लेते। एक व्यक्ति टाइप करता है। दूसरा तीन पैराग्राफ ऊपर एक लाइन डिलीट कर देता है। तीसरा Stack Overflow से एक स्निपेट पेस्ट करता है। किसी तरह वह डॉक्यूमेंट एक एकल, सुसंगत स्थिति (coherent state) में स्थिर हो जाता है। WebSockets या डिस्ट्रिब्यूटेड स्टेट (distributed state) के बिना, शून्य से उस तरलता (fluidity) को बनाना जोखिम भरा लग सकता है। लेकिन यह वास्तव में सीखने का सही तरीका भी लगता है।

यह प्रोजेक्ट शून्य से शुरू होता है। कोई उधार लिया हुआ बॉयलरप्लेट नहीं। कोई पॉलिश किए हुए YouTube वॉकथ्रू नहीं जहाँ तीस सेकंड के मोंटाज में कठिन हिस्सों को छोड़ दिया जाता है। लक्ष्य एक ऐसा कोलैबोरेटिव कोड एडिटर बनाना है जहाँ कई उपयोगकर्ता एक ही फ़ाइल को एक साथ एडिट कर सकें, और एक-दूसरे के बदलावों—और एक-दूसरे के कर्सर को—होते हुए देख सकें। वहां तक पहुँचने के लिए ट्रांसपोर्ट लेयर्स, कंसिस्टेंसी मॉडल्स और डॉक्यूमेंट को खराब किए बिना समवर्ती संपादन (concurrent edits) को मर्ज करने की पेचीदा समस्या को सुलझाना आवश्यक होगा।

"रियल-टाइम" का वास्तव में क्या अर्थ है

अधिकांश वेब एप्लिकेशन रिक्वेस्ट-रिस्पॉन्स साइकिल (request-response cycles) के साथ सहज होते हैं। आप एक फॉर्म सबमिट करते हैं, सर्वर उसे सेव करता है, और आप पेज रिफ्रेश करते हैं। रियल-टाइम कोलैबोरेशन उस अनुबंध को पूरी तरह से तोड़ देता है। हर कीस्ट्रोक एक इवेंट है जिसे हर अन्य जुड़े हुए क्लाइंट तक प्रसारित होना चाहिए, आमतौर पर मिलीसेकंड में, और एक ऐसे क्रम में पहुँचना चाहिए जो अर्थ को बनाए रखे।

WebSockets यहाँ ट्रांसपोर्ट के लिए स्पष्ट विकल्प हैं क्योंकि वे क्लाइंट और सर्वर के बीच एक स्थायी, फुल-डुप्लेक्स कनेक्शन बनाए रखते हैं। HTTP पोलिंग के विपरीत, जो हर कुछ सेकंड में "कुछ नया है?" पूछकर बैंडविड्थ बर्बाद करता है, एक WebSocket खुला रहता है। जब यूजर A एक सेमीकोलन टाइप करता है, तो वह कैरेक्टर एक मैसेज बन जाता है जो सॉकेट के माध्यम से सेंट्रल सर्वर तक जाता है, और फिर यूजर B और C तक फैल जाता है। वह हिस्सा अपेक्षाकृत सीधा है।

कठिन हिस्सा तब होता है जब B और C बिल्कुल एक ही समय पर टाइप करते हैं। यदि दोनों बदलाव लगभग एक साथ सर्वर पर पहुँचते हैं, तो कौन सा जीतता है? यदि आप केवल आगमन क्रम (arrival order) में मैसेज ब्रॉडकास्ट करते हैं, तो कैरेक्टर्स के गायब होने या टेक्स्ट के उलझने का जोखिम रहता है। साधारण 'लास्ट-राइट-विन्स' (last-write-wins) रणनीतियाँ विफल हो जाती हैं क्योंकि वे उपयोगकर्ता के इरादे (intent) को नजरअंदाज करती हैं। यदि मैं लाइन एक की शुरुआत में "hello" टाइप करता हूँ जबकि आप लाइन एक की शुरुआत में "world" टाइप करते हैं, तो परिणाम ऐसा टकराव नहीं होना चाहिए जहाँ हम में से एक मिट जाए। इसे नियतात्मक रूप से (deterministically) "helloworld" या "worldhello" चुना जाना चाहिए। इसे हासिल करने के लिए एक सिंक्रोनाइज़ेशन रणनीति की आवश्यकता होती है जो डॉक्यूमेंट की संरचना को समझती हो।

शून्य से शुरुआत करना क्यों महत्वपूर्ण है

ऐसे बेहतरीन फ्रेमवर्क्स मौजूद हैं जो इस जटिलता को छिपा देते हैं। Yjs, Automerge, और Socket.IO इस दर्द को कम कर सकते हैं और एक दोपहर में एक वर्किंग प्रोटोटाइप तैयार कर सकते हैं। लेकिन उनके अंतर्निहित प्रिमिटिव्स (primitives) को समझे बिना उनका उपयोग करना वैसा ही है जैसे बिना इंस्ट्रूमेंट्स पढ़ना जाने बिना ऑटोपायलट पर विमान उड़ाना। जब टर्बुलेंस आता है—और डिस्ट्रिब्यूटेड सिस्टम में, यह हमेशा आता है—तो आपको यह जानने की जरूरत होती है कि समस्या आपके नेटवर्क लेयर में है, आपके कॉन्फ्लिक्ट रेजोल्यूशन में है, या आपके डेटा मॉडल में।

यहाँ प्रतिबद्धता लाइब्रेरीज़ पर भरोसा करने से पहले अवधारणाओं (concepts) को सीखने की है। इसका मतलब है मैन्युअल रूप से यह समझना कि क्या होता है जब:

  • एक क्लाइंट कीस्ट्रोक के बीच में डिस्कनेक्ट हो जाता है और दस सेकंड बाद फिर से जुड़ जाता है
  • दो उपयोगकर्ता एक ही कर्सर पोजीशन पर एक साथ टेक्स्ट डालते हैं
  • एक उपयोगकर्ता उस ब्लॉक को डिलीट कर देता है जिसे दूसरा उपयोगकर्ता सक्रिय रूप से एडिट कर रहा है
  • सर्वर क्रैश हो जाता है और एक नए नोड को शून्य से डॉक्यूमेंट स्टेट को फिर से बनाना पड़ता है

Operational Transformation (OT) और Conflict-free Replicated Data Types (CRDTs) इन समस्याओं के लिए दो प्रमुख समाधान हैं। Google Docs ने प्रसिद्ध रूप से अपने शुरुआती आर्किटेक्चर को OT पर बनाया था, जिसके लिए ऑपरेशन्स को लागू करने से पहले एक सेंट्रल सर्वर की आवश्यकता होती है जो उन्हें एक-दूसरे के विरुद्ध ट्रांसफॉर्म कर सके। इसके विपरीत, CRDTs को इस तरह डिज़ाइन किया गया है कि समवर्ती अपडेट को बिना किसी समन्वय (coordination) के स्थानीय रूप से मर्ज किया जा सके, जो उन्हें पीयर-टू-पीयर या एज-आधारित सेटअप के लिए आकर्षक बनाता है। उनके बीच चुनाव करने के लिए—या हाइब्रिड दृष्टिकोण अपनाने के लिए—मेमोरी उपयोग, कन्वर्जेंस गारंटी और कार्यान्वयन जटिलता (implementation complexity) में उनके ट्रेड-ऑफ्स को समझना आवश्यक है। केवल उनके बारे में पढ़ना काफी नहीं है; योजना दोनों के साधारण और परिष्कृत संस्करणों को लागू करने की है ताकि यह देखा जा सके कि वे कहाँ विफल होते हैं।

रीबिल्ड्स, गलतियाँ और डेड एंड्स

अपेक्षाओं को ईमानदारी से तय किया गया है। ऐसे दौर आएंगे जब कुछ भी काम नहीं करेगा। पहला प्रयास टेक्स्ट परिवर्तनों को दर्शाने के लिए सरल JSON पैच का उपयोग कर सकता है, लेकिन बाद में यह पता चलेगा कि JSON में "पैराग्राफ में इंडेक्स 5" जैसी कोई अवधारणा नहीं है, इसलिए एक ही इंडेक्स पर दो समानांतर (concurrent) इंसर्शन एक-दूसरे को मर्ज करने के बजाय ओवरराइट कर देते हैं। दूसरा प्रयास एक कस्टम लीनियर हिस्ट्री लॉग (linear history log) बना सकता है, लेकिन यह महसूस हो सकता है कि जैसे-जैसे डॉक्यूमेंट बढ़ता है, उस लॉग को फिर से चलाना (replaying) Big O की एक बड़ी समस्या बन जाता है। तीसरा प्रयास स्थानीय स्तर पर WebSockets को काम करते हुए देख सकता है, लेकिन वास्तविक नेटवर्क पर यह विफल हो सकता है जहाँ पैकेट लॉस और वेरिएबल लेटेंसी (variable latency) सारे नियम बदल देते हैं।

वह घर्षण (friction) ही मुख्य उद्देश्य है। किसी काम करने वाले रिपॉजिटरी को कॉपी करने से आप इस बात की जांच नहीं कर पाएंगे कि क्यू (queue) उसी विशेष क्रम में क्यों फ्लश होती है, या सर्वर वर्जन वेक्टर (version vector) को क्यों बनाए रखता है। एक ही घटक को तीन बार फिर से बनाना धीमा है, लेकिन यह इस बात की समझ विकसित करने के लिए मजबूर करता है कि फ्रेमवर्क क्या करता है और आपके अपने लॉजिक को क्या संभालना चाहिए, उनके बीच की सीमा क्या है।

इस प्रक्रिया का दस्तावेजीकरण केवल सफलताओं का संग्रह नहीं होगा। इसमें गलत रास्ते भी शामिल होंगे। उदाहरण के लिए, प्रेजेंस अवेयरनेस (presence awareness) बनाना—यह जानना कि कौन ऑनलाइन है और उनका कर्सर कहाँ है—एक दिखावटी फीचर जैसा लग सकता है जब तक कि आपको यह अहसास न हो कि यह उसी कंसिस्टेंसी मॉडल (consistency model) पर निर्भर है जिस पर टेक्स्ट निर्भर है। यदि यूजर A, यूजर B के कर्सर को कॉलम 10 पर देखता है, और फिर यूजर B चार कैरेक्टर इंसर्ट करता है, तो वह कर्सर कहाँ जाएगा? डॉक्यूमेंट टोपोलॉजी (document topology) की साझा समझ के बिना, प्रेजेंस डेटा वास्तविकता से भटक जाता है। इसे हल करने के लिए कर्सर की स्थिति को केवल उसके संख्यात्मक इंडेक्स से नहीं, बल्कि अंतर्निहित डेटा स्ट्रक्चर की पहचान (identity) से जोड़ना आवश्यक है। ये वे विवरण हैं जिन्हें ट्यूटोरियल अक्सर छोड़ देते हैं क्योंकि वे उबाऊ होते हैं, इसलिए नहीं कि वे महत्वपूर्ण नहीं हैं।

आगे क्या होगा

रोडमैप को जानबूझकर संक्षिप्त रखा गया है। पहले मील के पत्थर (milestones) होंगे:

  • एक रॉ WebSocket सर्वर जो कैरेक्टर इवेंट्स को इको (echo) करे, ताकि लेटेंसी और कनेक्शन लाइफसाइकिल को प्रत्यक्ष रूप से महसूस किया जा सके
  • क्लाइंट पर एक साधारण स्ट्रिंग बफर, यह समझने के लिए कि कॉनकरेंसी (concurrency) के तहत साधारण इंसर्शन ऑर्डरिंग क्यों विफल हो जाती है
  • क्रमबद्ध अनुक्रमों (ordered sequences) के लिए स्क्रैच से बनाया गया एक CRDT, चाहे वह कितना भी अक्षम क्यों न हो, ताकि कम्यूटेटिव प्रॉपर्टी (commutative property) को क्रिया में देखा जा सके
  • एक वास्तविक कोड एडिटर सतह के साथ क्रमिक एकीकरण, संभवतः CodeMirror या Monaco जैसा कुछ, ताकि एडिटर के इम्पैरेटिव API और ऑपरेशनल हिस्ट्री की फंक्शनल प्रकृति के बीच के अंतर से निपटा जा सके

प्रत्येक चरण के साथ एक लिखित तर्क (rationale) होगा। यह दृष्टिकोण क्यों और वह क्यों नहीं? कौन सी धारणाएं गलत साबित हुईं? कौन सा एब्स्ट्रैक्शन लीक हुआ?

एक वास्तविक सीख

WebSockets या CRDTs में अनुभव के बिना इस तरह की परियोजना शुरू करना डरावना हो सकता है, लेकिन विशेषज्ञता अक्सर बेहतर लेबल के साथ बार-बार होने वाला भ्रम ही होती है। उद्देश्य तेजी से काम पूरा करना नहीं है। उद्देश्य एक ऐसा सिस्टम बनाना है जिसका व्यवहार पूर्वानुमेय (predictable) हो क्योंकि हर लेयर को केवल उम्मीद के साथ इम्पोर्ट करने के बजाय इरादे के साथ बनाया गया था।

यदि आपने पहले कभी कोलैबोरेटिव सॉफ्टवेयर बनाया है—चाहे वह टेक्स्ट एडिटर हो, डिज़ाइन टूल हो, या गेम स्टेट सिंक इंजन—तो उन फेलियर मोड (failure modes) को साझा करें जिन्होंने आपको चौंका दिया। यदि आप भी इन सिस्टम्स को सीख रहे हैं, तो साथ जुड़ें। कोड धीरे-धीरे आएगा, और इसे अक्सर फिर से लिखा जाएगा। डे 0 (Day 0) अब शुरू होता है।