Asal Mula Bloat
Karena MVCC, UPDATE tidak menimpa baris — ia menulis versi baru dan menandai yang lama sebagai mati. DELETE juga hanya menandai. Versi mati tetap memakan ruang sampai VACUUM membersihkannya.
UPDATE produk SET stok = 9 WHERE id = 1;
Sebelum: [v1: stok=10]
Sesudah: [v1: stok=10 MATI] [v2: stok=9 HIDUP]
↑ tetap memakan ruang
VACUUM biasa menandai ruang sebagai dapat dipakai ulang oleh tabel itu sendiri, tetapi berkasnya tidak mengecil. Ini justru yang diinginkan untuk operasi normal — ruang itu akan langsung terpakai oleh baris baru.
Untuk benar-benar mengembalikan ruang, dibutuhkan VACUUM FULL yang mengunci tabel sepenuhnya dan menulis ulang seluruh isinya. Pada tabel produksi, gunakan pg_repack yang melakukan hal serupa tanpa mengunci.
Mengukur Bloat
SELECT
relname,
n_live_tup,
n_dead_tup,
round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 1) AS persen_mati,
last_autovacuum,
pg_size_pretty(pg_total_relation_size(relid)) AS ukuran
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
Persentase baris mati di atas 20% pada tabel besar biasanya berarti autovacuum tidak mengejar laju perubahan.
Menyetel Autovacuum
-- Bawaan: vacuum ketika baris mati > 20% dari tabel autovacuum_vacuum_scale_factor = 0.2 autovacuum_vacuum_threshold = 50
Pada tabel berisi 100 juta baris, ambang 20% berarti autovacuum menunggu sampai ada 20 juta baris mati sebelum bertindak. Saat akhirnya berjalan, prosesnya berat dan berkepanjangan. Untuk tabel besar yang sering berubah, turunkan ambangnya per tabel:
ALTER TABLE pesanan SET (
autovacuum_vacuum_scale_factor = 0.01, -- 1%
autovacuum_vacuum_threshold = 1000,
autovacuum_analyze_scale_factor = 0.005,
autovacuum_vacuum_cost_delay = 2
);
Yang Menghalangi VACUUM
- Transaksi lama yang masih terbuka. VACUUM tidak boleh membuang versi baris yang secara teori masih bisa dibaca transaksi tersebut. Satu koneksi
idle in transactionmenghentikan pembersihan di seluruh basis data. - Replication slot yang tertinggal. Slot yang tidak aktif menahan WAL dan horizon vacuum. Slot yang terlupakan bisa memenuhi disk sekaligus menyebabkan bloat masif.
- Prepared transaction yang menggantung. Periksa
pg_prepared_xacts— jarang dipakai, tetapi bila ada yang tertinggal, dampaknya sama parahnya.
SELECT pid, state, now() - xact_start AS umur, left(query,60) FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start LIMIT 5; SELECT slot_name, active, restart_lsn FROM pg_replication_slots;
Transaction ID Wraparound
PostgreSQL memakai penghitung transaksi 32-bit. Setelah sekitar 2 miliar transaksi, penghitung berputar. VACUUM juga bertugas "membekukan" baris lama agar tetap terlihat setelah putaran itu.
SELECT datname,
age(datfrozenxid) AS umur_xid,
round(100.0 * age(datfrozenxid) / 2000000000, 1) AS persen
FROM pg_database
ORDER BY age(datfrozenxid) DESC;
Bila age(datfrozenxid) mendekati 2 miliar, PostgreSQL mulai memberi peringatan. Pada sekitar 2 miliar ia menolak semua penulisan untuk melindungi data, dan basis data hanya bisa dipulihkan lewat VACUUM dalam mode pengguna tunggal — proses yang bisa memakan waktu berjam-jam dengan sistem dalam keadaan mati total.
Beri alert pada 1 miliar (50%). Penyebabnya hampir selalu autovacuum yang terhambat oleh salah satu dari tiga penghambat di atas.
Perintah Perawatan
VACUUM (VERBOSE, ANALYZE) pesanan;
ANALYZE pesanan; -- hanya perbarui statistik
-- Mengembalikan ruang TANPA mengunci
pg_repack -d basisdata -t pesanan
-- Memantau vacuum yang sedang berjalan
SELECT pid, relid::regclass, phase,
heap_blks_scanned, heap_blks_total
FROM pg_stat_progress_vacuum;
- Hindari UPDATE pada kolom yang diindeks bila memungkinkan — pembaruan HOT hanya berlaku bila tidak ada kolom terindeks yang berubah, dan jauh lebih murah.
- Pakai partisi untuk data deret waktu. Menghapus data lama dengan
DROP PARTITIONbersifat instan dan tidak menghasilkan dead tuple sama sekali. - Pecah DELETE besar menjadi potongan kecil dengan jeda, agar autovacuum sempat mengejar.