সেশন ২৬

WhatsApp / Chat Design - ঘড়ি থেকে নীল tick পর্যন্ত

এই সেশনের লক্ষ্য: WhatsApp এর মতো একটা chat app শুরু থেকে design করা। Requirements আর সংখ্যার হিসাব থেকে শুরু করে WebSocket, connection server, message routing, tick এর পেছনের receipt, offline user, ordering, group chat, presence আর E2E encryption পর্যন্ত। শেষে বুঝবেন কোথায় চাপ পড়ে, আর কোন trade-off মেনে নিতে হয়।

০০ যে প্রশ্নগুলোর উত্তর খুঁজবো

Chat app আমরা সবাই দিনে শতবার খুলি। একটা message লিখি, send চাপি, আর এক মুহূর্ত পরে পাশে একটা tick আসে। এত সহজ একটা কাজের পেছনে আসলে অনেকগুলো সমস্যা লুকিয়ে আছে। কোটি কোটি phone এর সাথে একসাথে যোগাযোগ রাখতে হয়। দুর্বল network এ message হারানো চলে না। আর ৪০ জনের group এ সবার screen এ একই order দেখাতে হয়।

এই সেশনে আমরা “আড্ডা” নামের একটা chat app শুরু থেকে design করবো, ঠিক WhatsApp এর মতো। স্টেজ ০৯ এর বাকি সেশনগুলোর মতো এখানেও একই ধাপ মেনে চলবো: requirements, সংখ্যার হিসাব, API, data model, high-level design, তারপর একটা একটা করে গভীরে যাওয়া। শেষে দেখবো কোথায় চাপ পড়ে, আর কোন trade-off মেনে নিতে হয়।

  • ১ আড্ডা app কে কী কী করতে হবে? (functional আর non-functional requirements)
  • ২ কত message, কত connection, কত storage? (back-of-envelope estimation)
  • ৩ নতুন message আনতে HTTP polling কেন যথেষ্ট নয়? (long polling, WebSocket)
  • ৪ লাখো WebSocket connection কোথায় খোলা থাকে, আর message ঠিক connection server এ যায় কীভাবে? (connection server, routing)
  • ৫ এক tick, দুই tick আর নীল tick কীভাবে কাজ করে? (delivery receipt)
  • ৬ Offline user এর message কোথায় অপেক্ষা করে, আর order ঠিক থাকে কীভাবে? (data model, message ordering)
  • ৭ ৪০ জনের group এ একটা message সবার কাছে যায় কীভাবে? (group fan-out)
  • ৮ Server কীভাবে জানে কে online, আর offline হলে কী হয়? (presence, push notification)
  • ৯ Server কি message পড়তে পারে, আর পুরো design এর bottleneck কোথায়? (E2E encryption, trade-off)

০১ আড্ডা app কে কী কী করতে হবে?

শুক্রবার, রাত নয়টা। নেত্রকোনার এক গ্রামে নানু বারান্দায় বসে আছেন। মোবাইলের কোণায় সিগন্যালের একটা মাত্র দাগ, সেটাও একবার আসে, একবার যায়। অনেকক্ষণ ধরে তিনি একটা একটা অক্ষর টাইপ করলেন: “সবাই কেমন আছো?” তারপর পাঠিয়ে দিলেন পরিবারের ৪০ জনের group এ। Message এর পাশে একটা ছোট ঘড়ি দেখা দিলো। নানু চশমাটা ঠিক করে screen এর দিকে তাকিয়ে রইলেন। কখন একটা tick আসবে, কখন দুইটা, কখন নীল হবে।

ঐ group এ আছে ঢাকার রিমি, যে WiFi তে বসে পড়ছে। আছেন দুবাইয়ের মামা, যার phone এখন বন্ধ। আর আছে আরো ৩৬ জন, কেউ জেগে, কেউ ঘুমিয়ে। নানুর এক লাইনের এই message টা সবার কাছে পৌঁছানোই এই সেশনের পুরো গল্প।

যেকোনো design শুরু করার আগে ঠিক করে নিতে হয় system টা কী করবে, আর কতটা ভালোভাবে করবে। প্রথমটাকে বলে functional requirement, দ্বিতীয়টাকে non-functional requirement।

Functional requirements (কী করবে)

Voice call, video call, status আর payment এই সেশনের বাইরে রাখছি।

Non-functional requirements (কতটা ভালোভাবে করবে)

Interview এর প্রথম পাঁচ মিনিট

Interview এ “WhatsApp design করুন” শুনেই box আঁকা শুরু করবেন না। আগে interviewer এর সাথে মিলিয়ে নিন কোন feature ভেতরে আর কোনটা বাইরে। Group এর সর্বোচ্চ সাইজ, history কতদিন থাকবে, encryption লাগবে কিনা, এই তিনটা উত্তর পুরো design বদলে দিতে পারে।

০২ কত message, কত connection, কত storage?

সেশন ২৩ এ আমরা back-of-envelope estimation দেখেছি: খাতার কোণায় মোটা দাগে হিসাব করে বোঝা, system টা কত বড়। এখানেও আগে সংখ্যাগুলো বের করে নিই। ধরুন আড্ডা এখন বেশ জনপ্রিয়, প্রতিদিন ৫ কোটি মানুষ app টা খোলে:

১. ধরে নিই
   DAU (প্রতিদিন app খোলে): 5 কোটি = 50M
   একজন দিনে message পাঠায়: 40টা

২. Message rate
   দিনে: 50M × 40 = 2,000M message (200 কোটি)
   গড়ে প্রতি second: 2,000M ÷ 86,400 ≈ 23,000 msg/s
   Peak (ঈদের রাত, গড়ের 5 গুণ): 23,000 × 5 ≈ 1,15,000 msg/s

৩. একসাথে খোলা connection
   Peak এ DAU এর 40% online: 50M × 0.4 = 20M (2 কোটি) connection
   একটা connection server ধরে ~1 লাখ: 20M ÷ 1,00,000 = 200 server
   Headroom সহ (50% বাড়তি): ~300 server
   প্রতি connection ~20 KB memory: 1,00,000 × 20 KB ≈ 2 GB RAM প্রতি server

৪. Heartbeat
   2 কোটি connection, প্রতি 30s এ 1টা ping: 20M ÷ 30 ≈ 6,70,000 ping/s

৫. Storage (শুধু text, ছবি আর video আলাদা)
   একটা message ~200 bytes (body + metadata)
   দিনে: 2,000M × 200 B = 400 GB
   বছরে: 400 GB × 365 ≈ 146 TB
   3টা replica সহ: 146 TB × 3 ≈ 440 TB

৬. Bandwidth
   Peak: 1,15,000 msg/s × ~1 KB (frame + encryption) ≈ 115 MB/s ≈ ~1 Gbps

এই হিসাব থেকে তিনটা জিনিস চোখে পড়ে।

প্রথমত, message এর সংখ্যা ভয় পাওয়ার মতো নয়। ঈদের রাতে peak এ সেকেন্ডে প্রায় ১ লাখ ১৫ হাজার message, আর bandwidth মাত্র ~1 Gbps। ভালোভাবে shard করা একটা database cluster এটা সামলাতে পারে।

দ্বিতীয়ত, আসল চ্যালেঞ্জ হলো একসাথে খোলা ২ কোটি connection। প্রতিটা online phone এর সাথে server কে সারাক্ষণ একটা লাইন খোলা রাখতে হয়, message আসুক বা না আসুক। এজন্যই প্রায় ৩০০টা server শুধু connection ধরে রাখার কাজে লাগে।

তৃতীয়ত, একটা চমকে দেওয়ার মতো সংখ্যা আছে। শুধু “আমি এখনো আছি” জানানোর heartbeat ই সেকেন্ডে প্রায় ৬ লাখ ৭০ হাজার, আসল message এর প্রায় ছয় গুণ। Heartbeat হলো নিয়মিত পাঠানো ছোট একটা signal, যেটা দিয়ে বোঝা যায় অন্য পাশ এখনো চালু আছে (সেশন ০২)। এই সংখ্যাটা মাথায় রাখুন, সেকশন ০৮ এ এটা কাজে লাগবে।

হিসাবটা কী শেখালো

Chat app এর কঠিন অংশ message সংখ্যায় নয়। কঠিন অংশ হলো কোটি কোটি connection সারাক্ষণ খোলা রাখা, আর প্রতিটা message এর সাথে আসা tick আর heartbeat এর বাড়তি traffic সামলানো।

০৩ নতুন message আনতে HTTP polling কেন যথেষ্ট নয়?

এখন পর্যন্ত আমরা যত system দেখেছি, সবগুলো HTTP এর নিয়মে চলে। Client request পাঠায়, server উত্তর দেয়। এই নিয়মে server নিজে থেকে কিছু বলতে পারে না, client না জিজ্ঞেস করা পর্যন্ত চুপ থাকে। অথচ chat app এ রিমির phone জানেই না নানু কখন message পাঠাবেন।

Short polling: বারবার জিজ্ঞেস করা

সবচেয়ে সহজ উপায় হলো phone টা কয়েক সেকেন্ড পরপর server কে জিজ্ঞেস করবে: “নতুন কিছু আছে?” এর নাম polling (বা short polling)। হিসাবটা দেখুন। ২ কোটি online phone প্রতি ৫ সেকেন্ডে একবার জিজ্ঞেস করলে server এ সেকেন্ডে ৪০ লাখ request আসে। অথচ সেকেন্ডে আসল message মাত্র ২৩ হাজারের মতো। মানে প্রতি ১৭০টার বেশি request এর মধ্যে মাত্র একটায় কাজের কিছু থাকে, বাকিগুলোর উত্তর “নেই”।

ক্ষতিটা নানুর phone এও পড়ে। প্রতিটা খালি request এর সাথে কয়েকশো byte এর header যায়, battery খরচ হয়, আর 2G তে প্রতিটা request এ এক সেকেন্ডের মতো সময় লাগে। তার উপর message গড়ে আড়াই সেকেন্ড দেরিতে পৌঁছায়, কারণ পরের প্রশ্নের জন্য অপেক্ষা করতে হয়।

Long polling: উত্তর না আসা পর্যন্ত ধরে রাখা

একটু বুদ্ধিমান উপায় হলো long polling। Phone request পাঠায়, কিন্তু server সাথে সাথে “নেই” বলে না। নতুন message না আসা পর্যন্ত, বা ধরুন ৩০ সেকেন্ড পার না হওয়া পর্যন্ত, request টা ধরে রাখে। Message এলে তখনই উত্তর দেয়। Phone উত্তর পেয়েই আবার নতুন request পাঠায়।

এতে খালি উত্তর অনেক কমে যায়, message ও তাড়াতাড়ি পৌঁছায়। তবু সমস্যা থেকে যায়। প্রতিটা message এর পর নতুন একটা HTTP request বানাতে হয়। দুই request এর ফাঁকে message এলে সেটা আলাদা করে সামলাতে হয়। আর নানুর message পাঠানোর জন্য আলাদা আরেকটা request লাগে, কারণ ঐ ধরে রাখা request টা শুধু এক দিকে কাজ করে।

WebSocket: একবার খুলে রাখা লাইন

WebSocket হলো এমন একটা connection, যেটা একবার খুললে খোলাই থাকে, আর দুই পাশই যেকোনো সময় কথা বলতে পারে। শুরুটা হয় একটা সাধারণ HTTP request দিয়ে, যার header এ লেখা থাকে Upgrade: websocket। Server রাজি হলে 101 Switching Protocols উত্তর দেয়। তারপর থেকে সেই TCP connection টাই chat এর লাইন হয়ে যায়। প্রতিটা message (যাকে বলে frame) এর সাথে মাত্র কয়েক byte এর বাড়তি তথ্য যায়, পুরো HTTP header নয়।

Short polling Long polling WebSocket Phone Server Phone Server Phone Server আছে? নেই 1টা message আছে? ধরে রাখে 1টা message আবার আছে? 30s অপেক্ষা timeout, খালি Upgrade: websocket 101 Switching খোলা connection message message message প্রতি 5s এ নতুন request বেশিরভাগ উত্তর খালি message এলে সাথে সাথে উত্তর কিন্তু প্রতিবার নতুন request একটাই connection খোলা থাকে server নিজেই push করে
Short polling এ phone বারবার জিজ্ঞেস করে, আর বেশিরভাগ উত্তর আসে খালি। Long polling এ server request টা ধরে রাখে, message এলে তবেই উত্তর দেয়; কিন্তু প্রতিটা উত্তরের পর phone কে নতুন request পাঠাতে হয়। WebSocket এ একবার connection খুলে রাখা হয়, তারপর দুই দিক থেকেই যেকোনো সময় message যায়।
পদ্ধতিMessage কত দ্রুত আসেখালি requestServer এর কাজ
Short pollingগড়ে কয়েক সেকেন্ড দেরিঅনেক বেশিসেকেন্ডে লাখো খালি request
Long pollingপ্রায় সাথে সাথেকমপ্রতিটা message এ নতুন request
WebSocketসাথে সাথেনেইconnection খোলা রাখার memory
কেন WebSocket

Chat এ server কেই আগে কথা বলতে হয়: “রিমি, আপনার জন্য একটা message এসেছে।” HTTP এ server আগে কথা বলতে পারে না, WebSocket এ পারে। তাই chat, live score, online game এর মতো জায়গায় WebSocket ই স্বাভাবিক পছন্দ।

০৪ লাখো WebSocket connection কোথায় খোলা থাকে, আর message ঠিক connection server এ যায় কীভাবে?

Connection server: যে শুধু লাইন ধরে রাখে

যে server গুলো phone এর WebSocket connection খোলা রাখে, তাদের বলবো connection server। হিসাব অনুযায়ী একেকটা connection server প্রায় ১ লাখ connection ধরে, আর আমাদের লাগে প্রায় ৩০০টা। এদের কাজ খুব সীমিত রাখা হয়: phone থেকে frame নেওয়া, ভেতরের service এ দেওয়া, আর ভেতর থেকে আসা message phone এ push করা। Message কোথায় store হবে, কাকে যাবে, এসব সিদ্ধান্ত নেয় পেছনের Chat Service।

এখানে সেশন ০২ এর একটা শিক্ষা উল্টে যায়। সেখানে আমরা web server কে stateless বানিয়েছিলাম, যাতে যেকোনো request যেকোনো server এ যেতে পারে। কিন্তু WebSocket একটা নির্দিষ্ট machine এর সাথে বাঁধা থাকে। রিমির connection Conn-17 এ খোলা থাকলে রিমির কাছে message পাঠানোর একমাত্র পথ Conn-17। মানে connection server stateful: তার ভেতরে কোন phone আছে, এই তথ্যটাই তার state।

USERS LOAD BALANCER CONNECTION SERVICES STORAGE নানু (2G) রিমি (ঢাকা) মামা (Dubai) WebSocket Load Balancer (L4) TCP connection ভাগ করে দেয় Conn-3 ~1 লাখ connection Conn-17 ~1 লাখ connection Conn-41 ~1 লাখ connection Chat Service seq দেয়, store করে, পাঠায় Push Service offline হলে notification কে কোথায়? Registry (Redis) user → conn server, presence Message Store shard by chat_id FCM / APNs Google, Apple এর push
প্রতিটা phone একটা WebSocket খুলে রাখে কোনো একটা connection server এর সাথে। Connection server শুধু connection ধরে রাখে; আসল কাজ করে Chat Service: seq দেয়, Message Store এ রাখে, Registry দেখে বোঝে প্রাপক কোন connection server এ আছে। প্রাপক offline হলে Push Service FCM / APNs দিয়ে notification পাঠায়।

Load balancer এখানেও আছে, তবে তার কাজ একটু আলাদা। নতুন phone connect করতে এলে সে ঠিক করে কোন connection server এ পাঠাবে, সাধারণত যার connection সবচেয়ে কম তাকে। একবার connection বসে গেলে সেটা ঘণ্টার পর ঘণ্টা ঐ server এই থাকে।

Routing: রিমি কোন server এ আছে?

নানুর message Conn-3 এ এলো। রিমি আছে Conn-17 এ। Chat Service কীভাবে জানবে? এর জন্য একটা ছোট খাতা রাখা হয়, যার নাম connection registry। রিমির phone Conn-17 এ connect হলেই Conn-17 Redis এ লিখে রাখে: rimi → conn-17। রিমি disconnect হলে entry টা মুছে যায়।

প্রেরক প্রাপক নানুর phone গ্রাম, দুর্বল 2G রিমির phone ঢাকা, WiFi 1. WebSocket এ send 5. push Conn-3 নানুর connection এখানে Conn-17 রিমির connection এখানে 2. chat service কে দেয় 4. Conn-17 এ forward Chat Service store করে, seq 1042 3. রিমি কোথায়? conn-17 Registry (Redis) rimi → conn-17 mama → (নেই, offline)
নানুর phone আর রিমির phone দুটো আলাদা connection server এ বসে আছে। Chat Service message টা store করে, তারপর Registry তে খোঁজে রিমির connection কোথায়। উত্তর পেয়ে message টা ঠিক Conn-17 এ পাঠায়, আর Conn-17 সেটা রিমির খোলা WebSocket এ push করে দেয়।

Chat Service প্রথমে message টা store করে। তারপর registry তে রিমিকে খোঁজে, আর message টা সরাসরি Conn-17 এ পাঠায়। Server গুলোর মধ্যে এই পাঠানোটা সরাসরি call দিয়েও হতে পারে, আবার প্রতিটা connection server এর নামে একটা pub/sub topic দিয়েও হতে পারে। Pub/sub এ একজন একটা topic এ message ছাড়ে, আর সেই topic এ যারা subscribe করে আছে সবাই পায় (সেশন ১০)।

API: phone আর server কী বলাবলি করে

বেশিরভাগ কথা চলে খোলা WebSocket এর উপর দিয়ে, ছোট JSON frame এ। যেখানে সাথে সাথে push এর দরকার নেই, যেমন পুরোনো message আনা বা ছবি upload, সেখানে সাধারণ HTTP request ই যথেষ্ট:

# WebSocket frame: phone → server (নতুন message)
{ "type": "send", "client_msg_id": "n-7f3a",
  "chat_id": "fam-40", "body": "<ciphertext>" }

# server → নানুর phone (store হয়েছে: এক tick)
{ "type": "ack", "client_msg_id": "n-7f3a", "seq": 1042 }

# server → রিমির phone (নতুন message এলো)
{ "type": "msg", "chat_id": "fam-40", "seq": 1042,
  "from": "nanu", "body": "<ciphertext>" }

# রিমির phone → server (receipt: 1042 পর্যন্ত সব পেয়েছি / পড়েছি)
{ "type": "delivered", "chat_id": "fam-40", "up_to_seq": 1042 }
{ "type": "read",      "chat_id": "fam-40", "up_to_seq": 1042 }

# সাধারণ HTTP (REST), যেখানে সাথে সাথে push এর দরকার নেই
GET  /v1/chats/fam-40/messages?after_seq=1038
     reconnect এর পরে বাকি message আনা
POST /v1/media
     ছবি বা video upload, message এ যায় শুধু link

দুটো field আলাদা করে খেয়াল করুন। client_msg_id টা phone নিজে বানায়, server এ পৌঁছানোর আগেই। আর seq টা দেয় server, store করার সময়। এই দুটো নম্বর পরের তিনটা সেকশনের প্রায় সব সমস্যার সমাধান করবে।

০৫ এক tick, দুই tick আর নীল tick কীভাবে কাজ করে?

নানু এখনো screen এর দিকে তাকিয়ে আছেন। প্রতিটা tick তাকে আসলে একটা করে খবর দেয়। এই খবরগুলোকে বলে delivery receipt: প্রাপকের দিক থেকে প্রেরকের কাছে ফেরত আসা ছোট একটা message, যেটা জানায় message টা কতদূর পৌঁছালো।

নানু যা দেখেননামমানে
ছোট ঘড়িpendingmessage এখনো phone থেকে বের হয়নি, বা server এর ack আসেনি
এক ধূসর ticksentserver message store করেছে
দুই ধূসর tickdeliveredরিমির phone message পেয়েছে
দুই নীল tickreadরিমি chat খুলে message দেখেছে

পুরো যাত্রাটা ধাপে ধাপে দেখুন:

নানুর phone গ্রাম, 2G Conn-3 connection Chat Service + Message Store Conn-17 connection রিমির phone ঢাকা নানু দেখেন pending sent delivered read (নীল) 1 send send 2 store, seq 1042 3 ack 4 forward push 5 delivered delivered 6 রিমি chat খোলে read 7 read
বাম পাশে নানুর screen এর tick, ডান দিকে তার পেছনের ঘটনা। Server message store করে ack দিলে এক tick। রিমির phone message পেয়ে delivered জানালে দুই tick। রিমি chat খুললে read receipt যায়, আর tick নীল হয়। প্রতিটা tick আসলে একটা আলাদা ছোট message, যেটা উল্টো পথে নানুর কাছে ফেরে।

প্রথম tick আসে server এর ACK থেকে। ACK মানে acknowledgment, অর্থাৎ “পেয়েছি” জানানো (সেশন ০৮)। Server message টা Message Store এ লিখে ফেলার পরেই ack পাঠায়, তার আগে নয়। তাই এক tick দেখলে নানু নিশ্চিন্ত থাকতে পারেন: message টা আর হারাবে না।

দ্বিতীয় আর তৃতীয় tick আসে রিমির phone থেকে। Message হাতে পেয়েই রিমির phone একটা delivered receipt পাঠায়। রিমি chat টা খুললে যায় read receipt। Chat Service এই receipt গুলো উল্টো পথে নানুর connection server এ পাঠিয়ে দেয়।

একটা ছোট কৌশল traffic অনেক কমায়। প্রতিটা message এর জন্য আলাদা receipt না পাঠিয়ে phone বলে “1042 পর্যন্ত সব পড়েছি” (up_to_seq)। রিমি সকালে উঠে ৫০টা message একসাথে পড়লে ৫০টা নয়, একটাই read receipt যায়।

দুর্বল network এ ack হারালে কী হয়?

নানুর গ্রামের 2G তে message টা server এ পৌঁছে গেলো, store ও হলো। কিন্তু ফেরার পথে ack টা হারিয়ে গেলো। নানুর phone এর দিক থেকে দেখলে কোনো উত্তর আসেনি, তাই ঘড়ি ঘুরতেই থাকে। কয়েক সেকেন্ড পর phone message টা আবার পাঠায়।

এখানেই client_msg_id কাজে আসে। Phone দ্বিতীয়বারও একই ID পাঠায়। Server দেখে, n-7f3a ID এর message তো আগেই store হয়েছে। তাই নতুন copy না বানিয়ে আগের seq 1042 দিয়েই আবার ack পাঠায়। এটাই idempotency: একই কাজ দুইবার হলেও ফল একবারের মতো (সেশন ০৮)। Duplicate ঠেকানোর আরো গভীর কৌশল সেশন ১৮ এ দেখবো।

নানুর phone message পাঠায় client_msg_id = n-7f3a (phone নিজে বানায়) Server store করে, seq 1042 তারপর ack পাঠায় ack পৌঁছালো 2G তে ack হারালো এক tick দেখায় কাজ শেষ ঘড়ি ঘুরতেই থাকে 10s পরে একই n-7f3a আবার যায় Server: n-7f3a আগেই আছে নতুন copy না, আবার ack 1042
দুর্বল network এ message পৌঁছালেও ack পথে হারিয়ে যেতে পারে। নানুর phone তখন ধরে নেয় message যায়নি, আর একই client_msg_id দিয়ে আবার পাঠায়। Server ID টা চিনে ফেলে, তাই group এ "সবাই কেমন আছো?" দুইবার দেখায় না।
Tick গুলো আসলে প্রতিশ্রুতি

এক tick মানে server কথা দিচ্ছে message টা আর হারাবে না। তাই store শেষ হওয়ার আগে কখনো ack পাঠানো যাবে না। আগে ack পাঠিয়ে পরে store করতে গিয়ে server crash করলে নানু এক tick দেখবেন, অথচ message টা কারো কাছেই পৌঁছাবে না।

০৬ Offline user এর message কোথায় অপেক্ষা করে, আর order ঠিক থাকে কীভাবে?

দুবাইয়ে মামা রাতের shift এ কাজ করছেন, phone টা লকারে বন্ধ। নানুর message তার কাছে এখন পৌঁছানোর কোনো পথ নেই। তাহলে message টা কোথায় থাকবে?

Data model

প্রতিটা message server এ জমা থাকে, যতক্ষণ না প্রাপক সেটা নিয়ে যায়। এই কৌশলকে বলে store-and-forward: আগে জমা রাখা, প্রাপককে পাওয়া গেলে পৌঁছে দেওয়া, ঠিক ডাকঘরের মতো। আড্ডার data model এ তিনটা table:

messages              partition key: chat_id   sort key: seq
  chat_id         "fam-40"
  seq             1042               এই chat এর ভেতরে ক্রমিক নম্বর
  client_msg_id   "n-7f3a"           retry এলে duplicate চেনার জন্য
  sender_id       "nanu"
  body            <ciphertext>
  created_at      2026-04-10 21:00:03   (server এর ঘড়ি)

chat_members          partition key: chat_id
  chat_id, user_id
  last_delivered_seq   1042          এই পর্যন্ত phone এ পৌঁছেছে
  last_read_seq        1040          এই পর্যন্ত পড়া হয়েছে

user_chats (inbox)    partition key: user_id
  user_id, chat_id, last_seq, updated_at    কোন chat এ নতুন কিছু আছে

messages table এর partition key হলো chat_id। মানে একটা chat এর সব message একই shard এ থাকে (সেশন ০৫ এর shard key)। “fam-40 group এর শেষ ৫০টা message” আনার query টা তখন একটা shard থেকেই শেষ হয়, অনেকগুলো shard ঘুরতে হয় না। আর shard এর ভেতরে message গুলো seq অনুযায়ী সাজানো থাকে।

এই ধরনের data তে লেখার পরিমাণ অনেক, আর পড়া হয় মূলত “এই chat এর এই seq এর পরেরগুলো” ধরনের query দিয়ে। তাই বড় chat app গুলো সাধারণত Cassandra বা HBase এর মতো wide-column database ব্যবহার করে। এরা partition key ধরে data ভাগ করে, আর ভেতরে sort key অনুযায়ী সাজিয়ে রাখে।

মামার phone চালু হলে কী হয়

1 নানুর message আসে, seq 1042 Registry তে মামার কোনো connection নেই, মানে offline 2 Message Store এ নিরাপদে থাকে মামার inbox: fam-40 এ নতুন message আছে 3 Push Service → FCM / APNs → মামার phone notification: "আড্ডা তে নতুন message" 4 মামা phone খোলেন, WebSocket connect হয় phone বলে: fam-40 তে seq 1038 এর পরের গুলো দিন 5 Server 1039 থেকে 1042 পাঠায় phone delivered জানায়, নানুর screen এ দুই tick
মামা offline থাকলে message হারায় না, Message Store এ অপেক্ষা করে। Push notification মামার phone কে জাগিয়ে দেয়। Phone জানে সে শেষ কোন seq পর্যন্ত পেয়েছে, তাই connect হয়েই শুধু বাকিগুলো চেয়ে নেয়।

মামার phone জানে fam-40 group এ সে শেষ কোন seq পর্যন্ত পেয়েছিলো, ধরুন 1038। Connect হয়েই phone বলে “1038 এর পরেরগুলো দিন”। Server 1039 থেকে 1042 পাঠিয়ে দেয়। কিছু বাদ পড়ে না, কিছু দুইবারও আসে না। শেষে phone delivered receipt পাঠায়, আর নানুর screen এ মামার জন্যও দুই tick হয়।

Message ordering: কার ঘড়ি ঠিক?

এবার একটা মজার সমস্যা। নানুর phone এর ঘড়ি ৭ মিনিট পিছিয়ে চলে, কেউ কখনো ঠিক করে দেয়নি। মামার phone এর ঘড়ি আবার ১০ মিনিট পিছিয়ে। প্রত্যেকের phone এর সময় দিয়ে message সাজালে কী হয়, দেখুন:

Phone এর ঘড়ি দিয়ে sort Server এর seq দিয়ে sort 8:52 মামা: ভালো আছি আম্মা মামার ঘড়ি 10 মিনিট পিছিয়ে 8:53 নানু: সবাই কেমন আছো? নানুর ঘড়ি 7 মিনিট পিছিয়ে 9:01 রিমি: ভালো আছি নানু রিমির ঘড়ি ঠিক উত্তরটা প্রশ্নের আগে দেখায় 1042 নানু: সবাই কেমন আছো? server এ পৌঁছায় 9:00:03 1043 রিমি: ভালো আছি নানু server এ পৌঁছায় 9:01:10 1044 মামা: ভালো আছি আম্মা server এ পৌঁছায় 9:02:40 40 জনের phone এ একই order
প্রত্যেকের phone এর ঘড়ি একটু আলাদা। সেই সময় দিয়ে সাজালে মামার উত্তর নানুর প্রশ্নের আগে বসে যায়। Server প্রতিটা chat এ message পৌঁছানোর ক্রমে একটা নম্বর (seq) দেয়। সবাই সেই নম্বর দিয়ে সাজালে সবার screen এ একই order দেখায়।

মামার “ভালো আছি আম্মা” উত্তরটা নানুর প্রশ্নের আগে বসে গেছে। কেউ পড়লে ভাববে মামা প্রশ্ন আসার আগেই উত্তর দিয়েছেন। তাই phone এর ঘড়ির উপর ভরসা করা যায় না।

সমাধান হলো message ordering এর দায়িত্ব server কে দেওয়া। প্রতিটা chat এর জন্য server একটা counter রাখে। নতুন message এলে counter এক বাড়ে, আর সেই নম্বরটাই হয় message এর seq। একটা chat এর সব message একই shard এ যায়, তাই counter টাও এক জায়গায় থাকে। দুজন একসাথে পাঠালেও যার message আগে server এ পৌঁছায়, সে ছোট নম্বর পায়। ৪০ জনের phone একই নম্বর দেখে সাজায়, তাই সবার screen এ একই order।

খেয়াল করুন, আমরা শুধু একটা chat এর ভেতরের order নিশ্চিত করছি। নানুর group আর রিমির অফিসের group এর message এর মধ্যে কোনটা আগে, সেটা কেউ জানতে চায় না। পুরো পৃথিবীর জন্য একটা order রাখতে গেলে একটা counter এ সব message এর চাপ পড়তো, যেটা বিশাল bottleneck। Bottleneck মানে system এর সবচেয়ে সরু জায়গা, যেটা পুরো system এর গতি আটকে দেয় (সেশন ০১)।

সময় নয়, নম্বর

আলাদা আলাদা machine এর ঘড়ি কখনো পুরোপুরি মেলে না। তাই order ঠিক করতে timestamp নয়, একটা জায়গা থেকে দেওয়া ক্রমিক নম্বর ব্যবহার করুন। Chat এর ক্ষেত্রে সেই জায়গা হলো chat টার নিজের shard।

০৭ ৪০ জনের group এ একটা message সবার কাছে যায় কীভাবে?

১:১ chat এ message যায় একজনের কাছে। কিন্তু নানুর group এ ৪০ জন, মানে message টা নানু বাদে ৩৯ জনের কাছে যেতে হবে। একটা জিনিস অনেক জায়গায় ছড়িয়ে দেওয়াকে বলে fan-out। সেশন ২৫ এ feed এর জন্য fan-out on write আর fan-out on read দেখেছি। Chat এ পথটা মোটামুটি পরিষ্কার: message store হয় একবার, কিন্তু delivery যায় প্রত্যেক member এর কাছে আলাদা করে।

নানু 1টা message Chat Service store 1 বার, seq 1042 member 40, পাবে 39 Conn-17 15 জন online Conn-22 12 জন online Conn-41 8 জন online Push Service 4 জন offline 15 phone 12 phone 8 phone 4 phone পরে sync 1 message → 39 delivery + 39 delivered + 39 read = 117 event প্রতিটা connection server পায় একটা batch, প্রতিটা member এর জন্য আলাদা call নয়
Message টা store হয় একবারই। তারপর Chat Service 39 জন প্রাপককে তাদের connection server অনুযায়ী ভাগ করে, আর প্রতিটা server এ একটা করে batch পাঠায়। যারা offline, তাদের জন্য যায় push notification। একটা message থেকে ফিরতি receipt সহ প্রায় 117টা event তৈরি হয়।

Chat Service এর কাজ চার ধাপে:

  1. Store একবার: fam-40 এর shard এ seq 1042 হিসেবে message টা লেখে। ৩৯টা copy নয়, একটাই।
  2. Member list আনে: chat_members table থেকে ৪০ জনের তালিকা।
  3. Connection server ধরে ভাগ করে: registry দেখে বোঝে কে কোন connection server এ। ধরুন ১৫ জন Conn-17 এ, ১২ জন Conn-22 এ, ৮ জন Conn-41 এ, আর ৪ জন offline।
  4. প্রতিটা server এ একটা batch পাঠায়: ৩৯টা আলাদা call এর বদলে তিনটা call যায়। Offline ৪ জনের জন্য যায় push notification।

এবার হিসাবটা দেখুন। একটা message এর জন্য ৩৯টা delivery, তারপর ৩৯টা delivered receipt, পরে ৩৯টা read receipt। সব মিলিয়ে একটা “সবাই কেমন আছো?” থেকে প্রায় ১১৭টা event তৈরি হয়। Group যত বড়, এই গুণফল তত বড়। এজন্যই WhatsApp এর মতো app group এর সাইজে একটা সীমা রাখে।

Group এ tick এর নিয়মও একটু আলাদা। নানু দুই tick দেখবেন তখনই, যখন ৩৯ জনের সবার phone এ message পৌঁছাবে। নীল হবে যখন সবাই পড়বে। কে কে পড়েছে, সেটা message এর info screen এ আলাদা করে দেখা যায়। বড় group এ প্রতিটা receipt সাথে সাথে প্রেরকের কাছে না পাঠিয়ে কিছুক্ষণ জমিয়ে একসাথে পাঠানো হয়।

Group এ সবচেয়ে বড় খরচ receipt

Group message নিজে একবারই store হয়। কিন্তু প্রতিটা member এর delivered আর read receipt আলাদা আলাদা event। তাই বড় group design করার সময় message এর চেয়ে receipt এর traffic নিয়ে বেশি ভাবতে হয়।

০৮ Server কীভাবে জানে কে online, আর offline হলে কী হয়?

Presence: online আর last seen

কে এখন online আছে, আর না থাকলে শেষ কখন ছিলো, এই তথ্যকে বলে presence। প্রথমে মনে হতে পারে, connection খোলা থাকলেই online। কিন্তু নানুর phone এর সিগন্যাল চলে গেলে TCP connection টা সাথে সাথে বন্ধ হয় না। Server কিছুক্ষণ বুঝতেই পারে না যে অন্য পাশে আর কেউ নেই।

তাই phone নিয়মিত একটা heartbeat পাঠায়, ধরুন প্রতি ৩০ সেকেন্ডে। সবচেয়ে সহজ design এ Registry তে নানুর entry টা রাখা হয় একটা TTL সহ। TTL (time to live) মানে একটা সময়সীমা, যেটা পার হলে key নিজে থেকেই মুছে যায় (সেশন ০৪)। প্রতিটা heartbeat এ TTL আবার ৬০ সেকেন্ডে ফিরে যায়। Heartbeat বন্ধ হলে ৬০ সেকেন্ড পরে key মুছে যায়, আর নানুকে offline দেখায়। শেষ heartbeat এর সময়টাই হয় তার last seen।

9:00 9:01 9:02 9:03 9:04 9:05 সিগন্যাল নেই heartbeat TTL 60s status online offline online last seen 9:01:30 প্রতি 30s এ একটা heartbeat heartbeat ফিরলো, আবার online Registry এর key এ TTL থাকে, heartbeat না এলে key নিজে মুছে যায়
নানুর phone প্রতি 30 সেকেন্ডে একটা heartbeat পাঠায়। 9:01:30 এর পরে সিগন্যাল চলে যায়। শেষ heartbeat এর 60 সেকেন্ড পরে Registry এর TTL শেষ হয়, আর নানুকে offline দেখায়। রিমির screen এ তখন লেখা আসে "last seen 9:01"।

সেকশন ০২ এর সেই চমকে দেওয়া সংখ্যাটা এখানে কাজে লাগে। সেকেন্ডে ৬ লাখ ৭০ হাজার heartbeat এর প্রতিটা Redis এ লিখলে Redis এর উপর অকারণ চাপ পড়ে। তাই বড় scale এ heartbeat থামে connection server এই। Connection server নিজেই হিসাব রাখে কার heartbeat ঠিকমতো আসছে। Registry তে লেখে শুধু তখন, যখন কারো অবস্থা বদলায়: ৬০ সেকেন্ড heartbeat না এলে online থেকে offline, আবার এলে offline থেকে online।

গ্রামের দুর্বল সিগন্যালে আরেকটা ঝামেলা হয়। নানুর phone এক মিনিটে দশবার connect হয়, আবার কেটে যায়। প্রতিবার online/offline বদলালে রিমির screen এ লেখাটা মিটমিট করতে থাকে। তাই status বদলানোর আগে কয়েক সেকেন্ড অপেক্ষা করা হয়। অবস্থাটা স্থির হলে তবেই সবাইকে জানানো হয়।

আর presence এর খবর সবাইকে পাঠানোর দরকার নেই। নানুর contact list এ ২০০ জন থাকলেও তার online হওয়ার খবর শুধু তাদের কাছে যায়, যারা এই মুহূর্তে নানুর সাথে chat খুলে বসে আছে। এরা pub/sub এর মতো করে নানুর presence এ subscribe করে থাকে।

Push notification: ঘুমন্ত phone কে জাগানো

মামার phone বন্ধ, বা app টা background এ ঘুমিয়ে আছে। Phone এর operating system battery বাঁচাতে ঘুমন্ত app এর WebSocket কেটে দেয়। আড্ডার server তখন সরাসরি phone এ পৌঁছাতে পারে না।

এখানে কাজে আসে push notification। Android phone এ Google এর FCM, আর iPhone এ Apple এর APNs সারাক্ষণ phone এর সাথে নিজেদের একটা connection খোলা রাখে। আড্ডার Push Service তাদের বলে “মামার phone এ একটা notification পৌঁছে দিন”। Phone এ notification দেখা যায়, আর app জেগে উঠে WebSocket খোলে। তারপর আগের সেকশনের মতো seq দিয়ে বাকি message গুলো নিয়ে নেয়।

Notification এ message এর আসল লেখা থাকে না। থাকে শুধু “আড্ডা তে নতুন message”, বা encrypted একটা ছোট টুকরা। কারণটা পরের সেকশনে।

Presence নিখুঁত হয় না

Presence সবসময় একটু পিছিয়ে থাকে। Heartbeat এর ফাঁক আর TTL এর কারণে কেউ চলে যাওয়ার পরেও এক মিনিট পর্যন্ত online দেখাতে পারে। Chat app এ এটা মেনে নেওয়া যায়, কারণ presence দেখানোর জিনিস, কোনো জরুরি সিদ্ধান্ত এর উপর নির্ভর করে না।

০৯ Server কি message পড়তে পারে, আর পুরো design এর bottleneck কোথায়?

E2E encryption: খামের ভেতরটা শুধু প্রাপক দেখে

নানুর message টা কয়েকটা server ঘুরে রিমির কাছে যায়। এই পথে কেউ কি message টা পড়তে পারে? E2E encryption (end-to-end encryption) থাকলে পারে না। এই পদ্ধতিতে message lock হয় প্রেরকের phone এ, আর খোলে শুধু প্রাপকের phone এ। মাঝের কোনো server, এমনকি আড্ডার নিজের server ও, লেখাটা পড়তে পারে না।

ধারণাটা সহজ। প্রত্যেকের phone এ দুটো key থাকে। Public key সবাইকে দেওয়া যায়, আড্ডার server ও একটা copy রাখে। Private key কখনো phone ছেড়ে যায় না। Public key দিয়ে lock করা জিনিস শুধু সেই জোড়ার private key দিয়েই খোলে। নানুর phone রিমির public key দিয়ে message lock করে পাঠায়। পথে message টা দেখতে অর্থহীন অক্ষরের মতো, যাকে বলে ciphertext। রিমির phone নিজের private key দিয়ে সেটা খোলে।

নানুর phone সবাই কেমন আছো? রিমির public key দিয়ে lock (encrypt) করে আড্ডা Server x9#Qa7!kL2v... লেখাটা পড়তে পারে না দেখে শুধু: কে, কাকে, কখন রিমির phone নিজের private key দিয়ে unlock (decrypt) করে সবাই কেমন আছো? Private key কখনো phone ছেড়ে যায় না। Server এর কাছে থাকে শুধু public key।
E2E encryption এ message lock হয় নানুর phone এ, আর খোলে শুধু রিমির phone এ। মাঝের server message টা পৌঁছে দেয়, কিন্তু ভেতরে কী লেখা আছে জানে না। Server শুধু খামের বাইরের লেখাটা দেখে: কে পাঠালো, কাকে, কখন।

বাস্তবে WhatsApp এর Signal protocol আরো জটিল। প্রতিটা message এর জন্য নতুন key বানায়, আর group এর জন্য আলাদা ব্যবস্থা রাখে। কিন্তু system design এর জন্য মূল কথা এটুকুই: server শুধু খাম বহন করে, খামের ভেতরটা দেখে না। Server দেখে শুধু খামের বাইরের লেখা: কে পাঠালো, কাকে, কখন, কত বড়।

এর কিছু cost আছে। Server message এর ভেতরে search করতে পারে না। Spam বা ক্ষতিকর content নিজে থেকে ধরতে পারে না। নতুন phone নিলে পুরোনো message server থেকে পড়ে আনা যায় না, user কে নিজের encrypted backup রাখতে হয়।

Bottleneck আর trade-off

এখন পুরো design টা একসাথে দেখি:

এই design যা দেয়

  • প্রায় সাথে সাথে delivery: খোলা WebSocket দিয়ে server নিজেই message push করে, polling এর অপেক্ষা নেই।
  • Message হারায় না: ack পাওয়া প্রতিটা message Message Store এ থাকে, offline user পরে seq দিয়ে sync করে নেয়।
  • Duplicate দেখায় না: client_msg_id দিয়ে server retry চিনে ফেলে।
  • সবার কাছে একই order: প্রতিটা chat এ server একটা করে seq দেয়।
  • Privacy: E2E encryption এর কারণে server message এর লেখা পড়তে পারে না।

Downside আর bottleneck গুলো

  • Connection server stateful: একটা server down হলে বা deploy হলে প্রায় 1 লাখ phone একসাথে reconnect করতে চায়।
  • Registry এর উপর ভরসা: Registry ভুল বা পুরোনো তথ্য দিলে message ভুল server এ যায়, তাই TTL আর retry লাগে।
  • বড় group মানে hot chat: একটা chat এর সব message একটা shard এ থাকে, তাই অনেক সক্রিয় বড় group সেই shard এ চাপ ফেলে।
  • Receipt আর presence এর traffic: আসল message এর চেয়ে tick, heartbeat আর typing signal এর সংখ্যা অনেক বেশি।
  • E2E এর cost: server message search করতে পারে না, নতুন phone এ পুরোনো history আনা কঠিন।

সবচেয়ে বিপজ্জনক মুহূর্তটা আসে deploy এর সময়। একটা connection server restart হলে তার ১ লাখ phone একসাথে reconnect করতে ছোটে। সবাই একই সেকেন্ডে চেষ্টা করলে load balancer আর registry এর উপর হঠাৎ অনেক চাপ পড়ে। তাই phone গুলো reconnect এর আগে একটা এলোমেলো সময় অপেক্ষা করে, যাকে বলে jitter (সেশন ০৯)। আর deploy এর সময় server গুলো একটা একটা করে ধীরে ধীরে restart করা হয়।

ঈদের রাতের হিসাবও মাথায় রাখতে হয়। রাত বারোটা বাজার সাথে সাথে সবাই “ঈদ মোবারক” পাঠায়, message rate গড়ের পাঁচ গুণ হয়ে যায়। হঠাৎ এই ভিড় সামলাতে Chat Service আর Message Store এর মাঝে একটা queue রাখা যায় (সেশন ০৮)। তবে store এর ack না আসা পর্যন্ত এক tick দেওয়া যাবে না, তাই queue টাকেও durable হতে হবে, মানে disk এ লেখা থাকতে হবে।

১০ মগজে প্রেশার দিন

আগে নিজে ভাবুন। তারপর দেখুন সবাই সাধারণত কী উত্তর দেয়, কোথায় সেটা অসম্পূর্ণ, আর আসল কথাটা কী।

প্রশ্ন ১: সেশন ০২ এ আমরা server কে stateless বানিয়েছিলাম। Chat app এর connection server কে stateless বানানো যায় না কেন?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“Load balancer দিয়ে round-robin করলেই হয়। Message যেকোনো server এ পাঠিয়ে দিলে সেই server রিমির কাছে পৌঁছে দেবে।”

এই চিন্তা HTTP request এর জন্য ঠিক ছিলো। কিন্তু WebSocket এর একটা বৈশিষ্ট্য এখানে বাদ পড়ে গেছে।

যে জায়গাটা যোগ করা দরকার

WebSocket হলো একটা নির্দিষ্ট machine এর সাথে খোলা একটা TCP connection। রিমির connection Conn-17 এ থাকলে অন্য কোনো server রিমির কাছে কিছু পাঠাতে পারে না। তাই কোন user কোন server এ আছে, এই state টা কোথাও রাখতেই হবে। ভালো design এ এই state টাকে যতটা সম্ভব ছোট রাখা হয়। Connection server শুধু socket ধরে রাখে, আর কে কোথায় সেটা লেখা থাকে registry তে। বাকি সব logic থাকে stateless Chat Service এ। কোনো connection server crash করলে তার phone গুলো অন্য server এ reconnect করে, registry আপডেট হয়, আর seq দিয়ে বাদ পড়া message চলে আসে।

মূল কথা

State পুরোপুরি বাদ দেওয়া যায় না, শুধু এক কোণায় আটকে রাখা যায়। Connection server এ শুধু socket, registry তে শুধু ঠিকানা, আর বাকি সব stateless।

প্রশ্ন ২: Message deliver হওয়ার পরে server কি সেটা মুছে ফেলবে, নাকি চিরকাল রেখে দেবে?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“রেখে দেওয়াই ভালো। Storage এখন সস্তা, আর user নতুন phone এ পুরোনো message পেলে খুশি হয়।”

কথাটা আংশিক ঠিক। কিন্তু দুই পথেই কিছু পাওয়া যায়, কিছু হারাতে হয়।

যে জায়গাটা যোগ করা দরকার

এটা product এর সিদ্ধান্ত, আর দুটো পথই বাস্তবে চলে। WhatsApp server কে ডাকঘরের মতো ব্যবহার করে। Message deliver হলেই server থেকে মুছে যায়, আর না পৌঁছালে বড়জোর ৩০ দিন থাকে। এতে storage অনেক কমে, privacy বাড়ে। কিন্তু নতুন phone এ history আনতে user কে নিজের backup রাখতে হয়। অন্যদিকে Messenger বা Telegram এর মতো app সব history server এ রাখে। এতে যেকোনো device এ সব message পাওয়া যায়, কিন্তু আমাদের হিসাবে বছরে ~440 TB storage লাগে। Server এ data যত বেশিদিন থাকে, তার সুরক্ষার দায়ও তত বাড়ে।

Interview এ যা বলবেন

“রাখবো, নাকি মুছবো” প্রশ্নের উত্তর দেওয়ার আগে জিজ্ঞেস করুন: multi-device sync লাগবে কিনা, আর E2E encryption আছে কিনা। এই দুটো উত্তরই storage এর design ঠিক করে দেয়।

প্রশ্ন ৩: "WebSocket তো TCP এর উপর চলে, আর TCP reliable। তাই message হারানো বা duplicate নিয়ে ভাবার দরকার নেই।" কথাটা ঠিক?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“হ্যাঁ, TCP প্রতিটা packet এর ack নেয়, হারালে আবার পাঠায়, order ও ঠিক রাখে। তাই app এর আলাদা কিছু করার নেই।”

TCP সত্যিই এগুলো করে, তবে শুধু একটা connection যতক্ষণ চালু থাকে, ততক্ষণ।

যে জায়গাটা যোগ করা দরকার

নানুর 2G connection কেটে গেলে সেই connection এর ভেতরে পথে থাকা frame গুলোর খবর আর কেউ রাখে না। TCP জানে না message টা server এর app পর্যন্ত পৌঁছেছিলো কিনা। তাই reliability টা app এর নিজের স্তরে বানাতে হয়। Server store শেষ করে নিজের ack দেয়। Ack না এলে phone একই client_msg_id দিয়ে আবার পাঠায়, আর server ID দেখে duplicate বাদ দেয়। Reconnect এর পরে phone শেষ পাওয়া seq এর পর থেকে বাকিগুলো চেয়ে নেয়। এই তিনটা মিলে message হারায়ও না, দুইবারও দেখায় না।

মনে রাখার মতো

TCP একটা connection এর ভেতরের নিশ্চয়তা দেয়, পুরো যাত্রার নয়। পুরো যাত্রার নিশ্চয়তা আসে app এর ack, retry, client_msg_id আর seq থেকে।

১১ যে টার্মগুলো শিখলাম

টার্মমানে
Functional requirementSystem কী কী করবে, যেমন message পাঠানো বা tick দেখানো।
Non-functional requirementSystem কতটা ভালোভাবে করবে, যেমন latency, availability, privacy।
Polling (short polling)Client কয়েক সেকেন্ড পরপর server কে জিজ্ঞেস করে নতুন কিছু আছে কিনা।
Long pollingServer request টা ধরে রাখে, নতুন data এলে বা timeout হলে তবেই উত্তর দেয়।
WebSocketএকবার খুলে রাখা connection, যেখানে client আর server দুজনেই যেকোনো সময় message পাঠাতে পারে।
Connection serverযে server phone এর WebSocket connection খোলা রাখে আর message আনা নেওয়া করে। Stateful।
Connection registryকোন user কোন connection server এ আছে, তার তালিকা। সাধারণত Redis এ থাকে।
Delivery receiptপ্রাপকের দিক থেকে ফেরত আসা ছোট message, যেটা জানায় message delivered নাকি read হয়েছে।
client_msg_idPhone এর বানানো message ID, যেটা দিয়ে server retry করা duplicate চিনে ফেলে।
Store-and-forwardMessage আগে server এ জমা রাখা, প্রাপক reachable হলে পৌঁছে দেওয়া।
Message orderingএকটা chat এর message সবার screen এ একই ক্রমে দেখানো।
Sequence number (seq)প্রতিটা chat এ server এর দেওয়া ক্রমিক নম্বর, যেটা দিয়ে order আর offline sync দুটোই হয়।
Fan-out (group)একটা message group এর প্রত্যেক member এর কাছে আলাদা করে পৌঁছে দেওয়া।
Presenceকে এখন online আছে, আর না থাকলে শেষ কখন ছিলো (last seen)।
Push notificationFCM বা APNs এর মাধ্যমে ঘুমন্ত বা বন্ধ app কে জাগানোর notification।
E2E encryptionMessage প্রেরকের phone এ lock হয়, খোলে শুধু প্রাপকের phone এ। মাঝের server পড়তে পারে না।
Public key / Private keyPublic key দিয়ে lock করা যায়, খোলা যায় শুধু তার জোড়ার private key দিয়ে। Private key phone ছেড়ে যায় না।
CiphertextEncrypt করা লেখা, key ছাড়া যেটা অর্থহীন অক্ষরের মতো দেখায়।

১২ শর্ট সামারি

  1. Chat app এর আসল চ্যালেঞ্জ message এর সংখ্যা নয়, বরং কোটি কোটি connection সারাক্ষণ খোলা রাখা। তাই polling বাদ দিয়ে WebSocket ব্যবহার করা হয়, আর stateful connection server গুলো শুধু socket ধরে রাখে।
  2. Chat Service message store করে seq দেয়, তারপর connection registry দেখে প্রাপকের connection server এ পাঠায়। Store শেষ হলে তবেই ack যায়, আর সেটাই প্রথম tick।
  3. Delivered আর read receipt উল্টো পথে ফিরে দ্বিতীয় আর নীল tick আনে। client_msg_id retry এর duplicate ঠেকায়, আর per-chat seq দিয়ে order আর offline sync দুটোই ঠিক থাকে।
  4. Group এ message একবার store হয়ে connection server ধরে batch এ যায়। Presence চলে heartbeat আর TTL দিয়ে, offline user কে জাগায় push notification, আর E2E encryption এর কারণে server শুধু খাম বহন করে, ভেতরের লেখা পড়ে না।

১৩ মনে রাখুন