Belajar Fullstack Developer - Scalability Patterns
Episode 22 of 28

Belajar Fullstack Developer - Scalability Patterns

Merancang aplikasi agar melayani banyak pengguna: horizontal scaling dan stateless server, connection pooling untuk database, strategi scaling PostgreSQL (read replica, indexing, partitioning), serta kapan harus sharding dan event-driven.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

"Berapa pengguna yang bisa ditampung?" adalah pertanyaan yang menandai fase baru: dari membuat produk menjadi menjadikannya tahan beban. Episode ini membahas scalability patterns — pola arsitektur yang membuat aplikasi tetap cepat dan stabil saat pengguna tumbuh dari ratusan menjadi jutaan.

Ini bukan soal "framework ajaib" — ini soal memahami di mana bottleneck muncul dan menerapkan pola yang tepat di urutan yang benar. Berikut peta lengkapnya, dari termurah sampai termahal.

Pola Pertama dan Termurah: Stateless Server

Server yang menyimpan state di memori tidak bisa ditambah instance-nya. Kuncinya stateless: state tersimpan di tempat terpusat — database, Redis, session store.

100%

Contoh pelanggaran statefulness yang umum: clients Set di episode 14 (broadcast SSE) — ia hidup di memori satu proses. Solusi saat multi-instance: pindahkan daftar koneksi ke pub/sub Redis (Redis menyiarkan event ke semua instance):

Broadcast via pub/sub Redis
import { createClient } from "redis"
 
const pub = createClient()
const sub = createClient().duplicate()
 
export async function broadcast(type: string, payload: unknown) {
  await pub.publish("events", JSON.stringify({ type, payload }))
}
 
// di tiap instance: langganan Redis → kirim ke client SSE lokal

Dengan ini, berapa pun instance-nya, semua client menerima event. Ini pola yang harus diingat: apa pun yang sekarang di memori, harus pindah ke storage bersama sebelum diskalakan.

Tip

Tes stateless yang sederhana: jalankan dua instance di lokal (port berbeda) di belakang load balancer. Fitur yang rusak di setup ini (session ilang, broadcast ganda, counter aneh) adalah fitur yang menyimpan state lokal. Perbaiki itu dulu sebelum berpikir soal infrra.

Connection Pooling: Menyelamatkan Database

PostgreSQL membatasi jumlah koneksi (default 100). Jika server kalian menjalankan 10 koneksi per instance × 10 instance = 100 koneksi — database mati kehabisan koneksi, padahal beban query normal. Solusinya connection pooler (PGBouncer) yang berbagi sedikit koneksi untuk ribuan klien:

pgbouncer.ini
[databases]
app = host=db-prod port=5432
 
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20

Aturan yang wajib dipahami dengan Prisma:

SetupPoolingCocok untuk
DATABASE_URL langsung ke PostgresTanpa poolerDevelopment, satu instance
DATABASE_URL via PGBouncerPooledProduction, banyak instance

Jangan biarkan setiap instance memakai koneksi langsung di production — database adalah sumber daya paling langka di sistem kalian.

Scaling PostgreSQL: Naik Level Satu Per Satu

Database adalah bottleneck paling sering. Urutan strateginya dari paling murah:

1. Indexing — sebelum apa pun, pastikan index benar:

Index untuk query yang sering
CREATE INDEX idx_notes_org_created ON "Note" (organization_id, created_at DESC);

2. Read replica — pisahkan pembacaan berat ke salinan database:

Pemisahan baca/tulis dengan Prisma
export const prisma = new PrismaClient()
export const prismaRead = new PrismaClient({
  datasources: { db: { url: process.env.DATABASE_URL_READ } },
})

3. Partitioning — pecah tabel besar secara fisik berdasarkan kunci waktu:

Partisi tabel berdasarkan bulan
CREATE TABLE "Note" (id text, created_at timestamptz, ...)
  PARTITION BY RANGE (created_at);

4. Sharding — pecah data ke beberapa database. Ini pilihan terakhir: kompleksitasnya besar (query lintas shard, distribusi), dan kebanyakan aplikasi tidak pernah sampai sini.

Horizontal vs Vertical: Kapan Naik?

SkenarioPilihan
CPU server jenuhHorizontal (tambah instance) — murah & elastis
Database CPU/memori penuhVertical (tambah resource DB) dulu, lalu replica
Koneksi habisPooler, bukan tambah instance
Query lambat di jutaan barisIndex / partition, bukan ganti database

Aturan emas: sistem tanpa state men-skalakan horizontal tanpa batas; database men-skalakan lewat index → replica → partition → shard. Jangan lompat ke sharding sebelum dua langkah sebelumnya gagal.

Event-Driven: Menjadi Async di Skala

Semakin besar, semakin banyak pekerjaan yang tidak boleh sinkron. Pola event-driven menggunakan queue (episode 13) untuk semua kerja berat — sinkronisasi antar service, notifikasi, pembaruan data turunan:

100%

Dengan queue, lonjakan beban tidak membanjiri server — ia hanya memperpanjang antrean, dan worker memproses sesuai kapasitas. Ini prinsip yang sama yang membuat cloud "elastis".

Praktik: Merancang untuk Skala

Selesaikan dengan urutan berikut:

  1. Audit statefulness: jalankan dua instance + load balancer; perbaiki semua yang rusak (pindahkan state ke Redis/pub-sub).
  2. Pooling: arahkan DATABASE_URL production ke PGBouncer; verifikasi jumlah koneksi DB turun.
  3. Indexing: cek query lambat (EXPLAIN ANALYZE) dan tambahkan index yang diperlukan.
  4. Replica: sediakan read replica untuk halaman pembacaan berat.
  5. Simulasikan beban dengan alat load testing dan ukur sebelum/sesudah.
Cek koneksi aktif database
psql -h localhost -U postgres -c "SELECT count(*) FROM pg_stat_activity;"

Penutup

Inti yang harus dibawa pulang:

  • Stateless server dulu: pindahkan semua state ke storage bersama sebelum diskalakan.
  • Connection pooling (PGBouncer) mencegah database kehabisan koneksi.
  • Scaling PostgreSQL berurutan: index → read replica → partition → shard.
  • Horizontal untuk server (tanpa state), vertical/pooler untuk database.
  • Event-driven + queue menyerap lonjakan beban tanpa membanjiri sistem.

Di episode 23 selanjutnya kita membahas monorepo & DX — struktur project modern dengan workspace, shared packages, dan pengalaman developer yang sehat. Sampai jumpa di episode 23!

Belajar Fullstack Developer - Scalability Patterns | Belajar Fullstack