System Design শেখো
সব কেস স্টাডি

Google Docs (Collaborative Editor) ডিজাইন

13 মিনিটadvanced
এক নজরে
  • একাধিক ইউজার একই ডকুমেন্ট রিয়েল-টাইমে এডিট করবে, তাই কনফ্লিক্ট রেজল্যুশনই হলো মূল চ্যালেঞ্জ।
  • OT (Operational Transformation) আর CRDT — দুটিই কনকারেন্ট এডিট মার্জ করার দুই বড় কৌশল।
  • WebSocket দিয়ে লো-লেটেন্সি সিঙ্ক, presence cursor, আর offline edit সাপোর্ট দিতে হবে।

ভাই, আজকে আমরা একটা সিগনেচার প্রশ্ন ধরব — Google Docs। শুনতে সহজ, কিন্তু এর ভেতরে রিয়েল-টাইম কোলাবরেশনের সবচেয়ে কঠিন সমস্যাগুলো লুকানো। চলো ইন্টারভিউ ফ্রেমওয়ার্ক ধরে আগাই।

১. সমস্যা বোঝা (Requirements)

প্রথমে স্কোপ ঠিক করি। Functional requirements:

  • একই ডকুমেন্ট একাধিক ইউজার একসাথে এডিট করতে পারবে।
  • প্রতিটি কিস্ট্রোক প্রায় তাৎক্ষণিকভাবে অন্যদের স্ক্রিনে দেখা যাবে।
  • কে কোথায় আছে — cursor presence আর সিলেকশন দেখা যাবে।
  • নেট চলে গেলে offline edit করা যাবে, পরে রিকানেক্ট করলে মার্জ হবে।
  • ডকুমেন্টের version history থাকবে।

Non-functional:

  • লো লেটেন্সি — টাইপ করার পর অন্যের স্ক্রিনে ১০০ ms-এর কম দেরিতে দেখা গেলে ভালো।
  • High availability — ডক হারানো যাবে না।
  • Consistency — সব ইউজার শেষমেশ একই ডকুমেন্ট দেখবে (Eventual Consistency)।
সহজ উদাহরণ

একটা সাদা বোর্ডে চারজন একসাথে লিখছে। একজন লাইনের শুরুতে শব্দ ঢোকাল, আরেকজন একই সময়ে শেষে। বোর্ড যদি জাদুর হয় — সবার কলম একে অপরের লেখা ঠেলে সরিয়ে দেয় যাতে কারো লেখা মুছে না যায়। OT/CRDT হলো সেই জাদু।

২. স্কেল আন্দাজ (Estimation)

ধরি:
- Daily Active Users: 100M
- কনকারেন্ট এডিটিং সেশন: peak 5M
- গড়ে এক ডক-এ 2 জন একসাথে → ~2.5M সক্রিয় ডক

কিস্ট্রোক রেট:
- একজন টাইপ করে ~5 char/sec (অ্যাকটিভ থাকলে)
- 5M ইউজার × 5 = 25M ops/sec (peak, theoretical)
- বাস্তবে batching করে → effective ~2-3M ops/sec

কানেকশন:
- 5M concurrent WebSocket connection
- এক সার্ভার ~50K WS ধরলে → ~100 connection server

স্টোরেজ:
- গড় ডক 100 KB, 1B ডক → 100 TB
- version history সহ ~3-5x → ~400 TB
- op-log (compaction-এর আগে) আলাদা hot storage

মূল শিক্ষা: কানেকশন আর op-throughput-ই বটলনেক, raw storage নয়।

৩. API ডিজাইন

REST দিয়ে ডক CRUD, কিন্তু এডিট সিঙ্ক হবে WebSocket-এ।

# REST (control plane)
POST   /docs                      → নতুন ডক তৈরি
GET    /docs/{docId}              → ডক snapshot + version
GET    /docs/{docId}/history      → version history
POST   /docs/{docId}/share        → পারমিশন

# WebSocket (data plane) — wss://edit/{docId}
→ JOIN      { docId, userId, baseVersion }
← INIT      { snapshot, version, presence[] }
→ OP        { ops:[...], clientVersion, opId }
← ACK       { opId, serverVersion }
← OP        { ops:[...], serverVersion, fromUser }   # broadcast
→ CURSOR    { position, selection }
← PRESENCE  { userId, cursor, color }

প্রতিটি OP-এ opId (client-unique) থাকে যাতে retry-তে duplicate না হয় (Idempotency)।

৪. ডেটা মডেল

EntityFieldব্যাখ্যা
DocumentdocId, title, ownerId, currentVersionমেটাডেটা
SnapshotdocId, version, content, createdAtনির্দিষ্ট ভার্সনের ফুল কপি
OperationopId, docId, version, type(insert/delete), pos, char, userIdএক একটা এডিট
PresencedocId, userId, cursorPos, color, lastSeenephemeral, in-memory

Choice: আমরা op-log + periodic snapshot মডেল নেব। প্রতিটা এডিট একটা append-only operation। প্রতি N অপারেশনে (বা সময় ইন্টারভালে) একটা snapshot নেই যাতে নতুন ইউজার join করলে পুরো লগ replay করতে না হয়।

  • Snapshot → blob storage / object store (S3 ধরনের)।
  • Op-log → হট DB (যেমন একটা partitioned append store, docId দিয়ে shard)।
  • Presence → Redis-এর মতো in-memory, TTL দিয়ে auto-expire।

৫. হাই-লেভেল ডিজাইন

কম্পোনেন্টগুলো:

  1. Client (browser) — local copy রাখে, op তৈরি করে, optimistic-ভাবে নিজের স্ক্রিনে দেখায়।
  2. WebSocket Gateway — কানেকশন ধরে, একই docId-এর ইউজারদের একই backend-এ রুট করে (sticky / consistent hashing)।
  3. Collaboration Server (per-doc) — একটা ডকের জন্য authoritative। OT transform বা CRDT merge এখানে হয়, version bump করে, সবাইকে broadcast করে।
  4. Persistence layer — op-log + snapshot লেখে।
  5. Presence service — cursor/selection ব্রডকাস্ট, Redis pub/sub।

ফ্লো:

User টাইপ করল
  → client local-এ অ্যাপ্লাই (optimistic)
  → OP পাঠায় Collaboration Server-এ (clientVersion সহ)
  → server: এই op-কে concurrent ops-এর সাপেক্ষে transform করে
  → version++ , op-log-এ persist
  → ACK পাঠায় sender-কে
  → transform-করা op সব peer-কে broadcast
  → peers অ্যাপ্লাই করে, একই state-এ converge

একটা ডকের সব এডিট এক জায়গায় (single writer / per-doc actor) সিরিয়ালাইজ করা সবচেয়ে নিরাপদ — তাই version সবসময় একটাই সত্য।

৬. গভীরে (Deep Dive)

OT (Operational Transformation)

মূল আইডিয়া: দুটো কনকারেন্ট অপারেশন থাকলে একটাকে অন্যটার সাপেক্ষে transform করা। ধরো ডক = "HAT"।

  • User A: position 0-তে "C" insert → "CHAT"
  • User B: একই সময়ে position 0-তে "B" insert

A-এর op আগে সার্ভারে পৌঁছাল। এখন B-এর op-কে transform করতে হবে: যেহেতু A ইতিমধ্যে index 0-তে একটা char ঢুকিয়েছে, B-এর insert position ০ থেকে ১ হবে। ফলাফল consistent থাকে।

OT শক্তিশালী কিন্তু transform function লেখা কঠিন — প্রতিটি অপারেশন-জোড়ার জন্য transform(opA, opB) সঠিক হতে হবে (TP1/TP2 প্রপার্টি)। এ কারণে Google Docs একটা সেন্ট্রাল সার্ভারে OT চালায় যাতে অর্ডার নিয়ন্ত্রণ সহজ হয়।

CRDT (Conflict-free Replicated Data Type)

CRDT ভিন্ন পথে যায়: প্রতিটা character-কে একটা globally unique, sortable identifier দেয় (যেমন fractional index + siteId)। insert মানে নতুন id তৈরি, delete মানে tombstone মার্ক। যেহেতু অপারেশনগুলো commutative, যে কোনো অর্ডারে অ্যাপ্লাই করলে একই ডকুমেন্ট আসে — কোনো transform লাগে না।

দিকOTCRDT
সেন্ট্রাল সার্ভার লাগে?কার্যত হ্যাঁনা (P2P-ও সম্ভব)
অপারেশন সাইজছোটবড় (id metadata)
মেমরিকমবেশি (tombstone)
অফলাইন মার্জকঠিনসহজ

আজকের অনেক টুল (Figma, Yjs-ভিত্তিক এডিটর) CRDT বেছে নেয় কারণ offline ও P2P সহজ হয়।

Offline edit

ক্লায়েন্ট অফলাইনে op জমা রাখে একটা local queue-তে, প্রতিটায় baseVersionopId। রিকানেক্ট করলে সব op পাঠায়; সার্ভার missed ops পাঠায় client-কে; দুপক্ষ merge করে converge করে। CRDT হলে এটা ন্যাচারাল; OT হলে সার্ভারকে অনেক transform চালাতে হয়।

সাবধান

Optimistic UI ভুল করলে ইউজার দেখবে তার লেখা "লাফ দিচ্ছে" (jumpy cursor)। ACK আসার আগ পর্যন্ত local state রাখো, কিন্তু server op এলে নিজের pending ops-কে rebase/transform করতে ভুলো না — নইলে cursor ভুল জায়গায় চলে যাবে।

৭. বটলনেক ও স্কেলিং

  • Hot document — একটা ভাইরাল ডকে হাজার এডিটর। সমাধান: read-only viewer-দের আলাদা broadcast path, শুধু কয়েকজন editor write path-এ।
  • WebSocket fan-out — এক ডকের সব peer-কে broadcast। per-doc actor + Redis pub/sub দিয়ে ছড়াও।
  • Op-log গ্রোথ — periodic snapshot নিয়ে পুরোনো op compaction/archive করো।
  • Sticky routing — একই docId-এর সবাই একই server-এ; consistent hashing দিয়ে রিব্যালান্স।
  • Presence storm — cursor আপডেট খুব বেশি; throttle করো (যেমন ৫০ ms-এ একবার)।

৮. সারসংক্ষেপ

Google Docs-এর হৃদয় হলো কনকারেন্ট এডিট মার্জ। সেন্ট্রাল সার্ভারে চাইলে OT, ডিস্ট্রিবিউটেড/অফলাইন-ফার্স্ট চাইলে CRDT। ট্রান্সপোর্ট হিসেবে WebSocket, ডকের নিরাপত্তায় op-log + snapshot, আর ইউজার এক্সপেরিয়েন্সে presence + optimistic UI

টিপস

ইন্টারভিউতে OT vs CRDT-এর ট্রেড-অফ পরিষ্কার বলতে পারলেই অর্ধেক জেতা। মনে রাখো: OT সার্ভার-কেন্দ্রিক, CRDT মার্জ-কেন্দ্রিক। তোমার requirement (offline দরকার? P2P দরকার?) দিয়ে চয়েস জাস্টিফাই করো।

মিনি কুইজ

1. দুজন ইউজার একই পজিশনে একসাথে টাইপ করলে OT কী করে?

2. CRDT-এর মূল সুবিধা কী?

3. Offline edit সিঙ্ক করার সময় কোন প্রপার্টি জরুরি?