Kodeloka
  1. Kodeloka
  2. Kelas PostgreSQL
  3. Materi

5 · Pemeliharaan · Modul 6 dari 12

VACUUM, Bloat & Autovacuum

Kenapa tabel PostgreSQL membengkak, bagaimana autovacuum bekerja, dan bahaya nyata transaction ID wraparound.

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 tidak mengembalikan ruang ke sistem operasi

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
🚫 Nilai bawaan buruk untuk tabel besar

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

🚫 Tiga penghambat utama
  1. Transaksi lama yang masih terbuka. VACUUM tidak boleh membuang versi baris yang secara teori masih bisa dibaca transaksi tersebut. Satu koneksi idle in transaction menghentikan pembersihan di seluruh basis data.
  2. Replication slot yang tertinggal. Slot yang tidak aktif menahan WAL dan horizon vacuum. Slot yang terlupakan bisa memenuhi disk sekaligus menyebabkan bloat masif.
  3. 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;
🚫 Wraparound adalah salah satu kegagalan terburuk PostgreSQL

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;
📌 Kurangi bloat lewat perancangan
  • 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 PARTITION bersifat instan dan tidak menghasilkan dead tuple sama sekali.
  • Pecah DELETE besar menjadi potongan kecil dengan jeda, agar autovacuum sempat mengejar.