Studi kasus: chat (A)
Langkah 1–4: requirement, estimasi pesan dan koneksi, polling vs long polling vs WebSocket, data model per percakapan, dan desain v1
Sebelumnya, lesson 0032 menyelesaikan news feed: sistem read-heavy yang boleh terlambat beberapa detik. Chat berbeda di dua hal: server harus mendorong pesan ke client dalam hitungan ratus milidetik, dan urutan pesan di satu percakapan harus benar. Seperti news feed, soal ini dibagi dua: lesson ini langkah 1–4, lesson 0034 langkah 5.
Langkah 1 — Klarifikasi requirement
- Functional: pesan teks 1:1 dan grup kecil (sampai 100 anggota); indikator online (presence); pesan untuk user yang offline disampaikan saat ia kembali; riwayat tersimpan dan sama di semua perangkat user. Di luar scope: enkripsi end-to-end, media, panggilan suara.
- Skala (asumsi, tanyakan): 10 juta DAU, rata-rata 40 pesan terkirim per user per hari, 30% DAU online bersamaan pada puncak; riwayat disimpan selamanya.
- Non-functional: pesan sampai ke penerima yang online dalam < 500 ms; pesan yang sudah dikonfirmasi ke pengirim tidak boleh hilang; urutan pesan di satu percakapan sama untuk semua anggota.
Langkah 2 — Estimasi
| Besaran | Hitungan | Hasil |
|---|---|---|
| QPS pesan masuk | 107 × 40 ÷ 105 detik | ±4.000 pesan/detik; puncak 3× ≈ ±12.000 |
| Koneksi bersamaan | 30% × 107 | ±3 juta koneksi pada puncak |
| Storage | 4 × 108 pesan × ±200 byte per hari | ±80 GB/hari ≈ ±29 TB/tahun |
| Kalau memakai polling | 3 juta client ÷ polling tiap 2 detik | ±1,5 juta request/detik, > 99% kosong |
Dua angka menentukan soal ini. Baris terakhir menjawab pertanyaan protokol di langkah 3. Baris kedua, 3 juta koneksi yang harus tetap terbuka, menjadi deep dive di lesson 0034. Bandingkan dengan news feed: di sana yang mahal adalah QPS baca; di sini yang mahal adalah koneksi yang hidup lama.
Langkah 3 — API dan data model
Protokol: polling, long polling, atau WebSocket.
- Polling: client bertanya "ada pesan?" setiap beberapa detik. Sederhana, tetapi ±1,5 juta request/detik yang hampir semuanya kosong, dan latensi rata-rata setengah interval polling.
- Long polling: RFC 6202: "The server attempts to "hold open" (not immediately reply to) each HTTP request, responding only when there are events to deliver." [1] Latensi lebih baik, tetapi setiap pesan tetap satu siklus request/response baru, dan arah client → server butuh request terpisah.
- WebSocket: RFC 6455 dibuat untuk aplikasi "that need two-way communication with servers that does not rely on opening multiple HTTP connections".
Dengan teknik berbasis polling, RFC itu mencatat "The wire protocol has a high overhead, with each client-to-server message having an HTTP header." [2]
Koneksi dibuka dengan request HTTP
Upgradeyang dijawabHTTP/1.1 101 Switching Protocols, lalu koneksi TCP yang sama dipakai dua arah.
Pilih WebSocket untuk kirim dan terima pesan; ini pola yang sama dengan dokumentasi AWS API Gateway: "services can push data to clients without requiring clients to make an explicit request", dan WebSocket API "are often used in real-time applications such as chat applications". [3] Riwayat tetap lewat REST biasa, karena bisa di-cache dan di-paginate.
WebSocket wss://chat.example.com/v1/ws
client → server { "type": "send", "conversationId": "c_17", "clientMsgId": "a1b2…", "text": "halo" }
server → client { "type": "ack", "clientMsgId": "a1b2…", "seq": 5012 }
server → client { "type": "message", "conversationId": "c_17", "seq": 5012, "from": 42, "text": "halo" }
REST GET /v1/conversations/c_17/messages?beforeSeq=5012&limit=50 ← riwayat, cursor = seq
Tabel
messages PRIMARY KEY ((conversation_id), seq) — urut seq menurun
kolom: sender_id, client_msg_id, text, created_at
conversation_members (conversation_id, user_id)
user_conversations (user_id, conversation_id, last_read_seq) ← daftar chat + penanda sudah dibaca
Kenapa conversation_id sebagai partition key. Hampir semua query berbentuk "pesan terakhir di percakapan X". Dokumentasi Cassandra:
"the first field or component of a primary key is hashed to generate the partition key and the remaining fields or components are the clustering keys that are used to sort data within a partition." [4]
Satu percakapan = satu partition, terurut menurut seq. Bentuk key ini sama baiknya untuk PostgreSQL (primary key (conversation_id, seq)) di v1;
±29 TB/tahun baru membenarkan penyimpanan wide-column yang di-partisi, dan pindah ke sana tidak mengubah bentuk key.
clientMsgId dibuat client supaya kirim ulang setelah koneksi putus tidak membuat pesan ganda.
Latihan 1 — Protokol dan data model
Jawab tanpa scroll ke atas. Soal yang sama akan muncul lagi, diacak, di review 0035.
Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil review 0035.
Langkah 4 — High-level design (v1)
Desain v1 paling sederhana: satu chat server yang memegang semua koneksi WebSocket, plus satu database. Chat server menyimpan peta di memori: user_id → koneksi.
- Pengirim mengirim
send. Server memberi nomorseqberikutnya untuk percakapan itu, menyimpan pesan, lalu membalasack. Pesan dianggap terkirim setelah tersimpan, bukan sebelumnya. - Server mencari anggota percakapan di peta koneksi dan mendorong
messageke yang sedang terhubung. - Anggota yang offline tidak menerima apa pun saat itu. Ketika terhubung lagi, client mengirim
seqterakhir yang ia punya per percakapan, dan server mengirim semua pesan setelahnya dari database.
v1 ini sudah memenuhi semua requirement functional untuk satu server: urutan benar karena satu server memberi semua seq, dan pesan offline tidak hilang karena database menjadi sumber kebenaran.
Saat peluncuran (mis. puluhan ribu user online) ini cukup; ukur jumlah koneksi dan memori per koneksi di server itu sebelum menambah apa pun.
Latihan 2 — Gambar diagram v1 sendiri dulu
Gambar diagram v1 Anda sendiri dulu: koneksi mana yang hidup lama, kapan pesan disimpan relatif terhadap ack, dan bagaimana user offline mendapat pesannya. Setelah selesai, buka jawabannya.
Lihat jawaban
Di mana v1 berhenti cukup. Target skalanya ±3 juta koneksi bersamaan, jauh di atas yang wajar dipegang satu server, dan satu server juga titik gagal tunggal.
Begitu ada banyak chat server, muncul empat pertanyaan baru: bagaimana pesan berpindah dari server pengirim ke server penerima, bagaimana tahu siapa yang online, siapa yang memberi seq, dan bagaimana pesan offline sampai ke ponsel yang tertidur.
Latihan lisan — Ucapkan dengan suara keras
Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Rancang aplikasi chat untuk percakapan 1:1 dan grup kecil, 10 juta DAU. Mulai dari requirement sampai desain v1." (90 detik)
Bandingkan dengan jawaban contoh dan checklist-nya
"Scope-nya pesan teks 1:1 dan grup sampai 100 orang, status online, pesan untuk user offline disampaikan saat ia kembali, riwayat tersimpan di semua perangkat; enkripsi end-to-end dan media di luar scope. 10 juta DAU, 40 pesan per user per hari: sekitar 4.000 pesan per detik, puncak 12.000. Kalau 30% online di puncak, itu 3 juta koneksi bersamaan. Polling setiap 2 detik berarti 1,5 juta request per detik yang hampir semuanya kosong, jadi saya pakai WebSocket untuk kirim dan terima, dan REST untuk riwayat dengan cursor seq. Tabel messages di-partisi menurut conversation_id dengan clustering seq; storage sekitar 80 GB per hari, 29 TB per tahun. Desain v1: satu chat server WebSocket yang memberi seq, menyimpan pesan, lalu mendorongnya ke penerima yang terhubung; penerima yang offline mengambil pesan setelah seq terakhirnya saat reconnect. Batas v1 adalah jumlah koneksi per server; 3 juta koneksi itulah deep dive-nya."
Checklist: apakah Anda menyebut…
- requirement: 1:1 + grup kecil, presence, pesan offline, riwayat multi-device; yang di luar scope disebut?
- estimasi: ±4.000 pesan/detik (puncak ±12.000) dan ±3 juta koneksi bersamaan?
- protokol: WebSocket untuk kirim/terima, dengan alasan biaya polling (±1,5 juta request/detik kosong)?
- data model: messages dengan partition key conversation_id dan clustering seq; storage ±80 GB/hari?
- v1: satu chat server WebSocket + database, penerima offline mengambil pesan setelah seq terakhir?
- membenarkan komponen dengan angka (WebSocket karena biaya polling; batas v1 karena 3 juta koneksi)?
Lulus jika minimal 4 dari 6.
Desain v1 sudah berdiri, dan angka 3 juta koneksi sudah di tangan. Lanjut di lesson 0034: langkah 5, koneksi WebSocket stateful, presence, ordering, dan pesan offline.
Istilah: GLOSSARY.md (bagian "Studi kasus") · Indeks: semua lesson System Design
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "kapan Server-Sent Events cukup untuk chat?", "bagaimana grup 100 anggota mengubah estimasi?", atau "kenapa riwayat lewat REST dan bukan WebSocket?"
Sumber: [1] RFC 6202: Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP · [2] RFC 6455: The WebSocket Protocol · [3] AWS docs: API Gateway WebSocket APIs · [4] Apache Cassandra docs: Introduction to data modeling