Tiga Strategi Partisi
-- RANGE: paling umum, untuk data deret waktu
CREATE TABLE peristiwa (
id bigint GENERATED ALWAYS AS IDENTITY,
terjadi_pada timestamptz NOT NULL,
jenis text NOT NULL,
muatan jsonb
) PARTITION BY RANGE (terjadi_pada);
CREATE TABLE peristiwa_2026_07 PARTITION OF peristiwa
FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
-- LIST: untuk kategori diskret
CREATE TABLE pelanggan (...) PARTITION BY LIST (wilayah);
-- HASH: untuk menyebar beban secara merata
CREATE TABLE sesi (...) PARTITION BY HASH (pengguna_id);
CREATE TABLE sesi_0 PARTITION OF sesi
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
PostgreSQL tidak bisa menjamin keunikan lintas partisi tanpa kolom partisinya. Jadi PRIMARY KEY (id) ditolak; yang berlaku adalah PRIMARY KEY (id, terjadi_pada). Ini konsekuensi yang sering mengejutkan dan bisa memaksa perubahan pada foreign key yang menunjuk ke tabel tersebut.
Partition Pruning
EXPLAIN SELECT * FROM peristiwa WHERE terjadi_pada >= '2026-07-01' AND terjadi_pada < '2026-07-15'; -- Hanya peristiwa_2026_07 yang dipindai.
-- ❌ Memindai SEMUA partisi
SELECT * FROM peristiwa WHERE jenis = 'login';
-- ❌ Fungsi pada kunci partisi menggagalkan pruning
WHERE date_trunc('day', terjadi_pada) = '2026-07-15'
-- ✅ Bandingkan kolomnya secara langsung
WHERE terjadi_pada >= '2026-07-15'
AND terjadi_pada < '2026-07-16'
Bila mayoritas kueri Anda tidak menyertakan kunci partisi, partisi justru memperlambat — perencana harus memeriksa banyak tabel sekaligus.
Manfaat Terbesar: Menghapus Data Lama
-- ❌ DELETE massal: berjam-jam, WAL membengkak, -- jutaan dead tuple, bloat parah DELETE FROM peristiwa WHERE terjadi_pada < '2025-07-01'; -- ✅ Lepaskan lalu buang partisi: nyaris instan ALTER TABLE peristiwa DETACH PARTITION peristiwa_2025_06 CONCURRENTLY; DROP TABLE peristiwa_2025_06;
Banyak yang mengira partisi terutama mempercepat kueri. Untuk tabel dengan indeks yang baik, peningkatannya sering tidak sebesar yang dibayangkan. Manfaat yang benar-benar berubah drastis adalah pemeliharaan: menghapus data lama menjadi instan tanpa bloat, VACUUM berjalan per partisi sehingga jauh lebih cepat, dan pembuatan indeks bisa dilakukan sepotong demi sepotong.
Mengelola Partisi Secara Otomatis
-- Partisi default menangkap baris di luar rentang mana pun
CREATE TABLE peristiwa_default PARTITION OF peristiwa DEFAULT;
-- Membuat partisi bulan depan secara terjadwal
DO $$
DECLARE
awal date := date_trunc('month', now() + interval '1 month');
akhir date := awal + interval '1 month';
nama text := 'peristiwa_' || to_char(awal, 'YYYY_MM');
BEGIN
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF peristiwa
FOR VALUES FROM (%L) TO (%L)', nama, awal, akhir);
END $$;
Partisi default berguna sebagai jaring pengaman agar penyisipan tidak gagal ketika partisi yang seharusnya belum dibuat. Tetapi begitu ada baris di dalamnya, menambah partisi baru yang rentangnya tumpang tindih akan gagal — PostgreSQL harus memindai seluruh partisi default untuk memastikan tidak ada baris yang seharusnya pindah. Pantau isinya dan jaga agar tetap kosong.
Kapan Belum Perlu Partisi
Pertimbangkan partisi ketika tabel sudah melewati sekitar 50–100 GB, atau ketika Anda rutin menghapus data lama dalam jumlah besar. Di bawah itu, indeks yang dirancang baik hampir selalu cukup.
Yang menjadi lebih rumit setelah partisi: kunci utama wajib menyertakan kolom partisi, foreign key yang menunjuk ke tabel partisi punya batasan, indeks unik global tidak bisa dibuat, dan Anda harus mengelola pembuatan partisi baru secara berkelanjutan.