Memahami Database Partitioning: Dari Konsep, Performa, sampai Kapan Harus Dihindari
Tabel dengan puluhan juta baris punya masalah khas: query yang dulunya instan mulai terasa berat, index maintenance memakan waktu lama, dan operasi seperti DELETE data lama bisa mengunci tabel selama menit-menit yang menyakitkan. Partitioning sering muncul sebagai jawaban pertama yang terlintas — pecah tabel besar jadi potongan-potongan lebih kecil, masalah selesai. Tapi kenyataannya tidak sesederhana itu. Partitioning yang didesain dengan partition key yang salah, atau diterapkan pada tabel yang sebenarnya belum butuh, justru bisa menambah kompleksitas operasional tanpa manfaat performa yang berarti. Artikel ini membahas partitioning dari konsepnya, bagaimana database benar-benar memprosesnya di balik layar, seberapa besar dampak performanya secara realistis, sampai kapan sebaiknya kamu tidak usah repot-repot menerapkannya.
Apa itu Partitioning
Partitioning adalah teknik membagi satu tabel logis menjadi beberapa partisi — potongan fisik terpisah yang secara internal disimpan sebagai objek storage sendiri-sendiri, tapi dari sudut pandang aplikasi tetap terlihat sebagai satu tabel tunggal. Kamu tetap INSERT, SELECT, UPDATE ke nama tabel induknya seperti biasa; database yang menentukan partisi mana yang relevan untuk menyimpan atau membaca baris tertentu.
Bayangkan tabel orders dengan seratus juta baris. Tanpa partitioning, seluruh baris itu berada dalam satu struktur fisik tunggal — satu set file data, satu set index. Dengan partitioning berbasis tanggal misalnya, data itu dipecah menjadi partisi per bulan: orders_2026_01, orders_2026_02, dan seterusnya. Setiap partisi punya file data dan index sendiri, meski secara logis semuanya tetap bagian dari tabel orders.
-- Tabel induk terpartisi berdasarkan range tanggal
CREATE TABLE orders (
id BIGSERIAL,
pelanggan_id BIGINT NOT NULL,
total NUMERIC(12,2) NOT NULL,
created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);
-- Partisi individual untuk tiap bulan
CREATE TABLE orders_2026_01 PARTITION OF orders
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE orders_2026_02 PARTITION OF orders
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
flowchart TD
A[Tabel Logis: orders] --> B[Partisi: orders_2026_01]
A --> C[Partisi: orders_2026_02]
A --> D[Partisi: orders_2026_03]
A --> E[... dan seterusnya]Kolom yang dipakai untuk menentukan partisi mana yang menampung baris tertentu disebut partition key. Pemilihan partition key adalah keputusan paling krusial dalam desain partitioning — keputusan yang salah di sini akan terasa dampaknya di hampir semua section berikutnya dalam artikel ini, terutama di bagian performa.
Penting dicatat bahwa partitioning berbeda dari sekadar membagi data secara manual ke beberapa tabel terpisah tanpa relasi formal. Ciri khas partitioning yang sesungguhnya adalah adanya abstraksi tabel induk — aplikasi tidak perlu tahu partisi mana yang harus dituju, database yang menangani routing itu secara otomatis berdasarkan definisi partition key.
Cara Kerja Partitioning di Balik Layar
Manfaat utama partitioning datang dari mekanisme bernama partition pruning (kadang disebut partition elimination). Ketika kamu menjalankan query dengan kondisi WHERE yang menyentuh partition key, query planner cukup pintar untuk mengenali partisi mana saja yang mungkin berisi data relevan, lalu mengabaikan sepenuhnya partisi-partisi yang lain — tanpa perlu membacanya sama sekali.
sequenceDiagram
participant App as Aplikasi
participant Planner as Query Planner
participant P1 as Partisi Januari
participant P2 as Partisi Februari
participant P3 as Partisi Maret
App->>Planner: SELECT * FROM orders WHERE created_at >= '2026-02-01' AND created_at < '2026-03-01'
Planner->>Planner: Analisis kondisi WHERE terhadap batas partisi
Planner->>Planner: Tentukan hanya Partisi Februari relevan
Planner-->>P1: (diabaikan, tidak discan)
Planner->>P2: Scan hanya partisi ini
Planner-->>P3: (diabaikan, tidak discan)
P2-->>App: Hasil queryInilah alasan mengapa partitioning bisa memberi lonjakan performa signifikan untuk query yang tepat: alih-alih memindai seratus juta baris di satu tabel besar, database hanya perlu memindai beberapa juta baris di satu partisi yang relevan. Efeknya mirip seperti index, tapi bekerja di level struktur fisik tabel, bukan di level baris individual.
Namun ini juga menjelaskan sisi sebaliknya yang sering luput dipahami: kalau query tidak menyertakan kondisi terhadap partition key, planner tidak punya cara untuk melakukan pruning. Akibatnya database harus memindai semua partisi satu per satu — yang dalam beberapa kasus justru sedikit lebih lambat dibanding satu tabel besar tanpa partisi, karena ada overhead tambahan untuk membuka dan mengoordinasikan banyak partisi sekaligus.
flowchart TD
A[Query TANPA filter partition key] --> B{Planner tidak bisa pruning}
B --> C[Scan Partisi Januari]
B --> D[Scan Partisi Februari]
B --> E[Scan Partisi Maret]
B --> F[... scan semua partisi]Partition Pruning: Statis vs Dinamis
Ada dua jenis partition pruning yang perlu dipahami. Static pruning terjadi saat query di-parse, sebelum eksekusi dimulai — cocok untuk kondisi dengan nilai literal yang sudah pasti, seperti WHERE created_at = '2026-02-15'. Dynamic pruning terjadi saat eksekusi berjalan, dibutuhkan untuk kasus seperti WHERE created_at = (SELECT tanggal FROM parameter_report) di mana nilai partition key baru diketahui setelah subquery dieksekusi. Tidak semua database mendukung dynamic pruning dengan baik — PostgreSQL versi modern mendukungnya, tapi versi lama atau database lain mungkin terpaksa scan semua partisi meski secara logis harusnya bisa di-pruning.
Tipe-tipe Partitioning
Ada beberapa strategi untuk menentukan bagaimana baris didistribusikan ke partisi, masing-masing cocok untuk pola data dan query yang berbeda.
Range Partitioning
Baris didistribusikan berdasarkan rentang nilai — paling umum dipakai untuk kolom tanggal atau angka berurutan seperti ID.
CREATE TABLE log_aktivitas (
id BIGSERIAL,
user_id BIGINT,
aktivitas TEXT,
created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);
CREATE TABLE log_aktivitas_q1_2026 PARTITION OF log_aktivitas
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
Range partitioning paling cocok untuk data time-series seperti log, transaksi, atau metrik — pola akses yang umum adalah query terhadap rentang waktu tertentu, dan data lama sering di-archive atau dihapus secara berkala.
List Partitioning
Baris didistribusikan berdasarkan daftar nilai diskret tertentu — cocok untuk kolom kategorikal seperti region, status, atau tenant.
CREATE TABLE pesanan (
id BIGSERIAL,
region TEXT NOT NULL,
total NUMERIC(12,2)
) PARTITION BY LIST (region);
CREATE TABLE pesanan_jawa PARTITION OF pesanan
FOR VALUES IN ('jakarta', 'bandung', 'surabaya');
CREATE TABLE pesanan_luar_jawa PARTITION OF pesanan
FOR VALUES IN ('medan', 'makassar', 'denpasar');
List partitioning cocok ketika query aplikasi secara alami sering memfilter berdasarkan kategori tersebut, misalnya laporan operasional per region.
Hash Partitioning
Baris didistribusikan berdasarkan hasil hash dari partition key, tersebar merata ke sejumlah partisi tanpa mempedulikan makna nilainya.
CREATE TABLE sesi_user (
id BIGSERIAL,
user_id BIGINT NOT NULL,
data JSONB
) PARTITION BY HASH (user_id);
CREATE TABLE sesi_user_p0 PARTITION OF sesi_user
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE sesi_user_p1 PARTITION OF sesi_user
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE sesi_user_p2 PARTITION OF sesi_user
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE sesi_user_p3 PARTITION OF sesi_user
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
Hash partitioning berguna ketika kamu butuh distribusi data yang merata untuk keperluan skala tulis (write scaling) dan tidak ada rentang nilai alami yang bisa dipakai untuk range atau list — misalnya distribusi beban berdasarkan user_id yang nilainya acak. Trade-off-nya, partition pruning untuk hash jauh lebih terbatas dibanding range: kamu tidak bisa melakukan pruning berdasarkan rentang nilai, hanya berdasarkan kesetaraan nilai persis.
Composite Partitioning
Menggabungkan dua strategi bertingkat — misalnya range di level pertama, lalu hash atau list di level kedua (disebut juga sub-partitioning).
-- Level 1: range berdasarkan tanggal
CREATE TABLE transaksi (
id BIGSERIAL,
region TEXT NOT NULL,
created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);
CREATE TABLE transaksi_2026_q1 PARTITION OF transaksi
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01')
PARTITION BY LIST (region);
-- Level 2: list berdasarkan region, di dalam partisi Q1
CREATE TABLE transaksi_2026_q1_jawa PARTITION OF transaksi_2026_q1
FOR VALUES IN ('jakarta', 'bandung');
Composite partitioning cocok untuk tabel dengan volume sangat besar yang punya dua pola akses dominan sekaligus — misalnya query yang sering memfilter berdasarkan tanggal dan region secara bersamaan.
| Tipe | Cocok Untuk | Partition Pruning | Distribusi Data |
|---|---|---|---|
| Range | Data time-series, ID berurutan | Sangat efektif untuk query rentang | Bisa tidak merata (bulan sibuk vs sepi) |
| List | Kategori diskret (region, status, tenant) | Efektif untuk query per kategori | Tergantung distribusi kategori |
| Hash | Write scaling, tidak ada rentang alami | Terbatas, hanya kesetaraan | Merata by design |
| Composite | Volume sangat besar, dua pola akses dominan | Kombinasi dari kedua level | Tergantung kombinasi strategi |
Partitioning vs Sharding — Beda yang Sering Ketuker
Istilah partitioning dan sharding sering dipakai bergantian secara tidak tepat, padahal keduanya beroperasi di level yang berbeda.
Partitioning terjadi di dalam satu instance database yang sama. Semua partisi tetap berada di server (atau cluster) database yang sama, dikelola oleh satu database engine, dan query lintas partisi masih bisa dilakukan dalam satu transaksi tanpa masalah koordinasi lintas jaringan.
Sharding adalah membagi data ke beberapa instance database terpisah — bisa di server berbeda, bahkan datacenter berbeda. Setiap shard pada dasarnya adalah database independen dengan skema yang sama, dan aplikasi (atau layer middleware) yang bertanggung jawab menentukan shard mana yang harus dituju untuk data tertentu.
flowchart TD
subgraph Partitioning [Partitioning - Satu Instance Database]
A[Tabel Logis] --> B[Partisi 1]
A --> C[Partisi 2]
A --> D[Partisi 3]
end
subgraph Sharding [Sharding - Banyak Instance Terpisah]
E[Aplikasi/Router] --> F[Database Shard 1]
E --> G[Database Shard 2]
E --> H[Database Shard 3]
end| Aspek | Partitioning | Sharding |
|---|---|---|
| Lokasi data | Satu instance database | Banyak instance/server terpisah |
| Transaksi lintas bagian | Native didukung dalam satu instance | Butuh distributed transaction (kompleks) |
| Skalabilitas | Terbatas oleh kapasitas satu server | Bisa scale horizontal nyaris tanpa batas |
| Kompleksitas operasional | Relatif rendah, dikelola satu engine | Tinggi, butuh routing layer & koordinasi |
| Kegunaan utama | Mempercepat query & maintenance pada tabel besar | Mengatasi batas kapasitas satu server |
Keduanya bukan opsi yang saling eksklusif — banyak arsitektur skala besar memakai keduanya sekaligus: data di-shard ke beberapa server berdasarkan tenant misalnya, lalu di setiap shard, tabel yang besar dipartisi lagi berdasarkan tanggal untuk mempermudah query dan retention.
Sebagian orang menyebut partitioning sebagai “vertical scaling yang diorganisir dengan rapi”, sementara sharding adalah “horizontal scaling”. Analogi ini membantu tapi tidak sepenuhnya presisi — partitioning tidak menambah kapasitas total server, ia hanya mengorganisir data yang sudah ada di server yang sama agar lebih efisien diakses dan dikelola.
Membuat dan Mengelola Partisi
Selain membuat partisi awal, pengelolaan partisi jangka panjang — menambah partisi baru dan membuang yang lama — adalah bagian yang sama pentingnya dengan desain awal.
-- Menambahkan partisi baru untuk bulan mendatang
CREATE TABLE orders_2026_03 PARTITION OF orders
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
-- Melepas partisi lama tanpa menghapus datanya (bisa diarsipkan terpisah)
ALTER TABLE orders DETACH PARTITION orders_2023_01;
-- Menempelkan kembali tabel biasa menjadi partisi baru
ALTER TABLE orders ATTACH PARTITION orders_2026_04
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
-- Setelah detach, partisi lama bisa langsung di-drop untuk retention policy
DROP TABLE orders_2023_01;
Operasi DETACH PARTITION lalu DROP TABLE jauh lebih efisien dibanding DELETE FROM orders WHERE created_at < '2023-02-01' pada tabel besar tanpa partisi. DELETE biasa harus menghapus baris satu per satu, menghasilkan banyak dead tuple yang perlu dibersihkan oleh proses vacuum, dan bisa mengunci tabel dalam waktu lama. DETACH lalu DROP pada partisi hanya butuh mengubah metadata dan menghapus file secara langsung — operasinya nyaris instan tidak peduli seberapa besar partisi tersebut.
Automasi Pembuatan Partisi
Karena partisi baru untuk range waktu (misalnya bulan depan) harus sudah ada sebelum data mulai masuk, tim biasanya mengotomasi proses ini lewat scheduled job.
-- Contoh fungsi PostgreSQL untuk membuat partisi bulan berikutnya secara otomatis
CREATE OR REPLACE FUNCTION buat_partisi_bulan_depan()
RETURNS void AS $$
DECLARE
awal DATE := DATE_TRUNC('month', NOW() + INTERVAL '1 month');
akhir DATE := awal + INTERVAL '1 month';
nama_partisi TEXT := 'orders_' || TO_CHAR(awal, 'YYYY_MM');
BEGIN
EXECUTE FORMAT(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF orders FOR VALUES FROM (%L) TO (%L)',
nama_partisi, awal, akhir
);
END;
$$ LANGUAGE plpgsql;
Beberapa database seperti PostgreSQL versi terbaru mulai mendukung pembuatan partisi otomatis secara native untuk range waktu, tapi banyak tim masih memilih kontrol eksplisit lewat scheduled job supaya proses ini bisa dimonitor dan diaudit dengan jelas.
Kalau kamu lupa membuat partisi untuk rentang waktu mendatang sebelum data mulai masuk, INSERT akan gagal dengan error “no partition found” (tergantung apakah kamu punya default partition atau tidak). Selalu pastikan automasi pembuatan partisi berjalan dengan buffer waktu yang cukup — jangan menunggu sampai bulan berjalan baru membuat partisinya.Performa: Manfaat dan Jebakan Partitioning
Ini bagian inti dari keseluruhan pembahasan partitioning, karena keputusan menerapkan partitioning pada dasarnya adalah keputusan performa dan operasional — bukan soal fitur baru yang “bagus untuk dimiliki”.
Manfaat Nyata pada Query yang Tepat
Untuk query yang menyertakan kondisi terhadap partition key, manfaatnya bisa dramatis. Bayangkan tabel orders dengan seratus juta baris terpartisi per bulan menjadi kira-kira tiga juta baris per partisi. Query yang mencari transaksi di bulan tertentu hanya perlu memindai satu partisi berisi tiga juta baris, bukan seratus juta — potensi peningkatan performa berkali-kali lipat, tergantung seberapa selektif kondisi partition pruning-nya.
Manfaat lain yang sering terlewat adalah pada index maintenance. Index pada tabel seratus juta baris butuh waktu jauh lebih lama untuk dibangun ulang (REINDEX) dibanding index pada partisi tiga juta baris. Karena setiap partisi punya index sendiri-sendiri, operasi maintenance bisa dilakukan per partisi, sehingga downtime atau beban yang ditimbulkan jauh lebih kecil dan bisa dijadwalkan secara granular.
Jebakan yang Sering Tidak Disadari
Query tanpa filter partition key jadi lebih lambat, bukan lebih cepat. Ini poin paling penting untuk dipahami. Kalau pola akses aplikasi kamu banyak melakukan query tanpa menyentuh partition key sama sekali — misalnya SELECT * FROM orders WHERE pelanggan_id = 42 pada tabel yang dipartisi berdasarkan created_at — database terpaksa memindai semua partisi satu per satu. Overhead untuk membuka, mengoordinasikan, dan menggabungkan hasil dari banyak partisi ini bisa membuat query jadi lebih lambat dibanding kalau tabel tidak dipartisi sama sekali dan cukup mengandalkan index biasa pada kolom pelanggan_id.
-- ✗ Query tidak menyentuh partition key (created_at), harus scan semua partisi
SELECT * FROM orders WHERE pelanggan_id = 42;
-- ✓ Kombinasikan dengan kondisi partition key jika memungkinkan
SELECT * FROM orders
WHERE pelanggan_id = 42
AND created_at >= '2026-01-01' AND created_at < '2026-04-01';
Over-partitioning menambah overhead planning. Setiap partisi menambah kerja bagi query planner untuk menentukan mana yang relevan, bahkan sebelum pruning terjadi. Kalau kamu membuat ribuan partisi kecil (misalnya partisi per jam untuk tabel yang sebenarnya tidak sebesar itu), overhead planning itu sendiri bisa jadi lebih mahal dibanding manfaat pruning yang didapat. Beberapa database punya batas praktis jumlah partisi yang direkomendasikan — melebihi itu, performa keseluruhan sistem (termasuk query planning untuk tabel lain) bisa ikut terdampak.
Index lokal vs index global. Sebagian besar database mengharuskan index pada tabel terpartisi dibuat sebagai index lokal per partisi, bukan index global tunggal yang mencakup seluruh tabel. Ini berarti kalau kamu butuh keunikan (UNIQUE) di seluruh tabel bukan hanya per partisi, kamu harus memastikan partition key ikut serta dalam definisi constraint tersebut — batasan yang sering mengejutkan tim yang baru pertama kali mendesain skema terpartisi.
-- ✗ Constraint UNIQUE pada kolom yang bukan bagian dari partition key akan ditolak
CREATE TABLE orders (
id BIGSERIAL,
kode_invoice TEXT UNIQUE, -- error: harus menyertakan partition key
created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);
-- ✓ Sertakan partition key dalam definisi UNIQUE
CREATE TABLE orders (
id BIGSERIAL,
kode_invoice TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
UNIQUE (kode_invoice, created_at)
) PARTITION BY RANGE (created_at);
Join antar tabel besar terpartisi butuh perhatian ekstra. Kalau kedua tabel yang di-JOIN sama-sama dipartisi dengan skema yang sejalan (disebut partition-wise join), sebagian database bisa melakukan join per pasangan partisi yang cocok, jauh lebih efisien dibanding join brute-force terhadap seluruh data. Tapi ini butuh dukungan eksplisit dari database dan konfigurasi yang tepat — tidak otomatis terjadi begitu saja hanya karena kedua tabel dipartisi.
Mengukur Dampak Performa Secara Nyata
Jangan berasumsi partitioning pasti membantu tanpa mengujinya. Gunakan EXPLAIN ANALYZE untuk memverifikasi bahwa pruning benar-benar terjadi sesuai harapan.
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE created_at >= '2026-02-01' AND created_at < '2026-03-01';
Kalau output menunjukkan hanya satu partisi yang di-scan (orders_2026_02) dan partisi lain muncul sebagai “pruned” atau tidak muncul sama sekali dalam plan, itu tanda partition pruning bekerja sesuai harapan. Kalau semua partisi tetap muncul dalam execution plan padahal query kamu sudah menyertakan kondisi partition key, ada kemungkinan tipe data, cast implisit, atau bentuk kondisi WHERE yang menghalangi planner melakukan pruning — masalah yang sering terjadi ketika kondisi ditulis dalam bentuk fungsi seperti WHERE DATE(created_at) = '2026-02-15' alih-alih perbandingan langsung.
Kapan Sebaiknya Pakai Partitioning
PAKAI partitioning jika:
✓ tabel sudah berukuran puluhan juta baris ke atas dan terus bertambah
✓ ada partition key alami yang konsisten dipakai di pola query (tanggal, region, tenant)
✓ butuh retention/archiving data lama secara berkala (drop partisi jauh lebih cepat dari DELETE)
✓ index maintenance pada tabel besar sudah mulai jadi masalah operasional
✓ mayoritas query aplikasi memang memfilter berdasarkan calon partition key
Kapan Harus Dihindari
Karena judul artikel ini secara eksplisit menyoroti pertanyaan ini, penting untuk membahasnya lebih dalam daripada sekadar daftar poin ✗.
Tabel masih relatif kecil. Kalau tabel kamu baru berisi beberapa ratus ribu atau bahkan beberapa juta baris, index biasa pada kolom yang tepat kemungkinan besar sudah cukup cepat tanpa perlu partitioning sama sekali. Partitioning menambah kompleksitas skema, kompleksitas query constraint (seperti kasus UNIQUE di atas), dan kompleksitas operasional (perlu automasi pembuatan partisi baru) — biaya ini baru sepadan ketika ukuran data benar-benar jadi masalah nyata, bukan masalah hipotetis di masa depan.
Pola query tidak konsisten menyentuh calon partition key. Ini adalah alasan paling umum kenapa proyek partitioning gagal memberi manfaat yang diharapkan. Sebelum memutuskan partition key, audit dulu query-query yang paling sering dan paling berat dijalankan terhadap tabel tersebut. Kalau ternyata query-query itu memfilter berdasarkan kolom yang beragam dan tidak ada satu kolom dominan yang konsisten dipakai, partitioning berdasarkan satu kolom saja tidak akan memberi manfaat pruning yang berarti untuk sebagian besar query — dan malah berisiko memperlambat query yang tidak menyentuh partition key seperti dibahas di bagian performa.
Tim belum siap dengan beban operasional tambahan. Partitioning bukan “atur sekali lalu lupakan”. Ada kebutuhan berkelanjutan: memastikan partisi baru selalu tersedia sebelum data masuk, memonitor jumlah dan ukuran partisi supaya tidak over-partitioning, serta memahami batasan constraint yang muncul akibat partitioning. Kalau tim belum punya kapasitas atau proses untuk menangani beban operasional ini secara berkelanjutan, partitioning bisa berubah dari solusi jadi sumber insiden baru — misalnya INSERT yang gagal karena partisi bulan berjalan belum dibuat.
Masalah performa sebenarnya ada di tempat lain. Kadang tabel besar terasa lambat bukan karena ukurannya, tapi karena kurang index yang tepat, query yang tidak efisien (misalnya SELECT * pada tabel dengan banyak kolom besar), atau resource server yang memang sudah mendekati batas. Partitioning tidak menyelesaikan masalah-masalah ini. Sebelum memutuskan partitioning, pastikan kamu sudah mengesampingkan penyebab performa yang lebih mendasar dan lebih murah untuk diperbaiki.
HINDARI partitioning jika:
✗ tabel masih berukuran kecil hingga menengah tanpa masalah performa nyata
✗ tidak ada partition key yang konsisten dipakai di mayoritas query
✗ tim belum siap menangani beban operasional pembuatan & monitoring partisi
✗ masalah performa sebenarnya bersumber dari kurang index atau query yang tidak efisien
Anti-Pattern Umum
-- ✗ Partition key dipilih berdasarkan kolom yang jarang dipakai di WHERE
CREATE TABLE orders (
id BIGSERIAL,
kode_referral TEXT, -- jarang dipakai untuk filter query
created_at TIMESTAMP NOT NULL
) PARTITION BY LIST (kode_referral);
-- ✓ Pilih partition key berdasarkan kolom yang paling sering dipakai untuk filter
CREATE TABLE orders (
id BIGSERIAL,
kode_referral TEXT,
created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);
-- ✗ Over-partitioning: partisi per jam untuk tabel yang datanya tidak sebesar itu
CREATE TABLE log_kecil_2026_01_01_00 PARTITION OF log_kecil
FOR VALUES FROM ('2026-01-01 00:00:00') TO ('2026-01-01 01:00:00');
-- Menghasilkan ribuan partisi kecil, overhead planning lebih mahal dari manfaatnya
-- ✓ Granularitas partisi disesuaikan dengan volume data sebenarnya
CREATE TABLE log_kecil_2026_01 PARTITION OF log_kecil
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
-- ✗ Kondisi WHERE dalam bentuk fungsi menghalangi partition pruning
SELECT * FROM orders WHERE DATE(created_at) = '2026-02-15';
-- ✓ Bentuk kondisi langsung agar planner bisa melakukan pruning
SELECT * FROM orders
WHERE created_at >= '2026-02-15' AND created_at < '2026-02-16';
Best Practice Partitioning
1. Audit Pola Query Sebelum Memilih Partition Key
Jangan menebak. Analisis query log atau pg_stat_statements (di PostgreSQL) untuk melihat kolom apa yang paling sering muncul di kondisi WHERE pada query yang paling berat, lalu jadikan itu kandidat utama partition key.
2. Sesuaikan Granularitas dengan Volume Data Nyata
Partisi idealnya cukup besar untuk mengurangi overhead planning, tapi cukup kecil untuk memberi manfaat pruning yang berarti. Sebagai patokan kasar, targetkan ukuran partisi yang membuat setiap partisi tetap nyaman dikelola (misalnya index-nya bisa di-rebuild dalam waktu wajar), bukan sekadar mengikuti pola waktu yang terasa “rapi” seperti per hari atau per jam tanpa mempertimbangkan volume sebenarnya.
3. Otomasi Pembuatan dan Pembersihan Partisi
Jangan mengandalkan proses manual untuk membuat partisi baru atau menghapus partisi lama. Gunakan scheduled job dengan buffer waktu yang cukup, dan pastikan ada alerting kalau proses otomasi ini gagal berjalan.
4. Rancang Constraint dengan Partition Key Sejak Awal
Karena UNIQUE dan PRIMARY KEY pada tabel terpartisi harus menyertakan partition key, rancang skema constraint ini sejak tahap desain, bukan setelah tabel sudah berjalan di production dan sulit diubah.
5. Verifikasi Pruning dengan EXPLAIN Secara Rutin
Jangan hanya memverifikasi sekali saat awal implementasi. Seiring waktu, pola query aplikasi bisa berubah, ORM bisa menghasilkan bentuk query yang tidak terduga, atau tipe data bisa berubah — semua ini bisa diam-diam menghalangi partition pruning tanpa ada error yang muncul. Jadikan pengecekan EXPLAIN ANALYZE bagian dari review berkala, bukan sekadar langkah verifikasi satu kali.
6. Monitor Jumlah dan Distribusi Ukuran Partisi
Partisi yang terlalu banyak atau terlalu tidak merata ukurannya (misalnya satu partisi jauh lebih besar dari yang lain akibat lonjakan traffic musiman) bisa jadi tanda partition key perlu direvisi atau strategi partitioning perlu disesuaikan menjadi composite.
Checklist singkat sebagai referensi cepat:
SEBELUM MENERAPKAN PARTITIONING:
□ Sudah audit pola query, partition key dipilih berdasarkan data nyata
□ Ukuran tabel memang sudah jadi masalah performa/operasional nyata
□ Tim siap dengan beban operasional pembuatan & monitoring partisi
□ Constraint UNIQUE/PRIMARY KEY sudah dirancang menyertakan partition key
SETELAH PARTITIONING BERJALAN:
□ Automasi pembuatan partisi baru berjalan dengan buffer waktu cukup
□ Alerting aktif kalau automasi gagal
□ EXPLAIN ANALYZE dicek secara rutin untuk memastikan pruning tetap terjadi
□ Distribusi ukuran antar partisi dimonitor secara berkala
RETENTION & ARCHIVING:
□ Strategi DETACH + DROP partisi lama sudah didefinisikan
□ Proses archiving data lama (kalau dibutuhkan) sudah berjalan sebelum DROP
Ringkasan
- Partitioning membagi satu tabel logis menjadi beberapa partisi fisik, dikelola dalam satu instance database yang sama, berbeda dari sharding yang membagi data ke banyak instance terpisah.
- Manfaat utama datang dari partition pruning — query dengan kondisi terhadap partition key hanya perlu memindai partisi relevan, bukan seluruh tabel.
- Query yang tidak menyentuh partition key justru bisa lebih lambat karena harus memindai semua partisi tanpa manfaat pruning.
- Tipe partitioning yang tersedia: range (time-series), list (kategori diskret), hash (write scaling merata), dan composite (kombinasi bertingkat).
UNIQUE/PRIMARY KEYpada tabel terpartisi harus menyertakan partition key — batasan yang perlu dirancang sejak awal.DETACH PARTITIONlaluDROP TABLEjauh lebih efisien untuk retention data lama dibandingDELETEmassal pada tabel besar.- Hindari partitioning kalau tabel masih kecil, query tidak konsisten memakai partition key, atau tim belum siap menangani beban operasional pembuatan dan monitoring partisi.
- Selalu verifikasi partition pruning dengan
EXPLAIN ANALYZEsecara rutin, karena perubahan pola query bisa diam-diam menghalangi pruning tanpa error yang terlihat.