Studi kasus: news feed (A)
Langkah 1–4: requirement, estimasi read-heavy, API dengan cursor, data model follow, dan desain v1 fan-out on read
Sebelumnya, lesson 0030 menyelesaikan satu soal dalam satu lesson. News feed lebih besar, jadi dibagi dua: lesson ini mengerjakan langkah 1–4 sampai desain v1, dan lesson 0032 mengerjakan langkah 5 (deep dive fan-out). Soal ini klasik karena jawabannya bergantung hampir sepenuhnya pada angka: desain yang tepat untuk 100 ribu user salah untuk 10 juta, dan sebaliknya.
Langkah 1 — Klarifikasi requirement
- Functional: user membuat post (teks; gambar disimpan di object storage, di luar scope), follow akun lain, dan melihat home feed: post dari akun yang di-follow, urut terbaru dulu. Di luar scope: ranking dengan machine learning, iklan, komentar.
- Skala (asumsi, tanyakan): 10 juta DAU; setiap user rata-rata 1 post per hari dan membuka feed 10 kali per hari; rata-rata follow 200 akun; akun terbesar punya 10 juta follower.
- Non-functional: feed terbuka cepat (mis. p99 < 500 ms); post baru boleh muncul di feed follower beberapa detik kemudian (eventual consistency diterima); availability lebih penting daripada konsistensi kuat.
Pertanyaan "berapa follower akun terbesar?" terlihat kecil, tetapi jawabannya menentukan deep dive di lesson 0032. Tanyakan di langkah 1, jangan menunggu interviewer menyebutnya.
Langkah 2 — Estimasi
| Besaran | Hitungan | Hasil |
|---|---|---|
| QPS tulis (post) | 107 post ÷ 105 detik | ±100 QPS; puncak 3× ≈ ±300 QPS |
| QPS baca (feed) | 107 × 10 ÷ 105 detik | ±1.000 QPS; puncak ≈ ±3.000 QPS |
| Rasio baca:tulis | 1.000 ÷ 100 | 10:1, read-heavy |
| Storage post | 107 × ±1 KB per hari | ±10 GB/hari ≈ ±3,6 TB/tahun |
| Biaya baca v1 | 3.000 feed/detik × 200 akun di-follow | ±600.000 pencarian index per detik |
Rasio 10:1 menempatkan soal ini di keluarga read-heavy: deep dive akan berfokus pada jalur baca feed. Graf sosial di skala besar memang sangat timpang ke arah baca; paper TAO dari Facebook melaporkan bahwa "TAO can process a billion reads and millions of writes each second." [1] Baris terakhir tabel adalah angka yang paling penting: berapa pekerjaan database untuk satu pembukaan feed. Simpan angka itu; lesson 0032 dimulai dari sana.
Langkah 3 — API dan data model
POST /v1/posts { "text": "…" } → 201 { "postId": 9001 }
POST /v1/users/77/follow → 204
GET /v1/feed?limit=20&cursor=<created_at,id post terakhir>
→ 200 { "posts": [ … 20 post … ], "nextCursor": "2026-10-31T09:12:03Z,8995" }
Tabel
posts (id PK, author_id, text, created_at)
INDEX (author_id, created_at DESC)
follows (follower_id, followee_id, created_at, PK (follower_id, followee_id))
INDEX (followee_id) ← daftar follower, dipakai di lesson 0032
Kenapa cursor, bukan OFFSET. Dokumentasi PostgreSQL: "The rows skipped by an OFFSET clause still have to be computed inside the server; therefore a large OFFSET might be inefficient." [2]
Feed juga terus bertambah di atas: dengan OFFSET 20, satu post baru menggeser semua halaman sehingga user melihat post yang sama dua kali.
Cursor menyimpan posisi post terakhir (created_at, lalu id sebagai pemutus seri) dan halaman berikutnya mulai tepat dari sana.
Kenapa index (author_id, created_at). "A multicolumn B-tree index can be used with query conditions that involve any subset of the index's columns, but the index is most efficient when there are constraints on the leading (leftmost) columns." [3]
Query feed selalu menyaring author_id, lalu mengurutkan menurut waktu, jadi urutan kolom itu cocok dengan bentuk query.
Latihan 1 — Estimasi dan biaya baca
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: fan-out on read. Saat post dibuat, hanya satu baris yang ditulis ke posts. Feed dirakit saat dibaca:
SELECT p.* FROM posts p
JOIN follows f ON f.followee_id = p.author_id
WHERE f.follower_id = :me
AND (p.created_at, p.id) < (:cursor_time, :cursor_id)
ORDER BY p.created_at DESC, p.id DESC
LIMIT 20;
App server stateless di belakang load balancer, satu PostgreSQL. Tidak ada cache, queue, atau worker. Saat peluncuran (mis. 100 ribu DAU) puncak feed hanya ±30 QPS,
atau ±6.000 pencarian index per detik. Itu wajar untuk dicoba di satu database; ukur dengan EXPLAIN ANALYZE sebelum menambah komponen apa pun.
Desain ini juga benar secara otomatis: post yang dihapus atau akun yang di-unfollow langsung hilang dari feed, karena feed tidak pernah disalin.
Latihan 2 — Gambar diagram v1 sendiri dulu
Gambar diagram v1 Anda sendiri dulu: jalur tulis post dan jalur baca feed, dan di mana pekerjaan "merakit feed" terjadi. Setelah selesai, buka jawabannya.
Lihat jawaban
Jalur tulis: POST /v1/posts → INSERT satu baris → 201. Jalur baca: GET /v1/feed → query di atas → 20 post + nextCursor.
Di mana v1 berhenti cukup. Di skala target, pekerjaan baca menjadi ±600.000 pencarian index per detik pada puncak, dan setiap pembukaan feed menggabungkan dan mengurutkan post dari 200 penulis. Read replica bisa membagi beban itu, tetapi tidak mengurangi pekerjaan per feed. Pertanyaannya: bisakah feed disiapkan sebelum dibaca?
Latihan 3 — API dan desain v1
Jawab tanpa scroll ke atas.
Latihan lisan — Ucapkan dengan suara keras
Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Rancang news feed: user mem-posting dan melihat post terbaru dari akun yang mereka follow. Mulai dari requirement sampai desain v1." (90 detik)
Bandingkan dengan jawaban contoh dan checklist-nya
"Saya klarifikasi dulu: feed kronologis terbalik dari akun yang di-follow, tanpa ranking; 10 juta DAU, rata-rata follow 200 akun, ada akun besar dengan jutaan follower. Estimasinya: sekitar 100 post per detik dan sekitar 1.000 pembukaan feed per detik, puncak 3.000, jadi read-heavy 10:1. API-nya POST /v1/posts, POST follow, dan GET /v1/feed dengan cursor, bukan OFFSET, karena OFFSET besar tetap dihitung server dan halaman bergeser saat ada post baru. Data model: posts dengan index (author_id, created_at) dan follows (follower_id, followee_id). Desain v1-nya fan-out on read: app server stateless di belakang load balancer, satu query mengambil post terbaru dari akun yang di-follow, dari satu PostgreSQL. Itu cukup saat peluncuran; di skala target, 3.000 × 200 = 600.000 pencarian index per detik, dan itu yang saya serang di deep dive."
Checklist: apakah Anda menyebut…
- requirement: feed kronologis dari akun yang di-follow, ranking di luar scope, akun besar disebut?
- estimasi: ±100 post/detik, ±1.000 feed/detik (puncak ±3.000), read-heavy 10:1?
- API: POST post, follow, GET feed dengan cursor, dan alasan tidak memakai OFFSET?
- data model: posts dengan index (author_id, created_at) dan tabel follows?
- v1 fan-out on read: app server stateless + satu database, satu query per pembukaan feed?
- membenarkan komponen dengan angka (v1 cukup di awal; 600.000 pencarian index/detik di skala target jadi alasan deep dive)?
Lulus jika minimal 4 dari 6.
Desain v1 sudah berdiri, dan angka 600.000 sudah di tangan. Lanjut di lesson 0032: langkah 5, fan-out on write vs on read, dan hybrid untuk akun besar.
Istilah: GLOSSARY.md (bagian "Studi kasus") · Indeks: semua lesson System Design
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "kenapa cursor memakai id sebagai pemutus seri?", "kapan read replica cukup untuk feed?", atau "bagaimana feed berubah kalau ada ranking?"
Sumber: [1] Bronson et al., TAO: Facebook's Distributed Data Store for the Social Graph (USENIX ATC 2013) · [2] PostgreSQL docs: LIMIT and OFFSET · [3] PostgreSQL docs: Multicolumn Indexes