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.

"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.
Server yang menyimpan state di memori tidak bisa ditambah instance-nya. Kuncinya stateless: state tersimpan di tempat terpusat — database, Redis, session store.
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):
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 lokalDengan 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.
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:
[databases]
app = host=db-prod port=5432
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20Aturan yang wajib dipahami dengan Prisma:
| Setup | Pooling | Cocok untuk |
|---|---|---|
DATABASE_URL langsung ke Postgres | Tanpa pooler | Development, satu instance |
DATABASE_URL via PGBouncer | Pooled | Production, banyak instance |
Jangan biarkan setiap instance memakai koneksi langsung di production — database adalah sumber daya paling langka di sistem kalian.
Database adalah bottleneck paling sering. Urutan strateginya dari paling murah:
1. Indexing — sebelum apa pun, pastikan index benar:
CREATE INDEX idx_notes_org_created ON "Note" (organization_id, created_at DESC);2. Read replica — pisahkan pembacaan berat ke salinan database:
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:
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.
| Skenario | Pilihan |
|---|---|
| CPU server jenuh | Horizontal (tambah instance) — murah & elastis |
| Database CPU/memori penuh | Vertical (tambah resource DB) dulu, lalu replica |
| Koneksi habis | Pooler, bukan tambah instance |
| Query lambat di jutaan baris | Index / 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.
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:
Dengan queue, lonjakan beban tidak membanjiri server — ia hanya memperpanjang antrean, dan worker memproses sesuai kapasitas. Ini prinsip yang sama yang membuat cloud "elastis".
Selesaikan dengan urutan berikut:
DATABASE_URL production ke PGBouncer; verifikasi jumlah koneksi DB turun.EXPLAIN ANALYZE) dan tambahkan index yang diperlukan.psql -h localhost -U postgres -c "SELECT count(*) FROM pg_stat_activity;"Inti yang harus dibawa pulang:
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!