Sepanjang section ini kita memperlengkapi diri dengan satu senjata spesifik: pemecahan tabel besar. Tetapi skala adalah masalah sistem, bukan masalah tabel tunggal — dan melompat ke solusi paling canggih untuk masalah paling sederhana adalah cara paling cepat membayar kompleksitas yang tidak perlu. Bab penutup ini menyusun seluruh perjalanan course menjadi satu peta: tangga strategi skala dari yang termurah sampai yang paling mengubah arsitektur, perbedaan skala vertikal dan horizontal, alur keputusan yang bisa dipakai saat insiden nyata, hingga batas jujur kapan PostgreSQL sebaiknya ditinggalkan. Prinsipnya satu sepanjang tangga: naik satu anak tangga hanya setelah anak tangga sebelumnya terbukti habis.
Tangga Strategi Skala #
Setiap anak tangga menyelesaikan jenis hambatan yang berbeda. Melompati tangga berarti membayar biaya kompleksitas tanpa tahu apakah hambatan aslinya memang di situ:
| # | Strategi | Menyelesaikan | Biaya kompleksitas |
|---|---|---|---|
| 1 | tuning query + index | query lambat, rencana eksekusi buruk | waktu analisis; infrastruktur tidak berubah |
| 2 | connection pooling | ribuan koneksi baru, memori per proses habis | satu komponen (mis. PgBouncer) di jalur koneksi |
| 3 | read replicas | beban baca melampaui satu server | topologi replikasi, pemantauan lag, latihan failover |
| 4 | partitioning tabel besar | tabel raksasa: vacuum, index, rotasi data | kalender partisi dan pemeliharaan rutin |
| 5 | caching layer | hasil query yang sama diminta berulang | logika invalidasi, jendela data basi |
| 6 | sharding / Citus | beban tulis melampaui satu server | skema aplikasi dipecah; routing dan transaksi lintas node |
| 7 | specialized stores | pola akses yang bukan SQL relasional | dua sistem data, pipeline sinkronisasi, tim ganda |
Anak tangga 1-2 adalah urusan jam dan disiplin; PostgreSQL-nya sendiri tidak berubah. Anak tangga 3-5 menambah komponen di sekitar database tetapi skema tetap satu. Anak tangga 6-7 mengubah cara aplikasi berpikir tentang datanya — itulah sebabnya mereka harus menjadi pilihan terakhir, bukan pertama.
Anak tangga pertama saja sering menyelesaikan 80% keluhan “database lambat”: satu index yang tepat (termasuk BRIN
untuk tabel log besar), satu query yang ditulis ulang, atau satu statistik yang diperbarui ANALYZE. Anak tangga kedua, connection pooling
, menghapus pemborosan paling khas PostgreSQL — satu proses server per koneksi — tanpa menyentuh query sama sekali. Anak tangga ketiga, read replicas
, menggandakan kapasitas baca dengan streaming replication. Anak tangga keempat adalah seluruh section ini: partitioning dengan pruning yang menjaga query tetap cepat. Anak tangga kelima dan seterusnya berada di luar lingkungan uji kita dan dibahas berdasar dokumentasi resmi — keputusannya selalu dimulai dari pengukuran, bukan tren.
Vertikal dan Horizontal #
Dua arah membesar ini melandasi seluruh tangga. Skala vertikal memperbesar satu server: lebih banyak RAM agar data panas muat di cache halaman, CPU lebih cepat, NVMe lebih lebar. Keunggulannya: tidak ada satu baris aplikasi yang berubah, replikasi dan backup tetap seperti biasa. Batasnya juga jelas: harga per unit performa naik tajam di ujung spek, dan single server = satu titik kegagalan — untuk ketahanan, replikasi tetap wajib apa pun ukurannya. Skala horizontal menambah mesin: replikasi (baca), partitioning (organisasi data dalam satu server), hingga sharding (menulis lintas server). Setiap bentuk horizontal membawa koordinasi baru — lag replikasi, kalender partisi, routing shard — dan biaya operasionalnya menumpuk.
Praktik yang sehat umumnya: naikkan vertikal selama kurva harga masih masuk akal, gunakan horizontal untuk pola yang memang cocok (baca banyak, data deret waktu), dan jadikan sharding sebagai keputusan arsitektur besar yang diambil dengan data pengukuran di tangan.
Ukur Dulu, Verifikasi, Baru Naik Tangga #
Setiap keputusan skala dimulai dari angka, bukan firasat. Berapa ukuran tabel fisik, berapa baris per partisi, berapa lama query paling panas — perangkat ukurnya ada di SQL, dan kita rangkum polanya pada tabel penjualan kuartal yang sama dengan chapter-chapter sebelumnya:
-- Perangkat ukur: tabel penjualan kuartal terpartisi tiga bulanan.
CREATE TABLE log_penjualan (
id bigint NOT NULL,
tanggal date NOT NULL,
kota text NOT NULL,
total numeric(12,2) NOT NULL
) PARTITION BY RANGE (tanggal);
CREATE TABLE log_penjualan_2026_01 PARTITION OF log_penjualan
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE log_penjualan_2026_02 PARTITION OF log_penjualan
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE log_penjualan_2026_03 PARTITION OF log_penjualan
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
INSERT INTO log_penjualan
SELECT g,
DATE '2026-01-01' + ((g - 1) % 90),
(ARRAY['Bandung', 'Jakarta', 'Medan', 'Surabaya'])[1 + (g % 4)],
((g * 37) % 1000 + 1) * 1000.00
FROM generate_series(1, 9000) AS g;
-- Angka kapasitas per tabel fisik: dasar setiap keputusan skala.
SELECT tableoid::regclass AS partisi,
count(*) AS baris,
pg_size_pretty(pg_relation_size(tableoid)) AS ukuran
FROM log_penjualan
GROUP BY 1
ORDER BY 1;
-- Statistik diisi agar rencana eksekusi berikutnya stabil.
ANALYZE log_penjualan;Output yang Dihasilkan:
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 9000
partisi | baris | ukuran
-----------------------+-------+--------
log_penjualan_2026_01 | 3100 | 184 kB
log_penjualan_2026_02 | 2800 | 168 kB
log_penjualan_2026_03 | 3100 | 184 kB
(3 rows)
ANALYZETiga angka per partisi — 3.100/2.800/3.100 baris, 184/168/184 kB — adalah contoh paling sederhana dari dashboards kapasitas yang sesungguhnya dipantau tim operasional lewat pg_stat_user_tables, pg_stat_statements, dan ukuran relasi. Setelah berinvestasi pada satu anak tangga, investasi itu harus diverifikasi bekerja:
-- Verifikasi mekanisme inti bekerja SEBELUM menambah kompleksitas baru.
-- Laporan satu hari: pruning memangkas ke satu partisi.
EXPLAIN SELECT count(*)
FROM log_penjualan
WHERE tanggal = DATE '2026-02-15';
-- Rekap kuartal penuh: ketiga partisi memang diperlukan.
EXPLAIN SELECT count(*)
FROM log_penjualan;Output yang Dihasilkan:
QUERY PLAN
--------------------------------------------------------------------------------------------
Aggregate (cost=56.25..56.26 rows=1 width=8)
-> Seq Scan on log_penjualan_2026_02 log_penjualan (cost=0.00..56.00 rows=100 width=0)
Filter: (tanggal = '2026-02-15'::date)
(3 rows)
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Aggregate (cost=224.50..224.51 rows=1 width=8)
-> Append (cost=0.00..202.00 rows=9000 width=0)
-> Seq Scan on log_penjualan_2026_01 log_penjualan_1 (cost=0.00..54.00 rows=3100 width=0)
-> Seq Scan on log_penjualan_2026_02 log_penjualan_2 (cost=0.00..49.00 rows=2800 width=0)
-> Seq Scan on log_penjualan_2026_03 log_penjualan_3 (cost=0.00..54.00 rows=3100 width=0)
(5 rows)Rencana pertama membuktikan mekanisme inti section ini: laporan satu hari menyentuh satu partisi saja berkat pruning, bukan tiga. Rencana kedua memperlihatkan sisi jujurnya — rekap kuartal memang membaca ketiga partisi karena jawabannya tersebar di sana; partitioning tidak mengubah biaya query yang memang butuh semua data. Pola “ukur → ubah → verifikasi” inilah yang berlaku di setiap anak tangga: pooling diverifikasi dengan waktu respons dan jumlah koneksi, replikasi dengan lag, cache dengan rasio temuan.
Alur Keputusan saat Insiden #
Ketika laporan “aplikasi lambat” datang, urutan pemeriksaan berikut menyusuri tangga dari bawah — diagram ini merangkumnya:
Alur tersebut selalu kembali ke titik awal setelah setiap perbaikan: masalah yang sama harus diukur ulang, karena satu perbaikan sering menyingkap hambatan berikutnya yang tadinya tersembunyi. Perhatikan juga posisi vertikal scaling di jalur “bukan tulis yang jadi masalah” — menaikkan kapasitas mesin tetap pilihan sah ketika kompleksitas horizontal belum terbayar keuntungannya.
Kapan Meninggalkan PostgreSQL #
Kejujuran teknis menuntut kita mengakui batasnya. PostgreSQL kurang pas ketika beban tulis benar-benar melampaui satu server — sharding komunitas seperti Citus ada justru untuk ini, tetapi arsitektur aplikasinya berubah besar dan transaksi lintas shard kehilangan jaminan ACID penuh. Pola akses eksotis juga menjadi sinyal: pencarian teks skala mesin pencari (Elasticsearch), graf relasi berlapis-lapis dengan traversal dalam (Neo4j), atau deret waktu sensor bermiliar titik per hari dengan retensi sangat pendek (TimeScaleDB, VictoriaMetrics) lebih alami ditangani penyimpanan yang dibangun khusus untuk pola itu. Yang sering disalahpahami: skala “besar” saja bukan alasan pindah — PostgreSQL dipercaya menangani tabel multi-terabyte dan ribuan transaksi per detik di produksi selama pola aksesnya relasional. Keputusan pindah yang sehat selalu dibangun dari bukti: profil beban yang terukur, batas yang sudah disentuh, dan biaya kompleksitas alternatif yang sudah dihitung.
Rekap Section dan Lanjutan #
Perjalanan section ini menyusun satu narasi utuh: konsep partitioning menjelaskan mengapa tabel raksasa pecah menjadi tabel fisik kecil; range , list , dan hash adalah tiga cara membaginya; partition pruning menjaga query tetap cepat setelah pembagian; partitioned index menjalar index dan menjaga keunikan; partition maintenance merawat siklus hidupnya; dan inheritance menjelaskan sejarah sebelum semuanya ada. Dengan peta lengkap di tangan, perjalanan course berlanjut ke ekosistem di sekitar inti database: extension yang memperluas kemampuan PostgreSQL — topik section berikutnya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ukur-dulu.sql
Unduh berkas-- Perangkat ukur: tabel penjualan kuartal terpartisi tiga bulanan.
CREATE TABLE log_penjualan (
id bigint NOT NULL,
tanggal date NOT NULL,
kota text NOT NULL,
total numeric(12,2) NOT NULL
) PARTITION BY RANGE (tanggal);
CREATE TABLE log_penjualan_2026_01 PARTITION OF log_penjualan
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE log_penjualan_2026_02 PARTITION OF log_penjualan
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE log_penjualan_2026_03 PARTITION OF log_penjualan
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
INSERT INTO log_penjualan
SELECT g,
DATE '2026-01-01' + ((g - 1) % 90),
(ARRAY['Bandung', 'Jakarta', 'Medan', 'Surabaya'])[1 + (g % 4)],
((g * 37) % 1000 + 1) * 1000.00
FROM generate_series(1, 9000) AS g;
-- Angka kapasitas per tabel fisik: dasar setiap keputusan skala.
SELECT tableoid::regclass AS partisi,
count(*) AS baris,
pg_size_pretty(pg_relation_size(tableoid)) AS ukuran
FROM log_penjualan
GROUP BY 1
ORDER BY 1;
-- Statistik diisi agar rencana eksekusi berikutnya stabil.
ANALYZE log_penjualan;
verifikasi-benefit.sql
Unduh berkas-- Verifikasi mekanisme inti bekerja SEBELUM menambah kompleksitas baru.
-- Laporan satu hari: pruning memangkas ke satu partisi.
EXPLAIN SELECT count(*)
FROM log_penjualan
WHERE tanggal = DATE '2026-02-15';
-- Rekap kuartal penuh: ketiga partisi memang diperlukan.
EXPLAIN SELECT count(*)
FROM log_penjualan;