du, yang paling gemuk ternyata folder data MySQL, dan isinya bukan tabel, melainkan deretan file mysql-bin.000xxx yang jumlahnya ratusan. Itu binary log, dan di banyak server ia dibiarkan tumbuh tanpa batas karena tidak ada yang pernah mengatur retensinya.
Artikel ini fokus ke langkah yang bisa langsung dijalankan: cek seberapa besar binlog sekarang, cek kenapa tidak terhapus otomatis, bersihkan dengan aman, lalu verifikasi bahwa pembersihan otomatis benar-benar jalan.
Apa itu binary log dan kenapa bisa menumpuk
INSERT, UPDATE, DELETE, dan perubahan struktur tabel. Fungsinya ada dua. Pertama untuk replikasi, karena server replica membaca binlog dari server utama (source) lalu memutar ulang perubahannya. Kedua untuk point-in-time recovery, yaitu mengembalikan database ke kondisi menit tertentu setelah restore backup penuh.
Masalahnya, binlog hanya bertambah. MySQL menulis ke satu file sampai ukurannya mencapai max_binlog_size (default biasanya 1 GB), lalu membuka file baru. File lama tidak dihapus kecuali ada aturan retensi. Di MySQL 5.7 dan MariaDB, default expire_logs_days biasanya 0, artinya tidak pernah dihapus otomatis. MySQL 8.0 ke atas sudah punya default retensi 30 hari lewat binlog_expire_logs_seconds, tapi 30 hari di server dengan situs WooCommerce yang ramai, atau plugin yang menulis session dan statistik ke database terus-menerus, bisa berarti puluhan GB.
Yang sering membingungkan pemilik VPS: binlog kadang aktif tanpa mereka sadari, misalnya karena template OS, control panel, atau konfigurasi bawaan image. Jadi jangan berasumsi. Cek dulu.
Cek apakah binlog aktif dan seberapa besar
BASH
# Sisa ruang disk per partisi
df -h
# Binlog aktif? Di mana lokasinya? Berapa ukuran maksimal per file?
mysql -e 'SELECT @@log_bin, @@log_bin_basename, @@max_binlog_size;'
# Setting retensi (salah satu bisa error kalau variabelnya tidak ada di versi kamu)
mysql -e 'SELECT @@binlog_expire_logs_seconds;'
mysql -e 'SELECT @@expire_logs_days;'
# Daftar file binlog yang dikenali server, dengan ukuran dalam byte
mysql -e 'SHOW BINARY LOGS;'
# Total ukuran di disk (sesuaikan path dengan hasil log_bin_basename)
du -ch /var/lib/mysql/mysql-bin.[0-9]* | tail -n 1@@log_bin bernilai 0, binlog mati dan masalah disk kamu ada di tempat lain. Kalau 1, lihat @@log_bin_basename untuk tahu prefix dan lokasi file. Nama file bisa mysql-bin, binlog, atau nama host server, tergantung konfigurasi, jadi path di perintah du harus disesuaikan.
SHOW BINARY LOGS menampilkan kolom Log_name dan File_size, plus kolom Encrypted di MySQL 8. Kalau daftarnya panjang sekali dan file paling awal bertanggal jauh ke belakang, itu tanda retensi tidak jalan.
Untuk variabel retensi: binlog_expire_logs_seconds bernilai 0 atau expire_logs_days bernilai 0 berarti tidak ada penghapusan otomatis. Di MySQL 8.4, expire_logs_days sudah tidak ada, jadi query kedua akan error, dan itu normal.
Kenapa retensi sudah di-set tapi file lama masih ada
max_binlog_size tercapai atau ada FLUSH BINARY LOGS).
Akibatnya, kalau kamu baru mengubah retensi lewat SET GLOBAL, file lama tidak langsung hilang. Di server yang trafik tulisnya kecil, rotasi bisa jarang terjadi, jadi file lama bertahan lebih lama dari yang kamu kira.
Penyebab lain yang perlu dicurigai:
- Setting hanya ditulis di file konfigurasi yang tidak dibaca. Cek dengan
mysqld --verbose --help | grep -A 1 'Default options'untuk melihat urutan filemy.cnfyang dibaca, lalu pastikan nilainya muncul diSELECT @@...setelah restart. - Dua variabel di-set bersamaan dengan nilai berbeda. Cara MySQL dan MariaDB memprioritaskannya berbeda per versi, dan biasanya ada peringatan di error log. Lebih aman pakai satu saja.
- File dihapus manual pakai
rm. Ini membuat index binlog (file.index) tidak sinkron dengan isi disk, dan file yang masih dibuka olehmysqldtetap memakan ruang sampai proses ditutup. Polanya mirip yang dibahas di Kenapa Disk Tetap Penuh Padahal File Log Sudah Dihapus.
rm. Pakai PURGE BINARY LOGS, karena perintah itu menghapus file sekaligus memperbarui index.
Cek replica dan backup dulu, baru purge
PURGE BINARY LOGS menghapus file secara permanen dan tidak bisa dibatalkan. Kalau sebuah replica masih membutuhkan file yang sudah di-purge, replikasinya akan berhenti dengan error, dan biasanya replica itu harus dibangun ulang dari backup. Kalau tim kamu mengandalkan binlog untuk restore ke titik waktu tertentu, file yang di-purge juga tidak bisa lagi dipakai untuk itu.
BASH
# Di server source: ada replica yang sedang menarik binlog?
mysql -e 'SHOW PROCESSLIST;' | grep -i 'binlog dump'
# Di SETIAP replica: file binlog source yang sedang dibaca dan dieksekusi
mysql -e 'SHOW REPLICA STATUS\G' | grep -E 'Source_Log_File|Relay_Source_Log_File'
# Versi lama atau MariaDB: SHOW SLAVE STATUS\G, kolom Master_Log_File dan Relay_Master_Log_File
# Hapus semua binlog SEBELUM file yang disebut (file itu sendiri tetap ada)
mysql -e "PURGE BINARY LOGS TO 'mysql-bin.000120';"
# Atau berdasarkan waktu
mysql -e 'PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;'- Kalau baris
Binlog Dumptidak muncul di processlist dan kamu tahu server ini memang tidak punya replica, langkah replikasi bisa dilewati. - Kalau ada replica, catat file paling lama yang masih dipakai di antara semua replica. Ambil nilai
Relay_Source_Log_File(atauRelay_Master_Log_File) yang paling kecil. - Cek jadwal backup penuh terakhir. Binlog yang lebih tua dari backup penuh terakhir yang valid umumnya sudah tidak dibutuhkan untuk recovery.
- Purge pakai
TOdengan nama file yang masih dibutuhkan replica paling lambat, atau pakaiBEFOREdengan rentang waktu yang lebih pendek dari jarak antar backup.
Pasang retensi permanen dan verifikasi ulang
[mysqld] pada file konfigurasi yang benar-benar dibaca server:
INI
[mysqld]
# MySQL 8.0+ dan MariaDB 10.6+: retensi dalam detik (contoh: 7 hari)
binlog_expire_logs_seconds = 604800
# MySQL 5.7 atau MariaDB lama: pakai satuan hari. Pilih salah satu, jangan isi keduanya.
# expire_logs_days = 7SET GLOBAL binlog_expire_logs_seconds = 604800;. Di MySQL 8 ada SET PERSIST, yang menulis nilai ke mysqld-auto.cnf sehingga bertahan setelah restart. MariaDB tidak punya SET PERSIST, jadi file konfigurasi tetap wajib diubah.
Lalu picu evaluasi retensi dengan FLUSH BINARY LOGS;. Perintah ini menutup file binlog aktif dan membuka file baru, dan pada saat itulah file yang sudah melewati batas retensi dihapus.
Verifikasinya pakai perintah yang sama dengan langkah pertama. Jalankan lagi SELECT @@binlog_expire_logs_seconds; untuk memastikan nilainya terbaca, SHOW BINARY LOGS; untuk melihat file paling awal sekarang tidak lebih tua dari batas retensi, dan df -h untuk memastikan ruang disk memang bertambah. Kalau df belum berubah padahal daftar binlog sudah pendek, cek apakah ada file yang dihapus manual tapi masih dibuka proses, misalnya dengan lsof +L1.
Kenapa ini layak dicek rutin, bukan cuma saat darurat
binlog_error_action adalah ABORT_SERVER, artinya mysqld berhenti. Dari sisi pengunjung, gejalanya sama: situs menampilkan error koneksi database.
Di meja support, keluhan seperti ini sering datang sebagai "database tiba-tiba mati" atau "WordPress error establishing a database connection". Yang pertama kali perlu dicek adalah df -h dan isi datadir, karena binlog yang menumpuk termasuk penyebab yang mudah terlewat. Orang biasanya mencari file log web server atau backup lama, bukan file di dalam folder MySQL.
Cek rutinnya ringan. Masukkan SHOW BINARY LOGS dan df -h ke checklist mingguan, atau pasang alert penggunaan disk di monitoring yang sudah kamu pakai. Begitu retensi terpasang dan terverifikasi lewat FLUSH BINARY LOGS, binlog akan berputar sendiri. Tugas kamu tinggal memastikan angka retensinya tetap masuk akal setiap kali ada replica baru atau jadwal backup berubah.