Mendesain chat system real-time: WebSocket connections untuk persistent connection, message storage di Cassandra/ScyllaDB (write-heavy), fan-out ke group members via message queue, dan presence service untuk online status

Setelah di episode 20 kita mendesain URL shortener, pada episode ini kita naik ke case study yang lebih kompleks: chat system seperti WhatsApp/Telegram. Chat system adalah tantangan unik: real-time (latency harus rendah), write-heavy (jutaan message per detik), dan persistent (message harus survive restart).
Mengapa chat system menarik untuk system design? Karena ia menggabungkan hampir semua konsep yang sudah kita pelajari: WebSocket untuk real-time, database write-heavy, message queue untuk fan-out, caching untuk presence, dan distributed storage untuk message persistence.
DAU: 100M
Messages per user per hari: 40
Total messages: 100M × 40 = 4B/hari = ~46K QPS
Peak: 3x = ~140K QPS
Message size: avg 100 bytes
Storage per hari: 4B × 100B = 400 GB/hari
Storage per tahun: 400GB × 365 = ~146 TB
Active connections: 100M concurrent (WebSocket)
Bandwidth: 4B × 100B / 86400 = ~46 MB/s averageWebSocket memungkinkan persistent, bidirectional connection — server bisa push message ke client tanpa client harus poll.
1. Maintain WebSocket connections dari clients
2. Receive message dari sender
3. Store message ke database
4. Route message ke recipient(s)
5. Handle reconnection dan message syncCassandra dipilih karena:
CREATE TABLE messages (
chat_id UUID,
message_id TIMEUUID, -- UUID v1 dengan timestamp
sender_id UUID,
content TEXT,
content_type TEXT, -- 'text', 'image', 'video'
created_at TIMESTAMP,
PRIMARY KEY (chat_id, message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);Query: SELECT * FROM messages WHERE chat_id = ? ORDER BY message_id DESC LIMIT 50 → 50 message terbaru.
User A kirim message ke Group X (256 members):
1. Save message ke DB
2. Publish event "message.sent" ke Kafka
3. Fan-out service:
- List semua member Group X
- Kirim message ke setiap member's connection
- Jika member offline → queue untuk push notificationChat Server → Kafka (message-events)
→ Fan-out Worker 1: handle user_id hash 0-25%
→ Fan-out Worker 2: handle user_id hash 25-50%
→ Fan-out Worker 3: handle user_id hash 50-75%
→ Fan-out Worker 4: handle user_id hash 75-100%Redis key: "user:{id}:status"
Value: "online" | "offline" | "last_seen:{timestamp}"
TTL: 30 detik (heartbeat setiap 10 detik)
Flow:
1. Client kirim heartbeat setiap 10 detik
2. Server update Redis: SET user:123:status "online" EX 30
3. Jika heartbeat berhenti → status jadi "offline" setelah 30 detik
4. Client lain subscribe ke presence channel → get status updateSent: message diterima server
Delivered: message sampai ke device recipient
Read: recipient buka message (read receipt)
Tracking:
- sent_to_server: timestamp
- delivered_to: [device_id: timestamp]
- read_by: [device_id: timestamp]Inti yang harus dibawa pulang:
Di episode 22 selanjutnya kita akan membahas case study: design news feed (Twitter/Instagram) — write fan-out vs read fan-out (push vs pull model), hybrid approach untuk celebrity accounts, dan feed ranking. News feed adalah case study tentang trade-off write amplification vs read amplification!