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

Collaborative Whiteboard (Figma-style) ডিজাইন

13 মিনিটadvanced
এক নজরে
  • শেয়ার্ড ক্যানভাসে অনেকে একসাথে শেপ আঁকবে, তাই কনফ্লিক্ট-ফ্রি মার্জই মূল সমস্যা।
  • প্রতিটি শেপকে 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-এর জন্য।

৪. ডেটা মডেল

EntityFieldব্যাখ্যা
BoardboardId, title, ownerId, versionমেটাডেটা
ShapeshapeId, type, x, y, w, h, style, zএক একটা অবজেক্ট
ShapeCRDTshapeId, props(map), lamport, siteId, deletedmerge-able state
SnapshotboardId, version, shapes[], tsদ্রুত লোডের জন্য
PresenceuserId, cursor, selection, colorephemeral

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।

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

কম্পোনেন্ট:

  1. Client — local CRDT replica রাখে, op তৈরি করে optimistic render করে।
  2. WebSocket Gateway — কানেকশন; একই boardId একই backend (consistent hashing)।
  3. Room/Board Server — per-board op relay, version bump, persist trigger।
  4. CRDT merge — মূলত client-side, server relay + validate করে (lightweight)।
  5. Persistence — snapshot + delta।
  6. 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 কী করে?