Partitioning #
Tabel orders yang tadinya selesai diquery dalam 50ms kini butuh 4 detik. Index sudah ada. Query sudah dioptimalkan. Tapi data terus bertambah — sekarang sudah 500 juta baris — dan tidak ada lagi yang bisa dilakukan di level query untuk mengembalikan performa seperti semula. Inilah titik di mana partitioning menjadi relevan: bukan sebagai optimasi awal, tapi sebagai solusi skalabilitas ketika tabel sudah tumbuh melampaui batas yang bisa ditangani index biasa.
Partitioning membagi satu tabel logis menjadi beberapa segmen fisik yang terpisah — disebut partition. Dari sisi aplikasi, tabel tetap terlihat satu dan query ditulis seperti biasa. Dari sisi database, hanya partition yang relevan yang dibaca, bukan seluruh tabel. Untuk tabel 500 juta baris yang dipartisi per bulan, query yang memfilter satu bulan hanya perlu membaca sekitar 40 juta baris — bukan setengah miliar.
Tapi partitioning bukan tanpa biaya. Ia membawa kompleksitas baru: partition key yang salah justru membuat query lebih lambat, terlalu banyak partition membebani query planner, dan query yang tidak menyertakan filter partition key akan memindai semua partition sekaligus. Memahami cara kerja partitioning secara mendalam adalah prasyarat untuk menggunakannya dengan benar.
Cara Kerja Partitioning dan Partition Pruning #
Partitioning bekerja dengan menyimpan data di segmen fisik yang berbeda berdasarkan nilai partition key. Ketika query dijalankan dengan filter pada partition key, database melakukan partition pruning — ia menentukan partition mana saja yang mungkin mengandung data yang dicari, lalu hanya membaca partition tersebut.
Tanpa partitioning — query memindai seluruh tabel:
Tabel orders (500 juta baris, semua di satu segmen fisik):
┌─────────────────────────────────────────────────────────────┐
│ Jan 2023 │ Feb 2023 │ ... │ Des 2024 │ Jan 2025 │ Feb 2025 │
└─────────────────────────────────────────────────────────────┘
← database harus scan semua ini →
SELECT * FROM orders WHERE created_at >= '2025-02-01'
→ Scan 500 juta baris untuk menemukan ~5 juta baris Februari 2025
→ Boros I/O, lambat, index besar dan tidak efisien
Dengan partitioning per bulan — query hanya membaca partition relevan:
orders_2023_01 │ orders_2023_02 │ ... │ orders_2025_01 │ orders_2025_02
───────────────────────────────────────────────────────────────────────
↑
hanya ini yang dibaca
SELECT * FROM orders WHERE created_at >= '2025-02-01'
→ Partition pruning: hanya orders_2025_02 yang dibaca
→ Scan ~5 juta baris, bukan 500 juta
→ I/O berkurang 99%, query jauh lebih cepat
Kunci dari seluruh manfaat partitioning ada di pruning ini. Jika pruning tidak terjadi — misalnya karena filter tidak menyertakan partition key, atau partition key dibungkus fungsi — semua partition tetap dibaca dan tidak ada keuntungan performa sama sekali.
-- Cara verifikasi apakah partition pruning terjadi:
EXPLAIN SELECT * FROM orders WHERE created_at >= '2025-02-01';
-- Output EXPLAIN menampilkan kolom 'partitions':
-- partitions: orders_2025_02 → pruning berhasil, hanya 1 partition
-- partitions: orders_2023_01,... → pruning gagal, semua partition dibaca
-- Contoh query yang MENCEGAH pruning (anti-pattern):
EXPLAIN SELECT * FROM orders WHERE YEAR(created_at) = 2025;
-- partitions: semua partition → fungsi YEAR() mencegah pruning
-- Tulis ulang: WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01'
Empat Jenis Partitioning #
Range Partitioning #
Range partitioning membagi data berdasarkan rentang nilai — paling umum digunakan untuk data berbasis waktu. Setiap partition menyimpan data yang nilai partition key-nya jatuh dalam rentang tertentu.
Ini adalah jenis partitioning yang paling tepat untuk tabel yang datanya bersifat time-series: log, event, order, transaksi, audit trail. Query hampir selalu memfilter berdasarkan waktu, dan data lama mudah dihapus dengan menjatuhkan partition — operasi O(1) yang jauh lebih cepat dari DELETE.
-- Range partitioning berdasarkan bulan (MySQL)
CREATE TABLE orders (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
total DECIMAL(15,2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id, created_at) -- partition key harus bagian dari primary key
) PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
PARTITION p2024_01 VALUES LESS THAN (UNIX_TIMESTAMP('2024-02-01')),
PARTITION p2024_02 VALUES LESS THAN (UNIX_TIMESTAMP('2024-03-01')),
PARTITION p2024_03 VALUES LESS THAN (UNIX_TIMESTAMP('2024-04-01')),
-- ... dan seterusnya
PARTITION p2025_02 VALUES LESS THAN (UNIX_TIMESTAMP('2025-03-01')),
PARTITION p_future VALUES LESS THAN MAXVALUE -- menampung data masa depan
);
-- PostgreSQL: range partitioning lebih bersih dengan PARTITION BY RANGE
CREATE TABLE orders (
id BIGSERIAL,
user_id BIGINT NOT NULL,
total NUMERIC(15,2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMPTZ NOT NULL
) PARTITION BY RANGE (created_at);
-- Buat partition per bulan
CREATE TABLE orders_2025_01
PARTITION OF orders
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
CREATE TABLE orders_2025_02
PARTITION OF orders
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');
Visualisasi range partitioning per bulan:
orders
├── orders_2024_01 [Jan 2024: id 1–42M]
├── orders_2024_02 [Feb 2024: id 42M–81M]
├── orders_2024_03 [Mar 2024: id 81M–125M]
│ ...
├── orders_2025_01 [Jan 2025: id 450M–492M]
├── orders_2025_02 [Feb 2025: id 492M–...] ← partition aktif saat ini
└── p_future [data setelah Feb 2025] ← fallback
Query: WHERE created_at >= '2025-02-01'
→ Hanya baca orders_2025_02 dan p_future
→ 2 partition dari 26 total → pruning 92%
Keunggulan utama range partitioning: menghapus data lama semudah menjalankan DROP PARTITION — tidak perlu DELETE yang berat, tidak ada fragmentasi, tidak ada lock lama. Untuk sistem dengan retensi data 2 tahun misalnya, ini adalah operasi maintenance yang paling efisien.
-- Menghapus data lama: DROP PARTITION vs DELETE
-- Tanpa partitioning — DELETE berat, memakan waktu menit sampai jam:
DELETE FROM orders WHERE created_at < '2024-01-01';
-- Lock lama, WAL besar, fragmentasi tabel
-- Dengan partitioning — DROP PARTITION: O(1), hampir instan:
ALTER TABLE orders DROP PARTITION p2023_12;
-- Tidak ada lock signifikan, tidak ada fragmentasi, selesai dalam detik
List Partitioning #
List partitioning membagi data berdasarkan nilai diskrit yang sudah diketahui — cocok ketika data dikelompokkan berdasarkan kategori tetap seperti negara, region, atau tenant.
-- List partitioning berdasarkan region (PostgreSQL)
CREATE TABLE transactions (
id BIGSERIAL,
user_id BIGINT NOT NULL,
amount NUMERIC(15,2) NOT NULL,
region VARCHAR(10) NOT NULL, -- 'ID', 'SG', 'MY', 'TH', 'PH'
created_at TIMESTAMPTZ NOT NULL
) PARTITION BY LIST (region);
CREATE TABLE transactions_id PARTITION OF transactions FOR VALUES IN ('ID');
CREATE TABLE transactions_sg PARTITION OF transactions FOR VALUES IN ('SG');
CREATE TABLE transactions_my PARTITION OF transactions FOR VALUES IN ('MY');
CREATE TABLE transactions_sea -- gabungkan region kecil
PARTITION OF transactions FOR VALUES IN ('TH', 'PH', 'VN');
CREATE TABLE transactions_other
PARTITION OF transactions DEFAULT; -- menampung nilai yang tidak terdaftar
Kapan list partitioning tepat vs tidak tepat:
Tepat jika:
✓ Nilai partition key sudah diketahui dan relatif stabil
✓ Query hampir selalu memfilter berdasarkan nilai tersebut
✓ Ada keperluan isolasi data per nilai (contoh: data per negara untuk compliance)
✓ Beban per kategori cukup besar untuk dipisahkan
Tidak tepat jika:
✗ Nilai partition key sering bertambah — setiap nilai baru butuh partisi baru
✗ Data tidak merata — satu partition jauh lebih besar dari yang lain
✗ Query jarang memfilter berdasarkan kolom ini
Jika nilai partition key terus bertambah — misalnya daftar kota atau kategori produk yang selalu diperluas — list partitioning bisa menjadi beban operasional: setiap nilai baru membutuhkan DDL untuk membuat partition baru. Pertimbangkan apakah range atau hash lebih tepat untuk kasus seperti ini.
Hash Partitioning #
Hash partitioning mendistribusikan data secara merata berdasarkan nilai hash dari partition key. Berbeda dari range dan list, tidak ada semantik di sini — hanya distribusi yang seimbang.
-- Hash partitioning berdasarkan user_id (MySQL) — distribusi merata
CREATE TABLE user_activities (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
activity VARCHAR(100) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id, user_id)
) PARTITION BY HASH (user_id)
PARTITIONS 16; -- 16 partition, data didistribusikan secara merata
-- user_id 1 → hash(1) mod 16 = partition N
-- user_id 2 → hash(2) mod 16 = partition M
-- dst — distribusi merata tanpa hot spot
Perbandingan distribusi data:
Range partitioning (berdasarkan waktu):
Jan 2025 [━━━━━━━━━━━━━━━━━] ← semua traffic terbaru masuk sini (hot partition)
Des 2024 [━━━━━━━━━━]
Nov 2024 [━━━━━━━━]
Okt 2024 [━━━━━━━]
Hash partitioning (berdasarkan user_id):
Partition 0 [━━━━━━━━━]
Partition 1 [━━━━━━━━━]
Partition 2 [━━━━━━━━━] ← distribusi seragam
Partition 3 [━━━━━━━━━]
...semua sama ukurannya
Hash partitioning menyelesaikan masalah hot partition — kondisi di mana satu partition jauh lebih banyak diakses dari yang lain. Ini sering terjadi di range partitioning berbasis waktu: partition bulan ini selalu lebih panas dari partition bulan lalu.
Tapi hash partitioning tidak bisa melakukan partition pruning berbasis range atau waktu. Query seperti WHERE created_at >= '2025-02-01' harus membaca semua partition karena tidak ada cara untuk mengetahui di hash mana data Februari berada. Hash partitioning cocok untuk distribusi write yang merata, bukan untuk query berbasis waktu atau kategori.
Composite Partitioning #
Composite partitioning (atau subpartitioning) menggabungkan dua strategi sekaligus — biasanya range di level atas dan hash di level bawah. Digunakan untuk sistem dengan volume data sangat besar yang membutuhkan keduanya: kemampuan pruning berbasis waktu sekaligus distribusi write yang merata.
-- Composite: Range (bulan) + Hash (user_id) di PostgreSQL
-- Level 1: partisi per bulan (untuk pruning berbasis waktu)
CREATE TABLE events (
id BIGSERIAL,
user_id BIGINT NOT NULL,
type VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ NOT NULL
) PARTITION BY RANGE (created_at);
-- Level 2: setiap partisi bulan di-subpartisi berdasarkan user_id
CREATE TABLE events_2025_02
PARTITION OF events
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01')
PARTITION BY HASH (user_id);
CREATE TABLE events_2025_02_h0 PARTITION OF events_2025_02 FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE events_2025_02_h1 PARTITION OF events_2025_02 FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE events_2025_02_h2 PARTITION OF events_2025_02 FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE events_2025_02_h3 PARTITION OF events_2025_02 FOR VALUES WITH (MODULUS 4, REMAINDER 3);
Composite partitioning memberikan fleksibilitas terbesar tapi juga kompleksitas terbesar. Gunakan hanya jika sistem memang sudah berada di skala yang membutuhkannya — ratusan juta sampai miliaran baris dengan write rate yang sangat tinggi.
Memilih Partition Key yang Tepat #
Partition key adalah keputusan paling penting dalam merancang partitioning. Pilihan yang salah tidak hanya membuat partitioning tidak berguna — ia bisa membuat sistem lebih lambat dari sebelum dipartisi.
Framework memilih partition key:
Pertanyaan 1: Filter apa yang paling sering muncul di query?
→ Jika mayoritas query memfilter berdasarkan created_at → pakai created_at
→ Jika mayoritas query memfilter berdasarkan user_id → pakai user_id
→ Jika keduanya → pertimbangkan composite
Pertanyaan 2: Seberapa merata distribusi data pada kolom itu?
→ created_at biasanya merata (data terus masuk setiap hari)
→ status TIDAK merata ('pending' sedikit, 'completed' sangat banyak)
→ Status buruk sebagai partition key
Pertanyaan 3: Apakah ada kebutuhan maintenance berbasis nilai ini?
→ Ingin hapus data lebih dari 2 tahun → created_at ideal untuk range partition
→ Ingin isolasi data per tenant → tenant_id untuk list partition
Pertanyaan 4: Apakah ada hot spot yang perlu dihindari?
→ Jika semua write masuk ke partition terbaru (range by time) → hot partition
→ Tambahkan hash subpartition jika write rate sangat tinggi
Kolom yang baik sebagai partition key:
✓ created_at / event_date → time-series, pruning natural, maintenance mudah
✓ region / country_code → distribusi natural, isolasi data
✓ tenant_id → multi-tenant isolation
✓ user_id (untuk hash) → distribusi merata
✗ status → cardinality rendah, distribusi tidak merata
✗ boolean / is_active → hanya 2 nilai, tidak berguna
✗ kolom yang diubah-ubah → UPDATE pada partition key menyebabkan row berpindah partition
Automasi Lifecycle Partition #
Partitioning yang dikelola manual adalah resep untuk insiden. Jika partition bulan depan lupa dibuat, semua INSERT ke bulan itu akan gagal. Jika partition lama tidak pernah dihapus, storage terus bertambah tanpa batas. Lifecycle partition harus diotomasi sejak hari pertama.
-- Prosedur otomasi pembuatan partition bulanan (MySQL)
-- Simpan sebagai stored procedure, jalankan via event scheduler setiap awal bulan
DELIMITER $$
CREATE PROCEDURE create_monthly_partition(p_year INT, p_month INT)
BEGIN
DECLARE partition_name VARCHAR(20);
DECLARE next_month_start VARCHAR(20);
DECLARE sql_stmt TEXT;
SET partition_name = CONCAT('p', p_year, '_', LPAD(p_month, 2, '0'));
SET next_month_start = DATE_FORMAT(
DATE_ADD(CONCAT(p_year, '-', p_month, '-01'), INTERVAL 1 MONTH),
'%Y-%m-%d'
);
SET sql_stmt = CONCAT(
'ALTER TABLE orders REORGANIZE PARTITION p_future INTO (',
'PARTITION ', partition_name,
' VALUES LESS THAN (UNIX_TIMESTAMP(''', next_month_start, ''')),',
'PARTITION p_future VALUES LESS THAN MAXVALUE)'
);
PREPARE stmt FROM sql_stmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
-- Event scheduler: buat partition bulan depan di awal setiap bulan
CREATE EVENT auto_create_partition
ON SCHEDULE EVERY 1 MONTH
STARTS '2025-03-01 00:00:00'
DO CALL create_monthly_partition(YEAR(DATE_ADD(NOW(), INTERVAL 1 MONTH)),
MONTH(DATE_ADD(NOW(), INTERVAL 1 MONTH)));
-- Prosedur menghapus partition lama berdasarkan retensi (MySQL)
-- Hapus partition yang lebih tua dari 2 tahun
DELIMITER $$
CREATE PROCEDURE drop_old_partitions(retention_months INT)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE part_name VARCHAR(50);
DECLARE cutoff_date DATE;
SET cutoff_date = DATE_SUB(CURDATE(), INTERVAL retention_months MONTH);
-- Ambil daftar partition yang sudah melewati batas retensi
DECLARE partition_cursor CURSOR FOR
SELECT PARTITION_NAME
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'orders'
AND PARTITION_NAME != 'p_future'
AND STR_TO_DATE(CONCAT(
SUBSTRING(PARTITION_NAME, 2, 4), '-',
SUBSTRING(PARTITION_NAME, 7, 2), '-01'
), '%Y-%m-%d') < cutoff_date;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN partition_cursor;
read_loop: LOOP
FETCH partition_cursor INTO part_name;
IF done THEN LEAVE read_loop; END IF;
SET @sql = CONCAT('ALTER TABLE orders DROP PARTITION ', part_name);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE partition_cursor;
END$$
DELIMITER ;
Timeline automasi partition yang ideal:
T-1 bulan: Script membuat partition untuk bulan depan
T-0: Bulan baru dimulai, partition sudah siap menerima data
T+24 bulan: Script menghapus partition yang sudah melewati retensi 2 tahun
Alert yang harus dipasang:
→ Alert jika partition bulan depan belum ada 3 hari sebelum bulan berganti
→ Alert jika INSERT gagal karena partition key di luar range
→ Alert jika total jumlah partition melebihi threshold (contoh: > 100)
Hot Data vs Cold Data: Mengoptimalkan Akses #
Salah satu manfaat terbesar partitioning berbasis waktu adalah kemampuan memisahkan hot data dari cold data secara alami. Data terbaru — yang paling sering diakses — ada di partition terbaru yang kemungkinan besar masih berada di buffer pool (memory). Data lama ada di partition yang lebih tua, cenderung sudah keluar dari cache, dan hanya dibaca jika ada query historical.
Distribusi akses data di sistem tipikal:
orders_2025_02 [████████████████████] ← 70% query mengakses ini
orders_2025_01 [████████] ← 15% query
orders_2024_12 [████] ← 8% query
orders_2024_11 [██] ← 4% query
orders_2024_10 dan lebih tua [█] ← 3% query gabungan
Implikasi untuk konfigurasi:
→ InnoDB buffer pool fokus ke partition terbaru
→ Partition lama bisa dipindahkan ke storage yang lebih lambat tapi murah
→ Di cloud: partition baru di SSD, partition lama di HDD atau cold storage
Di PostgreSQL, ini bisa dikombinasikan dengan tablespace — menyimpan partition lama di storage yang berbeda (lebih murah, lebih lambat) sambil menjaga partition baru di storage cepat:
-- Pindahkan partition lama ke tablespace cold storage (PostgreSQL)
CREATE TABLESPACE cold_storage
LOCATION '/mnt/cold-storage/postgres';
-- Saat membuat partition lama, tentukan tablespace-nya
CREATE TABLE orders_2023_01
PARTITION OF orders
FOR VALUES FROM ('2023-01-01') TO ('2023-02-01')
TABLESPACE cold_storage;
-- Atau pindahkan partition yang sudah ada
ALTER TABLE orders_2023_01 SET TABLESPACE cold_storage;
Limitasi yang Perlu Dipahami #
Partitioning bukan tanpa batasan. Ada beberapa limitasi teknis yang harus dipahami sebelum mengimplementasikannya:
Limitasi partitioning di MySQL:
✗ Partition key harus bagian dari primary key dan semua unique index
→ Ini sering memaksa perubahan schema yang tidak sepele
→ Primary key (id) saja tidak cukup — harus (id, partition_key)
✗ Foreign key tidak didukung untuk tabel yang dipartisi
→ Jika ada foreign key ke tabel ini, foreign key harus dihapus dulu
→ Referential integrity harus dijaga di application layer
✗ Full-text index dan spatial index tidak didukung
✗ Jumlah partition per tabel maksimum 8192 (MySQL 8.0)
→ Untuk partisi harian selama 20+ tahun, ini bisa menjadi batasan
✗ Query yang tidak menyertakan partition key → semua partition dibaca
→ Ini sering lebih lambat dari tabel tidak terpartisi karena overhead metadata
Limitasi di PostgreSQL:
✗ Unique index dan primary key harus menyertakan partition key
→ Sama seperti MySQL
✗ Beberapa operasi DDL perlu dilakukan per partition, bukan di tabel induk
✓ PostgreSQL lebih fleksibel: mendukung foreign key ke tabel terpartisi (PostgreSQL 12+)
✓ Declarative partitioning di PostgreSQL lebih bersih dan lebih powerful
Anti-Pattern yang Harus Dihindari #
-- ✗ Anti-pattern 1: partitioning terlalu dini
-- Tabel dengan 100.000 baris — index biasa sudah lebih dari cukup
-- Menambah partitioning hanya menambah kompleksitas tanpa manfaat
-- ✓ Solusi: partitioning baru relevan di atas puluhan juta baris
────────────────────────────────────────────────────────────────────────────────
-- ✗ Anti-pattern 2: fungsi pada partition key di WHERE → pruning gagal
SELECT * FROM orders WHERE DATE(created_at) = '2025-02-15';
-- DATE() membungkus kolom → database tidak bisa pruning → semua partition dibaca
-- ✓ Solusi: query langsung ke kolom tanpa fungsi
SELECT * FROM orders
WHERE created_at >= '2025-02-15' AND created_at < '2025-02-16';
────────────────────────────────────────────────────────────────────────────────
-- ✗ Anti-pattern 3: query tanpa filter partition key sama sekali
SELECT * FROM orders WHERE user_id = 42;
-- Jika partition key adalah created_at, query ini scan semua partition
-- Mungkin lebih lambat dari tabel tidak terpartisi karena overhead metadata
-- ✓ Solusi: jika query sering tanpa filter partition key, tambahkan index biasa
-- atau pertimbangkan apakah partition key yang dipilih sudah tepat
────────────────────────────────────────────────────────────────────────────────
-- ✗ Anti-pattern 4: partition key pada kolom yang sering di-UPDATE
UPDATE orders SET status = 'completed', region = 'SG' -- region adalah partition key!
WHERE id = 42;
-- Jika region berubah, baris harus dipindahkan antar partition → operasi mahal
-- ✓ Solusi: partition key harus kolom yang nilainya tidak pernah berubah
-- created_at, inserted_at, user_id (immutable) → aman sebagai partition key
-- status, region, category → berbahaya jika bisa berubah
────────────────────────────────────────────────────────────────────────────────
-- ✗ Anti-pattern 5: terlalu banyak partition kecil
-- Partisi harian selama 10 tahun = 3650 partition
-- Query planner harus memeriksa metadata semua partition sebelum pruning
-- Overhead metadata bisa lebih mahal dari manfaat pruning itu sendiri
-- ✓ Solusi: granularitas yang tepat berdasarkan volume data
-- < 10 juta baris/bulan → partisi per bulan atau per kuartal
-- 10–100 juta baris/bulan → partisi per bulan
-- > 100 juta baris/bulan → partisi per minggu atau per hari
Checklist Review Partitioning #
KEPUTUSAN PARTITIONING:
□ Volume data sudah cukup besar untuk justifikasi partitioning (>50 juta baris)
□ Query pattern sudah dianalisis — ada filter konsisten yang bisa jadi partition key
□ Opsi lain (index, query optimization) sudah dipertimbangkan dan tidak cukup
□ Tim siap dengan kompleksitas operasional tambahan yang dibawa partitioning
DESAIN PARTITION:
□ Partition key dipilih berdasarkan query pattern yang paling sering, bukan asumsi
□ Partition key adalah kolom yang nilainya tidak pernah berubah (immutable)
□ Granularitas partition sesuai dengan volume data (per hari/minggu/bulan)
□ Jumlah total partition wajar (puluhan sampai ratusan, bukan ribuan)
□ Ada partition DEFAULT atau MAXVALUE untuk menampung data di luar range
IMPLEMENTASI:
□ Schema sudah disesuaikan: partition key masuk ke primary key dan unique index
□ Foreign key yang tidak kompatibel sudah ditangani
□ EXPLAIN dijalankan untuk memverifikasi partition pruning terjadi
□ Query tanpa filter partition key sudah diidentifikasi dan punya index alternatif
AUTOMASI DAN MAINTENANCE:
□ Script otomasi pembuatan partition baru sudah ada dan diuji
□ Partition bulan depan selalu dibuat sebelum bulan berganti (bukan saat bulan berganti)
□ Script penghapusan partition lama sudah ada sesuai retention policy
□ Alert dipasang jika INSERT gagal karena partition key di luar range
MONITORING:
□ Ukuran per partition dimonitor untuk mendeteksi distribusi tidak merata
□ Query yang scan banyak partition (pruning tidak efektif) dimonitor via slow query log
□ Total jumlah partition dimonitor — tidak tumbuh tanpa batas
Ringkasan #
- Partitioning bukan solusi awal — ia solusi skalabilitas — relevan ketika tabel sudah tumbuh ke puluhan juta baris dan index biasa tidak lagi cukup untuk menjaga performa. Jangan partisi tabel kecil.
- Manfaat utama partitioning berasal dari partition pruning — database hanya membaca partition yang relevan. Tanpa pruning, partitioning tidak memberikan manfaat performa apapun — bahkan bisa lebih lambat.
- Fungsi pada partition key di WHERE mencegah pruning —
DATE(created_at),YEAR(created_at), dan sejenisnya membuat database membaca semua partition. Tulis ulang query agar kolom tidak dibungkus fungsi.- Range partitioning berbasis waktu adalah pilihan paling aman untuk kebanyakan kasus — cocok untuk time-series, query selalu memfilter waktu, dan maintenance (hapus data lama) sangat efisien dengan DROP PARTITION.
- DROP PARTITION jauh lebih efisien dari DELETE — O(1), hampir tanpa overhead, tanpa fragmentasi. Ini adalah salah satu alasan terkuat untuk menggunakan partitioning pada data dengan retensi terbatas.
- Partition key harus immutable — UPDATE pada partition key menyebabkan baris berpindah partition, operasi yang mahal. Gunakan kolom yang nilainya tidak pernah berubah seperti
created_atatauuser_id.- Automasi lifecycle partition adalah wajib — partition yang dibuat manual rawan human error. Jika partition bulan depan lupa dibuat, semua INSERT ke bulan itu gagal.
- Hash partitioning untuk distribusi merata, range untuk pruning berbasis waktu — keduanya punya use case berbeda. Jika butuh keduanya, gunakan composite partitioning.
- Terlalu banyak partition kecil justru merugikan — metadata overhead bisa lebih mahal dari manfaat pruning. Granularitas yang tepat bergantung pada volume data per periode.
- Partitioning membutuhkan perubahan schema — di MySQL, partition key harus masuk ke primary key dan semua unique index. Ini sering menjadi kendala di tabel yang sudah ada foreign key.