Skip to main content
System design langkah demi langkah · Pelajaran 7 dari 7 · 14 menit

Contoh Studi Kasus System Design: Merancang Situs Ini

Studi kasus system design yang dikerjakan langsung: framework tujuh langkah diterapkan pada situs ini, dari requirements dan angka sampai titik lemahnya.

Oleh Matthews WongDitinjau pada

Poin utama

Studi kasus system design menerapkan satu framework yang bisa diulang pada sistem nyata: requirements, estimasi, API, model data, desain tingkat tinggi, deep dive, dan trade-off. Di sini sistemnya adalah situs ini: halaman hasil prerender di balik Cloudflare dan nginx, satu URL yang menjawab dalam HTML atau Markdown, API kecil dengan rate limit, dan satu container yang batasnya menentukan apa yang rusak lebih dulu.

Setelah pelajaran ini, Anda bisa

  • Menjalankan tujuh langkah framework pada sistem nyata, dengan hasil konkret di akhir setiap langkah.
  • Membaca requirements dan batas sebuah desain dari konfigurasinya, bukan menebaknya.
  • Menyebut komponen paling berisiko dan bottleneck pertama, serta menjelaskan harga setiap trade-off.
Lihat cara kerjanya

Ikuti diagramnya langkah demi langkah

Tekan Putar, atau melangkah dengan panah. Setiap langkah juga ditulis di bawah diagram.

Tujuh langkah, diterapkan pada situs ini

Requirements menyebut pembacanya, API dan model data menyusul, build mengubah konten menjadi file, kunjungan membaca salah satunya, URL yang sama menyajikan Markdown untuk agen, dan satu container menjadi batasnya.

Langkah 1 dari 7. Kita merancang situs ini dengan framework tujuh langkah: pembaca di kiri, Cloudflare dan satu server di tengah, proses build di bawah. Setiap langkah menambah satu lapisan.

Tombol: ← → langkah · Home / End · K putar atau jeda

  1. Kita merancang situs ini dengan framework tujuh langkah: pembaca di kiri, Cloudflare dan satu server di tengah, proses build di bawah. Setiap langkah menambah satu lapisan.

  2. Requirements: artikel berbahasa Inggris dan Indonesia yang terbaca tanpa JavaScript, dan URL yang sama dalam Markdown untuk agen. Angkanya sudah ada di konfigurasi: satu CPU, 512 MB.

    mem_limit: 512m cpus: "1.0"
  3. API: setiap halaman adalah GET, dan tiga endpoint POST ada di bawah /api/v1/ dengan batas per IP. Model data: tanpa database sama sekali; tulisan adalah file di git.

    chat: 20, followups: 30, contact: 5,
  4. Penulisan terjadi saat build. Push menjalankan CI, yang me-render semua tulisan dalam dua bahasa ke dalam image, jadi menyajikan halaman berarti membaca file.

    // Prerender every blog post at build time (x2 locales via the root layout's // generateStaticParams).Tebak langkah berikutnya Pengunjung membuka sebuah tulisan. Apa yang dilakukan container? (Membaca file hasil prerender · Query ke database · Me-render halaman dari nol)
  5. Desain tingkat tinggi: Cloudflare mem-proxy request, nginx menangani TLS dan meneruskannya ke container di loopback, lalu halamannya diambil langsung dari file hasil prerender.

    Tebak langkah berikutnya Agen AI meminta URL yang sama dalam Markdown. Ke mana request-nya? (Ke HTML hasil prerender · Ke route Markdown terpisah · Dialihkan ke /md/)
  6. Deep dive pada bagian paling berisiko: satu URL, dua isi. middleware.ts menulis ulang request yang meminta text/markdown ke route Markdown, yang menjawab dengan Vary: Accept.

    if (prefersMarkdown(request.headers.get("accept")))
  7. Bottleneck: satu container di satu server, diganti setiap deploy tanpa menunggu health check-nya. Trade-off: baca murah, build lambat. Langkah berikutnya: instance kedua.

Apa saja tujuh langkah studi kasus system design?

Setiap studi kasus di modul ini menjalankan tujuh langkah yang sama, dan setiap langkah menyerahkan sesuatu yang konkret ke langkah berikutnya: requirements, lalu estimasi, lalu API, lalu model data, lalu diagram tingkat tinggi jalur request, lalu deep dive ke bagian paling berisiko, lalu bottleneck dan trade-off. Urutannya penting karena setiap pilihan sesudahnya dinilai terhadap requirements dan angka. Diagram yang digambar sebelum keduanya hanyalah daftar nama tool.

Baca selengkapnya (1)

Soal latihan biasanya berupa produk yang belum pernah Anda bangun, misalnya URL shortener. Pelajaran ini melakukan kebalikannya dan merancang sistem yang setiap keputusannya bisa Anda periksa: situs ini. Setiap langkah berakhir di file nyata, bukan tebakan, dan bila repositori tidak menyimpan suatu angka, misalnya trafik harian, pelajaran ini mengatakannya alih-alih mengarangnya. Menolak mengarang angka adalah kebiasaan pertama yang diajarkan framework ini.

Bagaimana requirements dan angka membentuk situs ini?

Requirements fungsionalnya singkat. Pengunjung membaca artikel dan pelajaran dalam bahasa Inggris dan Indonesia di URL yang selalu membawa locale. Crawler dan agen AI harus mendapat teksnya tanpa menjalankan JavaScript, dan agen yang mengirim Accept: text/markdown mendapat Markdown di URL yang sama. Chatbot dan form kontak butuh API kecil. Di luar cakupan: akun, komentar, dan apa pun yang ditulis pengunjung, itulah sebabnya tidak ada database.

Baca selengkapnya (1)

Sisi non-fungsional adalah tempat angka berada, dan di sini angkanya tertulis. docker-compose.yml membatasi container pada memori 512 MB dan satu CPU. next.config.ts mengecilkan cache halaman di memori milik Next dari default 50 MB menjadi 16 MB, karena hampir setiap halaman di-prerender dan dibaca dari disk. lib/api/versioning.ts mengizinkan 20 request chat, 30 follow-up, dan 5 kontak per menit per IP klien. Trafik tidak tercatat di repositori, jadi estimasi yang berguna adalah biaya per request: halaman hasil prerender cukup dibaca dari file, sedangkan request chat memanggil model bahasa, dan perbedaan itulah alasan batasnya dipasang di API.

Seperti apa API, data, dan jalur request-nya?

API-nya sebagian besar berupa GET untuk halaman. Endpoint JSON membawa versinya di path, di bawah /api/v1/; path lama tanpa versi tetap menjawab sebagai alias yang deprecated, dan perubahan yang merusak akan dirilis sebagai /api/v2/, bukan dengan mengubah v1. Model datanya berupa file: BLOG_META menyimpan judul, deskripsi, keyword, dan tanggal setiap tulisan, file messages menyimpan isinya dalam dua bahasa, dan lib/blog-sources.json adalah indeks turunan yang ditolak oleh pemeriksaan prebuild bila sudah basi. Menulis data berarti git push.

Baca selengkapnya (1)

Saat build, generateStaticParams mendaftar setiap tulisan dan Next me-render masing-masing dalam dua locale. Saat request, Cloudflare mem-proxy request pengunjung dan, karena secara default tidak meng-cache HTML, meneruskannya ke server. nginx menangani TLS lalu meneruskannya ke container, yang hanya dipublikasikan di alamat loopback server, jadi tidak ada yang bisa mencapainya kecuali lewat nginx. Request di bawah /api/ melewati middleware dan langsung menuju route handler-nya.

Di mana desain ini pertama kali rusak, dan apa yang akan Anda ubah?

Bagian paling berisiko adalah satu URL dengan dua isi. middleware.ts mengirim request Markdown ke route-nya sendiri, yang menjawab dengan Vary: Accept agar cache membedakan berdasarkan header Accept. Halaman HTML yang di-render tidak bisa membawa header itu, karena page handler Next mengganti Vary saat render, seperti dicatat komentar di middleware itu sendiri. Saat ini kondisinya aman hanya karena edge tidak menyimpan HTML. Aktifkan cache HTML di edge tanpa menambahkan Vary: Accept di nginx, dan agen bisa menerima HTML, atau browser menerima Markdown.

Baca selengkapnya (2)

Bottleneck pertama adalah container tunggal. Ia satu proses di satu server, jadi crash atau penggantian container berarti jeda bagi setiap request yang tidak bisa dijawab Cloudflare sendiri, dan deploy menjalankan docker compose up -d tanpa --wait, sehingga pipeline sudah hijau sebelum container sehat. Instance kedua di belakang nginx adalah langkah berikutnya yang jelas, dan langkah itu membuka asumsi tersembunyi: lib/rate-limit.ts menyimpan penghitungnya di sebuah Map di dalam proses, jadi dua instance masing-masing akan mengizinkan batas penuh. State itu harus dipindahkan keluar sebelum tier ini bisa di-scale.

Trade-off-nya disengaja, dan menyebut harga setiap pilihan adalah langkah terakhir framework ini. Prerender memberi pembacaan yang murah dan bisa diprediksi dengan harga build yang lama, itulah sebabnya CI memulihkan build cache Next. Tanpa database, satu kelas kegagalan hilang dan setiap penulisan menjadi commit yang direview, dengan harga tidak ada konten dari pengunjung. Satu server membuat sistem cukup kecil untuk dijalankan satu orang, dengan harga tanpa redundansi.

Cara situs ini melakukannya

Konfigurasi nyata di balik pelajaran ini

Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.

next.config.ts:16-21
  // In-memory LRU for rendered pages, on top of the on-disk prerender cache.
  // Default is 50 MB. Nearly every route here is prerendered at build time and
  // read straight off disk, and Cloudflare absorbs the repeat traffic, so a
  // large resident cache buys little on a 2 GB VPS that also runs the other
  // self-hosted services. 16 MB still holds the hot pages.
  cacheMaxMemorySize: 16 * 1024 * 1024,

Langkah estimasi, ditulis sebagai konfigurasi. Komentarnya menyebut requirement-nya (halaman di-prerender dan dibaca dari disk), kendalanya (server kecil yang dipakai bersama layanan lain), dan keputusannya beserta angkanya. Itulah aturan framework dalam bentuk kecil: lekatkan angka pada setiap pilihan dan jelaskan alasannya.

lib/api/versioning.ts:32-44
/** Current endpoints; trailing slash because the site sets trailingSlash. */
export const API_PATHS = {
  chat: "/api/v1/chat/",
  followups: "/api/v1/chat/followups/",
  contact: "/api/v1/contact/",
} as const;

/** Requests per minute per client IP, enforced by each route. */
export const API_RATE_LIMITS = {
  chat: 20,
  followups: 30,
  contact: 5,
} as const satisfies Record<keyof typeof API_PATHS, number>;

Seluruh permukaan API beserta batasnya dalam satu modul. Route-route menegakkan angka ini, dan next.config.ts, deskripsi OpenAPI, serta panduan developer membacanya, jadi langkah API dalam desain tidak bisa melenceng dari yang benar-benar berjalan. Klausa satisfies membuat endpoint baru tanpa batas menjadi error tipe.

app/[locale]/(pages)/blog/[slug]/layout.tsx:169-173
// Prerender every blog post at build time (x2 locales via the root layout's
// generateStaticParams). Without this, all 300+ posts were rendered on demand.
export function generateStaticParams() {
  return Object.keys(BLOG_META).map((slug) => ({ slug }));
}

Model data dan desain tingkat tinggi bertemu di sini. Daftar tulisan adalah key dari BLOG_META, sebuah file di repositori, dan mengembalikannya dari generateStaticParams memindahkan proses render dari waktu request ke waktu build. Komentarnya mencatat alternatif yang digantikannya.

lib/rate-limit.ts:1-11
/**
 * Simple in-memory rate limiter for API routes
 * In production, consider using Redis (Upstash) for distributed rate limiting
 */

interface RateLimitEntry {
  count: number;
  resetTime: number;
}

const rateLimitMap = new Map<string, RateLimitEntry>();

Asumsi tersembunyi dari langkah bottleneck. Sebuah Map di satu proses benar selama prosesnya memang hanya satu. Komentar di file itu sendiri menyebut perbaikan yang akan dipaksakan oleh instance kedua: pindahkan penghitungnya ke penyimpanan bersama.

Yang sering rusak

Kesalahan yang benar-benar membuang waktu

  • Cache HTML di edge tanpa Vary: Accept

    Satu URL menjawab HTML atau Markdown, dan response HTML tidak membawa Vary: Accept. Cache bersama yang menyimpannya akan memberikan salinan itu ke semua klien, apa pun yang diminta. Jauhkan HTML dari cache edge, atau tambahkan Vary: Accept di nginx lebih dulu, seperti kata komentar di middleware.ts.

  • Scale out dengan state di dalam proses

    Tambahkan container kedua dan rate limiter in-memory diam-diam menggandakan setiap batas, karena setiap proses menghitung sendiri. Pindahkan state bersama, seperti penghitung rate limit, ke penyimpanan bersama sebelum menambah instance.

  • Deploy yang melaporkan sukses terlalu cepat

    docker compose up -d selesai begitu container mulai berjalan, bukan begitu container sehat, jadi image yang rusak tetap membuat pipeline hijau. Flag --wait menunggu sampai service berjalan dan sehat, sehingga health check menjadi bagian dari deploy.

  • Memilih fitur tanpa mengukur biayanya

    next.config.ts mencatat bahwa meng-encode satu gambar cover ke AVIF butuh 4 sampai 9 detik di server ini, dibandingkan sekitar 0,6 detik untuk WebP, jadi AVIF tetap dimatikan sampai edge meng-cache gambar yang sudah dioptimasi. Requirement yang tampak gratis bisa menjadi baris termahal dalam desain.

Render di setiap request atau prerender saat build
Kerja per tampilan halamanMenjalankan kode yang menyusun halamanMembaca file yang sudah ada
Perubahan konten tayangPada request berikutnyaSetelah build dan deploy berikutnya
Waktu buildSingkatBertambah setiap ada tulisan baru
Kebutuhan saat requestSumber data dan CPUDisk dan sedikit memori
Cocok untukHalaman yang berbeda per penggunaArtikel yang berubah lewat commit
Coba sendiri

Lakukan di komputer Anda sendiri

Anda butuh curl dan terminal. Amati dulu desain situs ini dari luar, lalu jalankan tujuh langkahnya pada sistem milik Anda sendiri.

  1. Ambil header sebuah tulisan. Cari server: cloudflare, x-nextjs-prerender yang menandai halaman hasil build, dan cf-cache-status yang menunjukkan apakah edge menjawab dari cache-nya.

    curl -sI https://www.matthewswong.com/en/blog/system-design-interview-framework-step-by-step/ \
      | grep -iE "^(server|cf-cache-status|x-nextjs-prerender|vary|content-type)"
  2. Minta URL yang sama dalam Markdown. Content type berubah menjadi text/markdown dan muncul baris Vary: Accept; bandingkan dengan response HTML di atas.

    curl -s -D - -o /dev/null -H "Accept: text/markdown" \
      https://www.matthewswong.com/en/blog/system-design-interview-framework-step-by-step/ \
      | grep -iE "^(content-type|vary)"
  3. Panggil path API dengan method yang salah. Jawabannya error JSON dengan code yang stabil dan petunjuk: batas API tetap memegang kontraknya walau Anda salah memakainya.

    curl -s https://www.matthewswong.com/api/v1/contact/
  4. Sekarang tulis tujuh langkah untuk sistem yang Anda jalankan atau pakai, satu baris per langkah, dan cocokkan setiap angka dengan file, bukan ingatan.

    1. Requirements: functional, non-functional, out of scope
    2. Numbers: limits already in config, cost per request
    3. API: paths, methods, versioning
    4. Data: what is stored where, what is derived
    5. Request path: every hop from user to data
    6. Deep dive: the riskiest component
    7. Bottlenecks and trade-offs: what fails first, what each choice costs
Kuis singkat

Tiga pertanyaan sebelum lanjut

Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada yang disimpan atau dinilai, jadi tebak saja dengan bebas.

  1. 1.Diminta merancang situs ini dalam interview, Anda langsung memilih Postgres dan Redis. Langkah mana yang Anda lewati?
  2. 2.Browser lalu agen AI meminta URL yang sama. Cache di depan menyimpan HTML untuk browser tanpa Vary: Accept. Apa yang didapat agen?
  3. 3.Anda menambah container kedua di belakang nginx. Kini form kontak menerima hingga 10 pesan per menit dari satu IP. Kenapa?
Pikirkan dulu

Jawab dalam hati, lalu cek

  1. 1.Kenapa situs ini tidak punya database, dan apa harganya?Lihat jawabannya

    Jawaban:Requirements-nya tidak berisi penulisan oleh pengunjung: konten datang sebagai commit dan di-prerender saat build. Harganya: tidak ada konten dari pengunjung, dan setiap perubahan konten butuh build serta deploy.

  2. 2.Kenapa meng-cache HTML situs ini di edge berisiko dengan kondisi sekarang?Lihat jawabannya

    Jawaban:URL yang sama bisa menjawab Markdown, dan response HTML tidak membawa Vary: Accept, jadi cache bersama bisa memberikan satu representasi ke klien yang salah. Response Markdown membawa header itu.

  3. 3.Apa yang harus diubah sebelum container kedua bisa berjalan dengan aman?Lihat jawabannya

    Jawaban:Penghitung rate limiter ada di sebuah Map di dalam satu proses; dengan dua container, masing-masing akan mengizinkan batas penuh. Penghitung itu harus dipindah ke penyimpanan bersama lebih dulu.

Baca modulnya

Semua artikel di modul ini, berurutan

Pelajaran di atas adalah petanya. Setiap artikel membahas satu bagiannya secara mendalam, lengkap dengan contoh.

  1. Framework System Design Interview: Panduan Langkah demi LangkahCara menghadapi system design interview langkah demi langkah: requirements, estimasi scale, API dan data model, diagram, deep dive, trade-off, plus contoh pastebin.
  2. Design URL Shortener Seperti bit.ly: Panduan System DesignCara mendesain URL shortener seperti bit.ly: hitungan kapasitas, key generation base62, schema Postgres, 301 vs 302, Redis cache, analytics, dan anti-abuse.
  3. Desain Search Autocomplete (Typeahead): Trie, Redis, PostgresCara mendesain search autocomplete skala besar: hitungan kapasitas, latency budget 100 ms, trie vs Postgres dan Redis, ranking dengan decay, agregasi log offline, caching top-K, dan client dengan debounce.
  4. Desain Sistem News Feed: Fan-Out on Write vs Fan-Out on ReadCara mendesain news feed: fan-out on write versus fan-out on read, masalah selebriti, feed Redis sorted set, cursor pagination dan ranking, lengkap dengan hitungannya.
  5. Desain Aplikasi Chat Seperti WhatsApp: System Design Real-TimeCara mendesain aplikasi chat real-time seperti WhatsApp: WebSocket gateway, ack dan urutan dengan sequence number per percakapan, offline delivery, presence, dan group fan-out.
  6. Cara Mendesain Sistem Notifikasi Scalable (Push, Email, SMS)Sistem notifikasi terdiri dari event ingester, satu queue per channel, pengecekan preferensi, constraint dedup, retry ke dead-letter queue, dan tracking delivery lewat webhook.
  7. Geohash vs Quadtree: Cara Kerja Pencarian Driver TerdekatBagaimana aplikasi menemukan driver atau tempat terdekat? Geohash vs quadtree dengan TypeScript, PostGIS ST_DWithin, Redis GEOSEARCH, dan hitungan write rate.
  8. Desain Distributed Job Scheduler: Jalan Sekali per JobCara mendesain distributed job scheduler yang menjalankan tiap job sekali: slot row, lease Postgres SKIP LOCKED, advisory lock, catch-up, retry, dan handler idempotent.
  9. Desain Web Crawler: URL Frontier, robots.txt dan DedupBagaimana mendesain web crawler yang scalable? Desain lengkap: URL frontier, robots.txt menurut RFC 9309, dedup, DNS caching, crawler trap dan jadwal recrawl.
  10. Cara Mendesain Metrics dan Monitoring System dengan PrometheusCara mendesain metrics dan monitoring system dari ujung ke ujung: instrumentasi, pull vs push, hitungan storage time-series, cardinality, retention, dan alerting.
  11. Desain Sistem Video Streaming: HLS, CDN dan Adaptive BitrateCara kerja video streaming dan cara mendesainnya: transcode ladder, segment dan manifest HLS, object storage, CDN, logika adaptive bitrate, signed URL, serta live versus VOD, lengkap dengan hitungan storage dan egress.
  12. Desain Sistem Flash Sale yang Tidak Oversell StokCara mendesain sistem flash sale yang tidak oversell stok: atomic decrement di Postgres dan Redis Lua, reservasi dengan TTL, waiting room, sharded counter, dan rekonsiliasi.
  13. Desain Payment System: Ledger, Idempotency, ReconciliationCara mendesain payment system yang tidak kehilangan atau menagih ganda: state machine pembayaran, ledger double-entry append-only dalam integer sen, idempotency key, webhook, dan reconciliation harian.
  14. Cara Mendesain Distributed Key-Value Store ala DynamoCara mendesain distributed key-value store ala Dynamo: consistent hashing, quorum N/W/R, vector clock, hinted handoff, Merkle tree, gossip, dan node LSM.