Collaborative Whiteboard (Figma-style) ডিজাইন
- ●শেয়ার্ড ক্যানভাসে অনেকে একসাথে শেপ আঁকবে, তাই কনফ্লিক্ট-ফ্রি মার্জই মূল সমস্যা।
- ●প্রতিটি শেপকে CRDT হিসেবে মডেল করলে অর্ডার-নিরপেক্ষভাবে merge হয়।
- ●Snapshot + delta sync, presence cursor, আর room-ভিত্তিক scaling দরকার।
ভাই, আজকে Figma/Miro-র মতো একটা Collaborative Whiteboard ডিজাইন করব। Google Docs ছিল লিনিয়ার টেক্সট; এখানে ক্যানভাসে স্বাধীন shape — তাই merge-এর গল্পটা আলাদা।
১. সমস্যা বোঝা (Requirements)
Functional:
- একটা শেয়ার্ড canvas-এ একাধিক ইউজার শেপ (rectangle, line, text, freehand) আঁকবে।
- আঁকা/সরানো/রিসাইজ রিয়েল-টাইমে সবার কাছে যাবে।
- Presence — কে কোথায় কার্সর রাখছে, কে কোন শেপ সিলেক্ট করেছে।
- Conflict-free merge — একসাথে এডিট করলেও কারো কাজ হারাবে না।
- Offline করলে পরে merge হবে।
Non-functional:
- লো লেটেন্সি — কার্সর/শেপ আপডেট ১০০ ms-এর কম দেরিতে।
- Eventual consistency — সবাই শেষে একই ক্যানভাস দেখবে।
- বড় বোর্ডেও (হাজার শেপ) স্মুথ পারফরম্যান্স।
ভাবো একটা বড় কাচের দেয়ালে অনেকে স্টিকি নোট লাগাচ্ছে। প্রতিটা নোট স্বাধীন — কেউ একটা সরালে আরেকজনের নোটে প্রভাব পড়ে না। দুজন একই নোট একসাথে সরালে? যার হাত শেষে সরিয়েছে (latest timestamp) তারটাই থাকে। এটাই LWW CRDT।
২. স্কেল আন্দাজ (Estimation)
ধরি:
- Concurrent users: 2M
- প্রতি রুমে গড়ে 5 জন → ~400K active room
- শেপ আপডেট রেট (drag): ~20 ops/sec/active user
- cursor move: ~30 events/sec/user (throttle করে 20-এ)
ops:
- 2M × 20 = 40M shape-ops/sec (peak, drag চলাকালীন)
- batching + throttle করে effective ~5-10M/sec
storage:
- গড় বোর্ড 500 শেপ × ~1 KB = 500 KB
- 50M বোর্ড → ~25 TB
- snapshot + recent delta hot storage
কানেকশন:
- 2M concurrent WebSocket
- ~40K WS/server → ~50 gateway server
মূল শিক্ষা: drag চলাকালীন op-burst সামলানো (throttle/batch) আর room fan-out-ই বটলনেক।
৩. API ডিজাইন
# REST (control)
POST /boards → নতুন বোর্ড
GET /boards/{id}/snapshot → latest snapshot + version
POST /boards/{id}/share
# WebSocket — wss://board/{id}
→ JOIN { boardId, userId, sinceVersion }
← INIT { snapshot, version, presence[] }
→ OP { type:"add|update|delete", shapeId, props, lamport }
← OP { ...broadcast, fromUser }
→ CURSOR { x, y, selection:[shapeId] }
← PRESENCE{ userId, cursor, color }
প্রতিটি op-এ একটা Lamport timestamp (বা hybrid logical clock) থাকে — LWW merge ও causal ordering-এর জন্য।
৪. ডেটা মডেল
| Entity | Field | ব্যাখ্যা |
|---|---|---|
| Board | boardId, title, ownerId, version | মেটাডেটা |
| Shape | shapeId, type, x, y, w, h, style, z | এক একটা অবজেক্ট |
| ShapeCRDT | shapeId, props(map), lamport, siteId, deleted | merge-able state |
| Snapshot | boardId, version, shapes[], ts | দ্রুত লোডের জন্য |
| Presence | userId, cursor, selection, color | ephemeral |
Choice: পুরো ক্যানভাসকে একটা CRDT map of shapes হিসেবে মডেল করি — key = shapeId, value = একটা per-property LWW register। শেপ add/delete-ও CRDT সেট অপারেশন (add-wins বা remove-wins পলিসি)। এতে কোনো সেন্ট্রাল lock লাগে না, merge গণিতই কনফ্লিক্ট সলভ করে।
- Snapshot → object storage।
- Delta/op-log → hot append store, boardId দিয়ে shard।
- Presence → Redis pub/sub, TTL।
৫. হাই-লেভেল ডিজাইন
কম্পোনেন্ট:
- Client — local CRDT replica রাখে, op তৈরি করে optimistic render করে।
- WebSocket Gateway — কানেকশন; একই boardId একই backend (consistent hashing)।
- Room/Board Server — per-board op relay, version bump, persist trigger।
- CRDT merge — মূলত client-side, server relay + validate করে (lightweight)।
- Persistence — snapshot + delta।
- Presence service — cursor broadcast।
ফ্লো:
User একটা rectangle আঁকল
→ client local CRDT-তে add (instant render)
→ OP { add, shapeId, props, lamport } পাঠায়
→ Board Server: validate, op-log-এ append, version++
→ সব peer-কে broadcast
→ peers নিজের CRDT-তে merge → একই ক্যানভাসে converge
→ পর্যায়ক্রমে snapshot নেওয়া হয়
লক্ষ্য করো — Google Docs-এ সার্ভার OT transform করত; এখানে merge logic CRDT-তে বিল্ট-ইন, সার্ভার মূলত relay + ordering + persistence সামলায়। তাই এটা পরিষ্কারভাবে স্কেল করে।
৬. গভীরে (Deep Dive)
শেপের জন্য CRDT
প্রতিটি শেপ একটা map CRDT: প্রতিটা প্রপার্টি (x, y, width, color...) একটা আলাদা LWW register যাতে (value, lamport, siteId) থাকে। দুজন একই শেপের ভিন্ন প্রপার্টি বদলালে (একজন color, একজন position) — কোনো কনফ্লিক্টই নেই, দুটোই merge হয়। একই প্রপার্টি বদলালে বেশি lamport (টাই হলে বড় siteId) জেতে। ফলে অর্ডার যাই হোক, সবাই একই deterministic রেজাল্ট পায়।
শেপ যোগ/মোছার জন্য একটা add-wins observed-remove set ব্যবহার করা যায়, যাতে "একজন মুছল, আরেকজন একই সময়ে এডিট করল" পরিস্থিতিতে predictable আচরণ থাকে।
Snapshot + Delta sync
পুরো op-log কখনো বড় হয়ে যায়। তাই পর্যায়ক্রমে (যেমন প্রতি ১০০০ op বা ৩০ সেকেন্ডে) একটা snapshot নেওয়া হয় = বর্তমান merged CRDT state। নতুন ইউজার join করলে: latest snapshot পায় + তার পরের delta অ্যাপ্লাই করে — পুরো ইতিহাস replay লাগে না। reconnect-এ ক্লায়েন্ট শুধু sinceVersion-এর পরের delta চায়।
Presence
Cursor আর selection খুব দ্রুত বদলায়, কিন্তু এগুলো persist করার দরকার নেই — তাই ephemeral channel (Redis pub/sub), throttle করে (~৫০ ms) ব্রডকাস্ট। প্রতিটি ইউজারকে একটা color দেওয়া হয় যাতে কার্সর আলাদা চেনা যায়।
freehand drawing বা fast drag-এ প্রতি পিক্সেলে একটা op পাঠালে নেটওয়ার্ক ভেসে যাবে। স্ট্রোককে batch/sample করো (যেমন প্রতি ১৬ ms বা প্রতি কয়েক পয়েন্টে একটা op), আর drag শেষ হলে final position-এর একটা commit পাঠাও — নইলে op-storm-এ রুম ল্যাগ করবে।
৭. বটলনেক ও স্কেলিং
- Op-storm (drag/freehand) — throttle, batch, intermediate op-কে coalesce।
- Big board (হাজার শেপ) — viewport-based rendering, শুধু দৃশ্যমান শেপ render; spatial index।
- Room fan-out — per-board actor + pub/sub; hot board হলে read-replica broadcast।
- CRDT metadata গ্রোথ — tombstone/old-version garbage collect, snapshot-এ compact।
- Sticky routing — same board same server, consistent hashing দিয়ে রিব্যালান্স।
- Cold storage — পুরোনো বোর্ড object storage-এ archive, প্রয়োজনে rehydrate।
৮. সারসংক্ষেপ
Collaborative Whiteboard-এ প্রতিটি শেপ স্বাধীন অবজেক্ট, তাই per-property LWW CRDT দিয়ে কনফ্লিক্ট-ফ্রি merge পাওয়া যায় — সার্ভার মূলত relay, ordering আর persistence করে। দ্রুত join-এর জন্য snapshot + delta, লাইভ feel-এর জন্য presence, আর স্কেলের জন্য room-ভিত্তিক sharding + throttling।
Google Docs (linear text → OT/sequence CRDT) আর Whiteboard (independent objects → map/LWW CRDT)-এর পার্থক্য ইন্টারভিউতে বললে তুমি আলাদা দাগ কাটবে। ডেটার গঠন (sequence vs set/map) ঠিক করে দেয় কোন CRDT/merge স্ট্র্যাটেজি লাগবে।
মিনি কুইজ
1. Figma-style হোয়াইটবোর্ডে শেপ মার্জের জন্য কোন মডেল সবচেয়ে উপযুক্ত?
2. নতুন ইউজার রুমে join করলে কীভাবে দ্রুত state পায়?
3. দুজন একই শেপ একসাথে move করলে LWW CRDT কী করে?