Database Scaling — System Design

Ketika database menjadi bottleneck karena traffic tinggi atau data terlalu besar, kamu perlu strategi scaling database. 1. Read Replicas (Replication) Satu…

Database scaling adalah cara membuat database sanggup menerima beban yang lebih besar: lebih banyak query per detik, atau lebih banyak data. Bayangin semua penjualan tiket dicatat di satu buku besar oleh satu petugas. Orang yang cuma bertanya ("kursi A-12 masih ada?") dan orang yang membeli (petugas menulis di buku) antre di meja yang sama. Kamu bisa mengganti petugasnya dengan yang lebih cepat, memberi buku fotokopian ke petugas lain khusus untuk menjawab pertanyaan, atau memecah bukunya: pembeli A sampai M di meja 1, N sampai Z di meja 2. Tiga cara itu adalah vertical scaling, read replica, dan sharding.

🧪 Tebakan cepat: saat war tiket, primary database TiketNada di 130% karena 520 write per detik. Sudah ada 4 read replica. Kamu tambah replica ke-5. Primary-nya turun di bawah 100%? Tebak dulu, jawabannya ada di bagian "Read replica: membagi read, bukan write".

Model contoh: database TiketNada

Angka di lesson ini dan di Lab adalah model contoh, bukan benchmark. Di hari biasa (traffic 1x), aplikasi TiketNada mengirim 1.300 read (SELECT) dan 130 write (INSERT, UPDATE, DELETE) per detik ke PostgreSQL. Satu server 8 vCPU sanggup 4.000 read per detik, atau 400 write per detik. Write lebih mahal karena harus mencatat ke disk dan memperbarui index.

Beban server = read / 4.000 + write / 400 = 32,5% + 32,5% = 65%. Di bawah 100%, query menunggu sekitar 4 ms / (1 - beban): 11 ms di 65%, 40 ms di 90%. Di atas 100%, server menerima lebih banyak kerja daripada yang sanggup dia selesaikan, jadi antreannya bertambah terus. Di traffic 2x bebannya 130%: tiap detik antrean bertambah 0,3 detik kerja. Setelah 10 detik, query baru menunggu sekitar 3 detik, dan pembeli melihat timeout.

Sebelum menambah server, pastikan query-nya sudah sehat. Query tanpa index yang membaca 2 juta baris gak tertolong server mana pun. Bahasannya ada di lesson Index Strategy dan Connection Pooling (track Database), dan cache di lesson Caching Strategies.

Vertical scaling: server yang lebih besar

Vertical scaling (scale up) artinya menambah resource di satu server: CPU, RAM, disk yang lebih cepat. Di model ini, server dua kali lebih besar sanggup sekitar 1,8 kali bebannya, bukan 2 kali, dan harganya dua kali.

Ukuran Kapasitas Biaya Beban di traffic 2x Beban di traffic 6x
8 vCPU 1x 1x 130% 390%
16 vCPU 1,8x 2x 72% 217%
32 vCPU 3,2x 4x 41% 122%
64 vCPU 5,8x 8x 22% 67%

Kelebihannya: satu klik di cloud, kode gak berubah sama sekali. Kekurangannya: ada batas mesin terbesar, biayanya naik lebih cepat dari kapasitasnya, dan tetap satu mesin. Kalau mesin itu mati, semuanya ikut mati.

Horizontal scaling: lebih banyak server

Horizontal scaling (scale out) artinya menambah jumlah server. Untuk server aplikasi ini gampang, karena tiap server sama (lesson Load Balancing). Untuk database lebih sulit, karena datanya harus ada di server-server itu. Ada dua cara:

Read replica: membagi read, bukan write

Read replica adalah salinan database yang cuma bisa dibaca. Satu server jadi primary yang menerima semua write, lalu setiap perubahan disalin ke replica. Aplikasi mengirim SELECT ke replica, dan INSERT, UPDATE, DELETE ke primary:

function route(sql) {
  return sql.trim().toUpperCase().startsWith('SELECT') ? 'replica' : 'primary';
}

console.log(route('SELECT * FROM orders WHERE user_id = 48213')); // replica
console.log(route('INSERT INTO orders (user_id, event_id, qty) VALUES (48213, 42, 2)')); // primary

Yang sering terlupa: setiap replica juga harus menerapkan ulang setiap write supaya datanya sama. Di model ini, itu makan setengah biaya write. Ini traffic 3x (3.900 read dan 390 write per detik):

Setup Primary Tiap replica
Tanpa replica 97,5% + 97,5% = 195% -
1 replica 390 / 400 = 97,5% 97,5% read + 48,75% replay = 146%
2 replica 97,5% 48,75% + 48,75% = 97,5%
4 replica 97,5% 24,4% + 48,75% = 73%

Naik ke traffic 4x: 520 write per detik semuanya tetap ke primary, 520 / 400 = 130%, berapa pun replica-nya. Replica ke-5 cuma mengambil read, jadi primary tetap 130%. Itu jawaban tebakannya. Read replica menolong beban yang read-heavy (kebanyakan aplikasi web), tapi gak menolong write.

Satu lagi: replica selalu sedikit tertinggal dari primary (replication lag), jadi data yang baru ditulis bisa belum terlihat di replica. Bahasan lengkapnya ada di lesson Replication & HA (track Database).

Sharding: membagi data

Sharding memecah satu tabel besar ke beberapa server. Tiap server, disebut shard, memegang sebagian baris dan menerima read dan write untuk baris itu saja. Kolom yang menentukan sebuah baris masuk ke shard mana disebut shard key.

Di Lab, TiketNada punya 120.000 user dan 4 shard. Ada tiga pilihan shard key:

// hash(user_id): angka acak tapi tetap, dari id-nya
function shardByHash(userId) {
  return hash(String(userId)) % 4; // hash() = FNV-1a, ada di kode Lab
}
// range user_id: id 1-30.000 di shard 0, 30.001-60.000 di shard 1, dan seterusnya
function shardByRange(userId) {
  return Math.min(3, Math.floor((userId - 1) / 30000));
}
// event_id: semua pesanan satu event di satu shard
function shardByEvent(eventId) {
  return eventId % 4;
}

console.log(shardByHash(48213), shardByRange(48213), shardByEvent(42)); // 2 1 2

Hot shard dan query lintas shard

Dua fakta model ini: 7 dari 10 pembeli memburu Konser Nusantara, dan user yang daftar paling akhir (id 90.001 ke atas) tiga kali lebih aktif, jadi mereka membawa 50% traffic. Di traffic 4x, 4 shard:

Shard key Shard tersibuk memegang Bebannya
hash(user_id) 25,2% traffic 66%
range user_id 50% (shard 3, user terbaru) 130%
event_id 70% (shard 2, Konser Nusantara) 182%

Shard yang menanggung jauh lebih banyak dari yang lain disebut hot shard. Tiga shard santai, satu kewalahan, dan sistemnya ikut lambat. Pilih shard key yang nilainya banyak dan traffic-nya tersebar rata.

Kelemahan utama sharding adalah query lintas shard. Query yang gak menyebut shard key harus dikirim ke semua shard lalu digabung di aplikasi (scatter-gather): latency-nya ikut shard paling lambat, dan bebannya 4 query, bukan 1. JOIN antar shard lebih sulit lagi.

Query Shard key hash(user_id) Shard key event_id
SELECT * FROM orders WHERE user_id = 48213 1 shard 4 shard
SELECT COUNT(*) FROM orders WHERE event_id = 42 4 shard 1 shard

Jadi shard key diambil dari query yang paling sering. Kelemahan lain: menambah shard dengan % N memindahkan hampir semua baris (consistent hashing mengurangi ini, lihat lesson Load Balancing), dan id harus unik di semua shard, bukan per shard.

🎯 Urutan dari yang paling murah

  1. Perbaiki query dan index. Cek dengan EXPLAIN. Sering kali selesai di sini.
  2. Connection pooling dan cache. Redis untuk data yang sering dibaca.
  3. Vertical scaling. Satu klik, tanpa ubah kode.
  4. Read replica. Untuk beban yang kebanyakan read.
  5. Pisah per fitur (vertical partitioning): tabel analytics di database sendiri, misalnya.
  6. Sharding. Paling akhir, karena paling rumit dijalankan dan sulit dibatalkan.

⚠️ Jebakan yang sering ketemu

TL;DR: Vertical scaling menambah resource di satu server: mudah, tapi ada batas mesin terbesar dan biayanya naik cepat. Horizontal scaling menambah jumlah server. Read replica menyalin seluruh database ke server read-only: SELECT dibagi ke replica, semua write tetap ke primary, jadi replica menolong read tapi gak menolong write (di model ini primary tetap 130% di traffic 4x berapa pun replica-nya). Sharding memecah data ke beberapa shard lewat shard key, jadi read dan write sama-sama dibagi. Pilih shard key yang tersebar rata dan ada di query paling sering: event_id membuat hot shard (182%), hash(user_id) rata (66%). Kelemahan utama sharding adalah query lintas shard yang harus ditanyakan ke semua shard lalu digabung. Perbaiki query, index, dan cache dulu; sharding paling akhir.

Yang akan kamu pelajari