सहयोगात्मक एडिटिंग उतनी आसान नहीं जितनी लगती है
रियल-टाइम सहयोगात्मक एडिटिंग के लिए CRDT बनाम OT: दोनों तरीकों के वो ट्रेड-ऑफ़ जिनकी बात कोई नहीं करता। मल्टीप्लेयर टेक्स्ट के पीछे की असली तस्वीर।

Google Docs लाखों यूज़र्स के लिए रियल-टाइम सहयोगात्मक एडिटिंग संभालता है। देखने में यह बिल्कुल आसान लगता है — आप टाइप करते हैं, आपका कर्सर चलता है, और आपके साथी के बदलाव तुरंत स्क्रीन पर आ जाते हैं। यह सादगी धोखा देती है। अंदर से देखें तो सहयोगात्मक एडिटिंग distributed systems की सबसे कठिन समस्याओं में से एक है, और इसे हल करने के दो मुख्य तरीके — Operational Transformation और CRDTs — दोनों के अपने ट्रेड-ऑफ़ हैं, जो तब तक साफ़ नहीं होते जब तक आप इनसे कुछ बनाने की कोशिश न करें।
CRDTs (Conflict-free Replicated Data Types) का वादा बड़ा लुभावना है: ऐसे डेटा स्ट्रक्चर जो बिना किसी टकराव के अपने-आप merge हो जाते हैं, जिन्हें केंद्रीय सर्वर की ज़रूरत नहीं होती, और जिनमें eventual consistency गणित से गारंटीड है। लेकिन कई टीमों ने सहयोगात्मक एडिटर के लिए CRDTs चुनने के बाद पाया है कि असली तस्वीर उलझी हुई है। थ्योरी बेहद खूबसूरत है। इंजीनियरिंग बेहद कठिन है।
समस्या: एक साथ होने वाले एडिट
मूल चुनौती यह है: दो यूज़र नेटवर्क देरी के साथ एक ही डॉक्यूमेंट को एक साथ एडिट कर रहे हैं। यूज़र A पोज़िशन 5 पर 'Hello' डालता है। यूज़र B, जिसने A का एडिट अभी तक नहीं देखा, पोज़िशन 5 पर मौजूद कैरेक्टर डिलीट कर देता है। अंतिम डॉक्यूमेंट कैसा दिखना चाहिए?
यह अस्पष्ट है। क्या B का डिलीट A के इनसर्ट से पहले वाले कैरेक्टर पर लागू होना चाहिए, या बाद वाले पर? अगर पहले, तो मूल कैरेक्टर हटेगा और 'Hello' दिखेगा। अगर बाद में, तो 'Hello' का 'H' हटेगा। दोनों ही व्याख्याएँ सही लगती हैं। सिस्टम को एक चुनना होगा और यह पक्का करना होगा कि सभी क्लाइंट एक ही नतीजे पर पहुँचें — divergence का मतलब है कि दो यूज़र अलग-अलग डॉक्यूमेंट देखें, और यही वह विफलता है जिसे माफ़ नहीं किया जा सकता।
The convergence problem:
Initial document: "The quick brown fox"
^ position 10
User A (at time T): Insert "very " at position 10
User B (at time T): Delete character at position 10
A sees locally: "The quick very brown fox"
B sees locally: "The quick rown fox" (deleted 'b')
Now A receives B's operation: delete at position 10
But A already inserted 5 chars at position 10...
Should B's delete now apply at position 10? (deletes 'v' from 'very')
Or at position 15? (deletes 'b' from 'brown' — the original target)
Both A and B must arrive at the same document.
This is the problem OT and CRDTs solve differently.
Operational Transformation (OT)
OT, जो पुराना तरीका है (1989), ऑपरेशनों को एक-दूसरे के सापेक्ष transform करके इस समस्या को हल करता है। जब A को B का 'पोज़िशन 10 पर डिलीट' ऑपरेशन मिलता है, तो A का सिस्टम देखता है कि A ने कौन-से ऐसे ऑपरेशन लगाए हैं जो B ने अभी नहीं देखे। फिर वह B के ऑपरेशन को उसी हिसाब से बदलता है: चूँकि A ने पोज़िशन 10 पर 5 कैरेक्टर डाले हैं, इसलिए B का डिलीट अब पोज़िशन 15 पर लागू होना चाहिए (मूल टार्गेट कैरेक्टर दाईं ओर खिसक गया है)।
यह तरीका काम करता है, और Google Docs इसी का इस्तेमाल करता है। दिक्कत transformation functions की है। insert और delete वाले टेक्स्ट एडिटर के लिए आपको यह तय करना होता है कि हर जोड़ी ऑपरेशन एक-दूसरे के सापेक्ष कैसे transform होगा। दो ऑपरेशन टाइप्स के लिए ही चार केस बनते हैं। इसमें फ़ॉर्मेटिंग, टेबल, इमेज, लिस्ट और कमेंट जोड़ दें, तो केसों की संख्या combinatorially बढ़ जाती है। हर केस सही होना चाहिए, और छोटी-सी गलती ऐसा divergence पैदा करती है जिसे दोबारा reproduce करना और debug करना लगभग नामुमकिन होता है।
OT के लिए पारंपरिक रूप से एक केंद्रीय सर्वर भी ज़रूरी होता है, जो ऑपरेशनों का total ordering तय करे। सर्वर के बिना, अलग-अलग क्लाइंट concurrent ऑपरेशनों को अलग-अलग क्रम में transform कर सकते हैं, और नतीजे अलग हो जाते हैं। Google के पास Docs के लिए केंद्रीय सर्वर का खर्च उठाने की क्षमता है। पीयर-टू-पीयर ऐप्लिकेशन में OT आसानी से नहीं चल सकता।
CRDTs: थ्योरी का वादा
CRDTs बिल्कुल अलग रास्ता अपनाते हैं। ऑपरेशनों को transform करने के बजाय वे ऐसे डेटा स्ट्रक्चर इस्तेमाल करते हैं जिनमें merge का कोई भी क्रम एक ही नतीजा देता है। यह गणितीय गारंटी है — अगर डेटा स्ट्रक्चर वैध CRDT है, तो convergence अपने-आप मिल जाती है। न transformation functions, न केंद्रीय सर्वर, न ऑर्डरिंग पर निर्भरता।
टेक्स्ट एडिटिंग के लिए मुख्य CRDT वेरिएंट RGA (Replicated Growable Array) और इसी तरह के sequence CRDTs हैं। पोज़िशन ट्रैक करने के बजाय (जो एडिट होने पर खिसकती रहती हैं), ये हर कैरेक्टर को एक यूनिक, ग्लोबली-ordered ID देते हैं। इनसर्ट मौजूदा IDs के बीच नई IDs बनाते हैं। डिलीट किसी ID को tombstone के रूप में चिह्नित करते हैं — हटाते नहीं (इस पर बाद में और बात होगी)।
CRDT sequence representation:
Document: "cat"
Internal representation (simplified):
ID: (A,1) → 'c' (user A, logical clock 1)
ID: (A,2) → 'a' (user A, logical clock 2)
ID: (A,3) → 't' (user A, logical clock 3)
User B inserts 'h' between 'c' and 'a':
ID: (B,1) → 'h' position: between (A,1) and (A,2)
Document: "chat"
User A (concurrently) inserts 'r' between 'c' and 'a':
ID: (A,4) → 'r' position: between (A,1) and (A,2)
Both IDs go between the same characters.
The CRDT uses a deterministic tie-breaking rule
(e.g., higher user ID wins) to order them.
Final document: "chart" or "chrat"
(deterministic — all clients get the same result)
CRDTs: व्यावहारिक हकीकत
CRDT का वादा — बिना टकराव, विकेंद्रीकृत, और गणितीय रूप से गारंटीड convergence — सच है। लेकिन इंजीनियरिंग की चुनौतियाँ काफ़ी बड़ी हैं।
Tombstones जमा होते जाते हैं। CRDT में जब आप कोई कैरेक्टर डिलीट करते हैं, तो उसे डेटा स्ट्रक्चर से हटाया नहीं जा सकता — हो सकता है दूसरे replicas ने वह डिलीट अभी न देखा हो और अपने एडिट सही जगह रखने के लिए उस ID की ज़रूरत हो। इसलिए डिलीट किए गए कैरेक्टर tombstone बनकर रह जाते हैं: दिखते नहीं, पर मौजूद रहते हैं। जिस डॉक्यूमेंट में बहुत एडिटिंग हुई हो, उसमें हज़ारों tombstones जमा हो जाते हैं। 1000 कैरेक्टर के डॉक्यूमेंट में एडिटिंग इतिहास से 50,000 tombstones हो सकते हैं। इससे मेमोरी फूलती है और ऑपरेशन धीमे होते हैं।
Metadata का बोझ बहुत ज़्यादा है। हर कैरेक्टर को एक यूनिक ID चाहिए (यूज़र आइडेंटिफ़ायर + logical timestamp), पड़ोसी IDs के pointers, और tombstone flag। एक कैरेक्टर का metadata 50-100 bytes तक हो सकता है। 100KB के डॉक्यूमेंट का CRDT रूप 5-10MB तक जा सकता है। यह sync में मायने रखता है — पूरी CRDT स्टेट नेटवर्क पर भेजना महंगा पड़ता है।
इतिहास के साथ परफ़ॉर्मेंस गिरती है। Sequence CRDT पर ऑपरेशन O(1) नहीं होते — कैरेक्टर इनसर्ट करने के लिए ID-ordered sequence में सही जगह खोजनी पड़ती है, और यह IDs की कुल संख्या पर निर्भर करता है (tombstones सहित)। लंबे एडिटिंग इतिहास वाले डॉक्यूमेंट में यह साफ़ तौर पर धीमा हो जाता है।
Intention को बचाना मुश्किल है। CRDT convergence की गारंटी देते हैं — सभी replicas एक ही स्टेट पर पहुँचते हैं। लेकिन क्या वे सही स्टेट पर पहुँचते हैं? जब दो यूज़र एक ही शब्द को एक साथ एडिट करते हैं, तो CRDT उनके कैरेक्टरों को निर्धारित तरीके से आपस में interleave कर देता है। नतीजा convergent तो होता है, पर बेतुका भी हो सकता है। OT को ऐसा डिज़ाइन किया जा सकता है कि एक यूज़र का एडिट सुरक्षित रहे और दूसरे का उसके चारों ओर लागू हो। CRDT मशीनी तरीके से merge करते हैं।
Yjs और व्यावहारिक बीच का रास्ता
Yjs JavaScript के लिए सबसे लोकप्रिय CRDT लाइब्रेरी है और कई वेब ऐप्लिकेशन में सहयोगात्मक फ़ीचर्स को शक्ति देती है। यह अच्छी तरह इंजीनियर की गई है और कई सैद्धांतिक CRDT समस्याओं को व्यावहारिक optimizations से हल करती है — compact binary encoding metadata का बोझ घटाती है, और सभी क्लाइंट ऑनलाइन हों तो tombstones का garbage collection काम करता है।
लेकिन Yjs कोई जादू नहीं है। जो टीमें इसे अपनाती हैं, वे पाती हैं कि CRDT लाइब्रेरी convergence की समस्या हल करती है, सहयोग की समस्या नहीं। आपको अब भी चाहिए: peer discovery के लिए signaling server, ऑफ़लाइन बदलावों के लिए persistence layer, हाई-लेवल ऑपरेशनों के लिए conflict resolution (जब दो यूज़र एक ही सेक्शन को दोबारा ढाँचा दें तो क्या होगा?), presence tracking (कर्सर, सिलेक्शन), ऐसा undo/redo जो दूसरे यूज़र्स के बदलावों का सम्मान करे, और permission management।
कुछ टीमों ने Yjs पर सहयोगात्मक एडिटर बनाने के बाद निष्कर्ष निकाला है कि CRDT का तरीका ऐसी जटिलता जोड़ता है जिसकी उन्हें ज़रूरत नहीं थी। अगर आपके ऐप्लिकेशन में केंद्रीय सर्वर है (और ज़्यादातर में होता है), तो OT या और भी सरल तरीके (last-write-wins with conflict detection) ज़्यादा व्यावहारिक हो सकते हैं। CRDTs असल में पीयर-टू-पीयर परिदृश्यों में चमकते हैं — offline-first ऐप्लिकेशन, local-first सॉफ़्टवेयर, और ऐसे सिस्टम जहाँ कोई केंद्रीय अधिकार मानकर नहीं चला जा सकता।
ज़्यादातर ऐप्लिकेशन को क्या करना चाहिए
अगर आप अपने ऐप्लिकेशन में सहयोगात्मक एडिटिंग जोड़ रहे हैं, तो यहाँ एक व्यावहारिक निर्णय-ढाँचा है।
- अगर आपके पास केंद्रीय सर्वर है और आपके डॉक्यूमेंट छोटे हैं: OT इस्तेमाल करें। Google का तरीका काम करता है। Node.js के लिए ShareDB जैसी लाइब्रेरी OT लागू करती हैं। केंद्रीय सर्वर ordering, persistence, conflict resolution और permissions — सब कुछ आसान कर देता है।
- अगर आपको ऑफ़लाइन सपोर्ट या पीयर-टू-पीयर चाहिए: CRDTs (Yjs या Automerge) इस्तेमाल करें। सच्चे ऑफ़लाइन एडिटिंग और पीयर-टू-पीयर sync को सही ढंग से संभालने वाला यही एकमात्र तरीका है। metadata का बोझ और tombstones का जमा होना विकेंद्रीकरण की कीमत मानकर स्वीकार करें।
- अगर आपके सहयोगी शायद ही कभी एक ही सेक्शन को एक साथ एडिट करते हैं: शायद आपको दोनों में से किसी की ज़रूरत ही न हो। सरल locking (हर सेक्शन में एक ही एडिटर) या अच्छे conflict UI के साथ last-write-wins कई वास्तविक सहयोग पैटर्न को OT या CRDTs की जटिलता के बिना कवर कर लेता है।
- अगर आप Google Docs जैसा प्रतिस्पर्धी बना रहे हैं: आपको distributed systems इंजीनियरों की टीम और सालों के विकास की ज़रूरत होगी। यह वीकेंड प्रोजेक्ट नहीं है। सहयोगात्मक एडिटिंग की ऊपरी सादगी के नीचे असाधारण जटिलता छिपी है।
ईमानदार आकलन
सहयोगात्मक एडिटिंग उन समस्याओं में से है जिनकी कठिनाई पहली नज़र में दिखती नहीं। सीधा रास्ता — दो यूज़र जो एक-दूसरे से न टकराने वाले एडिट करते हैं — लगभग किसी भी तरीके से चल जाता है। कठिन मामले — एक ही हिस्से में एक साथ एडिट, देर से sync होने वाली ऑफ़लाइन एडिटिंग, या जटिल डॉक्यूमेंट संरचनाएँ (टेबल, नेस्टेड लिस्ट, embedded objects) — OT और CRDTs के ट्रेड-ऑफ़ की गहरी समझ माँगते हैं।
कोई भी तरीका सीधे तौर पर बेहतर नहीं है। सर्वर हो तो OT सरल है, बिना सर्वर के कठिन। CRDTs बिना सर्वर चलते हैं, पर metadata का बोझ और tombstones का जमा होना साथ लाते हैं। दोनों को मुख्य एल्गोरिदम से आगे भी सावधान इंजीनियरिंग चाहिए — और वह इंजीनियरिंग (presence, persistence, permissions, undo) अक्सर convergence की समस्या से भी ज़्यादा कठिन होती है।
इंडस्ट्री धीरे-धीरे एक व्यावहारिक हाइब्रिड की ओर बढ़ रही है: डेटा मॉडल के लिए CRDTs (अपने-आप merge की गारंटी), और coordination (presence, permissions, garbage collection) के लिए एक सर्वर। इससे दोनों दुनिया का सर्वश्रेष्ठ मिलता है — गणितीय convergence के साथ व्यावहारिक infrastructure। लेकिन इससे दोनों दुनियाओं की जटिलता भी मिलती है, और यही वजह है कि 35 साल की रिसर्च के बाद भी सहयोगात्मक एडिटिंग एक कठिन समस्या बनी हुई है।


