Disk Doldu: MySQL Veri Dizini Nasıl Temizlenir?
Gece 2’de telefonun çalıyor. Müşteri panik halinde: “Site çalışmıyor, veritabanına bağlanamıyoruz!” Sunucuya bağlanıyorsun, df -h çıktısına bakıyorsun ve görüyorsun: /var/lib/mysql dizininin bulunduğu partition %100 dolu. MySQL log dosyaları şişmiş, binary loglar birikmiş, eski tablo dosyaları yerinde duruyor. Bu senaryo her sysadmin’in kâbusu. Bu yazıda hem bu krizi nasıl çözeceğini hem de bir daha yaşamamak için neler yapman gerektiğini adım adım anlatacağım.
Durumu Tanımak: Önce Nefes Al, Sonra Analiz Et
Panikle bir şeyler silmeye başlamak en büyük hata. MySQL veri dizininde yanlış dosyayı silersen veriyi kaybedebilirsin, bu da disk dolmasından çok daha büyük bir felaket olur.
İlk adım olarak diskin gerçekten dolup dolmadığını ve hangi dizinin sorun çıkardığını teyit et:
df -h
df -h /var/lib/mysql
du -sh /var/lib/mysql/*
du -sh /var/lib/mysql/* | sort -rh | head -20
Bu komutlar sana hangi veritabanının veya hangi dosya türünün en çok yer kapladığını gösterir. sort -rh ile en büyükten küçüğe sıralıyoruz, head -20 ile de ilk 20 sonucu görüyoruz.
MySQL’in o an çalışıp çalışmadığını kontrol et:
systemctl status mysql
# veya MariaDB kullanıyorsan:
systemctl status mariadb
# MySQL process'inin çalışıp çalışmadığına da bak
ps aux | grep mysql
Eğer MySQL çalışmıyorsa zaten disk dolduğu için çökmüş olabilir. Ama temizlik yaparken servisi durdurmak gerekmeyebilir, hangisini sileceğine göre karar vereceksin.
MySQL Veri Dizininde Ne Var?
/var/lib/mysql dizininin içini anlamadan temizlik yapmamalısın. Temel dosya türlerini şöyle özetleyeyim:
ibdata1: InnoDB shared tablespace dosyası. Bu dosyayı silme, veritabanın gider.ib_logfile0,ib_logfile1: InnoDB redo log dosyaları. Çalışan sunucuda bunlara dokunma.mysql-bin.000001,mysql-bin.000002vb.: Binary log dosyaları. Replikasyon kullanmıyorsan bunlar büyük yer kaplayabilir.mysql-bin.index: Binary log index dosyası..frm,.ibd: Tablo tanımlama ve InnoDB tablo dosyaları.slow.log,error.log,general.log: Log dosyaları. Bunlar ciddi boyutlara ulaşabilir.mysql-relay-bin.*: Slave sunucularda relay log dosyaları.#innodb_temp/: InnoDB geçici dosyaları.
Hangi dosyaların ne kadar yer kapladığını daha detaylı görmek için:
find /var/lib/mysql -name "*.log" -exec ls -lh {} ;
find /var/lib/mysql -name "mysql-bin.*" -exec ls -lh {} ;
find /var/lib/mysql -name "*.ibd" -exec ls -lh {} ; | sort -k5 -rh | head -10
En Kolay Kurtarma: Binary Log Temizliği
Binary loglar replikasyon ve point-in-time recovery için kullanılır. Eğer master-slave replikasyon kullanmıyorsan ve full backup stratejin binary log’a bağlı değilse, bu dosyaları güvenle temizleyebilirsin. Deneyimlerime göre binary loglar bazen 20-30 GB’a kadar şişiyor.
Ama dikkat: Silmeden önce replikasyon kullanıp kullanmadığını teyit et.
# MySQL'e bağlan
mysql -u root -p
# Replikasyon durumunu kontrol et
SHOW SLAVE STATUSG
SHOW MASTER STATUSG
# Hangi binary loglar var?
SHOW BINARY LOGS;
Replikasyon yoksa ve binary logları temizleyeceksen, bunu asla manuel olarak silme. MySQL’in kendi komutlarını kullan:
mysql -u root -p -e "PURGE BINARY LOGS BEFORE DATE(NOW() - INTERVAL 3 DAY);"
# veya belirli bir log dosyasına kadar sil:
mysql -u root -p -e "PURGE BINARY LOGS TO 'mysql-bin.000150';"
Bu komut hem dosyaları siler hem de index dosyasını günceller. Manuel silersen index bozulur ve MySQL hata verir.
Binary logları kalıcı olarak devre dışı bırakmak istersen /etc/mysql/mysql.conf.d/mysqld.cnf veya /etc/my.cnf dosyasına şunu ekle:
# my.cnf'ye ekle
[mysqld]
skip-log-bin
# veya eski sürümlerde:
# disable_log_bin
Sonra MySQL’i restart et. Ancak bunu yapmadan önce backup stratejini gözden geçir.
MySQL Log Dosyalarını Temizlemek
Error log, slow query log ve general query log dosyaları da devasa boyutlara ulaşabilir. Özellikle general_log açıksa bütün sorguları yazdığı için saatler içinde gigabytea ulaşabilir.
Log dosyasının nerede olduğunu bul:
mysql -u root -p -e "SHOW VARIABLES LIKE '%log%';"
mysql -u root -p -e "SHOW VARIABLES LIKE 'general_log_file';"
mysql -u root -p -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
mysql -u root -p -e "SHOW VARIABLES LIKE 'log_error';"
Log dosyasını truncate ile temizle. Sakın rm ile silme, çünkü MySQL dosyayı açık tutar ve sildikten sonra disk alanı serbest bırakılmaz (open file handle sorunu):
# Dosyayı sıfırla ama sil
truncate -s 0 /var/lib/mysql/hostname.err
truncate -s 0 /var/lib/mysql/slow.log
# Veya MySQL'den:
mysql -u root -p -e "FLUSH ERROR LOGS;"
mysql -u root -p -e "FLUSH SLOW LOGS;"
mysql -u root -p -e "FLUSH GENERAL LOGS;"
FLUSH LOGS komutu log dosyasını kapatıp yeniden açar, bu yüzden log rotation için de kullanılır.
General log sürekli açık kalmamalı. Kapatmak için:
mysql -u root -p -e "SET GLOBAL general_log = 'OFF';"
Ve my.cnf‘den de kaldır:
# Bu satırı kaldır veya yorum satırına al
# general_log = 1
# general_log_file = /var/lib/mysql/general.log
InnoDB ibdata1 Sorunu: En Sinir Bozucu Durum
ibdata1 dosyasının 50 GB, 100 GB hatta daha büyük olduğunu görüyorsun. Bu MySQL’in en bilinen sorunlarından biri. Bu dosyayı silemezsin, içinde tablo verileri, undo loglar ve sistem tabloları var.
Peki ne yapabilirsin? Önce neden şiştiğini anla:
mysql -u root -p -e "SELECT table_schema, table_name,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb
FROM information_schema.TABLES
ORDER BY size_mb DESC
LIMIT 20;"
Eğer MySQL 5.6+ kullanıyorsan ve innodb_file_per_table açıksa her tablo kendi .ibd dosyasına yazar. Bu ayarın açık olup olmadığını kontrol et:
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_file_per_table';"
Eğer OFF ise her şey ibdata1‘e yazılıyor demektir. Bu ayarı açmak uzun vadeli çözümdür ama mevcut ibdata1‘i küçültmez.
ibdata1‘i küçültmenin tek yolu tam dump + temizlik + restore işlemidir. Bu ciddi bir işlem, bunu planlı maintenance window’da yapmak gerekir:
# 1. Tam backup al
mysqldump -u root -p --all-databases --routines --triggers --events > /backup/full_backup_$(date +%Y%m%d).sql
# 2. MySQL'i durdur
systemctl stop mysql
# 3. my.cnf'ye innodb_file_per_table ekle
echo "innodb_file_per_table=1" >> /etc/mysql/mysql.conf.d/mysqld.cnf
# 4. Veri dizinini tamamen temizle (BU GERİ DÖNDÜRÜLEMEZ!)
# Bu adımı yapmadan önce backup'ın tam olduğundan emin ol!
mv /var/lib/mysql /var/lib/mysql_old
# 5. MySQL'i başlat (yeni boş dizin oluşturur)
systemctl start mysql
# 6. Backup'ı restore et
mysql -u root -p < /backup/full_backup_20240115.sql
Bu işlemden sonra ibdata1 çok daha küçük olur ve yeni tablolar artık kendi .ibd dosyalarına yazar.
Eski ve Kullanılmayan Tablo Dosyalarını Bulmak
Bazen bir veritabanını silersin ama orphan .ibd dosyaları kalır. Ya da birisi manuel olarak dosya bırakmıştır. Bunları bulmak için:
# Hangi .ibd dosyaları var?
find /var/lib/mysql -name "*.ibd" -size +100M
# MySQL'in bilmediği orphan .ibd dosyalarını bul
# Önce mevcut tabloları listele
mysql -u root -p -e "SELECT CONCAT(table_schema, '/', table_name, '.ibd')
FROM information_schema.TABLES
WHERE engine='InnoDB';" > /tmp/known_ibd_files.txt
# Disk üzerindeki .ibd dosyalarıyla karşılaştır
find /var/lib/mysql -name "*.ibd" | sed 's|/var/lib/mysql/||' > /tmp/disk_ibd_files.txt
diff /tmp/known_ibd_files.txt /tmp/disk_ibd_files.txt
Emin olmadığın bir .ibd dosyasını silmeden önce mutlaka adını araştır. MySQL’in bilmediği ama silmek istemediğin verileri içeriyor olabilir.
Büyük Tabloları Optimize Etmek
Silinen kayıtlar MySQL’de fiziksel olarak hemen silinmez, işaretlenir. Bu yüzden çok sayıda DELETE yapılan tablolar şişer. OPTIMIZE TABLE ile bu alanı geri kazanabilirsin:
# Önce hangi tablolar çok parçalanmış?
mysql -u root -p -e "SELECT table_schema, table_name,
ROUND(data_free / 1024 / 1024, 2) AS free_space_mb,
ROUND(data_length / 1024 / 1024, 2) AS data_mb
FROM information_schema.TABLES
WHERE data_free > 100 * 1024 * 1024
ORDER BY data_free DESC;"
# Belirli bir tabloyu optimize et
mysql -u root -p -e "OPTIMIZE TABLE mydb.big_table;"
# Bütün bir veritabanını optimize et (dikkatli ol, lock alır)
mysqlcheck -u root -p --optimize --all-databases
Uyarı: OPTIMIZE TABLE büyük tablolarda çok uzun sürebilir ve production ortamında tablo lock’una neden olabilir. Bu işlemi düşük trafikli saatlerde yap veya pt-online-schema-change gibi araçları kullan.
Temporary Dosyaları ve InnoDB Temp Tablespace
MySQL’in geçici dosyaları da yer kaplayabilir. Özellikle büyük sorgular geçici tablolar oluşturur:
# Geçici dosyaların nerede olduğunu bul
mysql -u root -p -e "SHOW VARIABLES LIKE 'tmpdir';"
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_temp_data_file_path';"
# ibtmp1 dosyasını kontrol et
ls -lh /var/lib/mysql/ibtmp1
# Geçici dizini kontrol et
du -sh /tmp/mysql* 2>/dev/null
ls -la $(mysql -u root -p -se "SELECT @@tmpdir;")/
ibtmp1 geçici tablespace’i MySQL restart olduğunda sıfırlanır. Eğer bu dosya devasa büyüdüyse ve hemen yer kazanmak istiyorsan MySQL’i restart etmek işe yarar. Ama önce aktif sorguları kontrol et:
mysql -u root -p -e "SHOW PROCESSLIST;"
mysql -u root -p -e "SHOW FULL PROCESSLIST;"
Uzun süredir çalışan bir sorgu varsa ve ibtmp1‘i o şişiriyorsa, o sorguyu KILL etmeyi düşünebilirsin:
mysql -u root -p -e "KILL QUERY 1234;"
Acil Durum: Disk Hala Dolu, MySQL Başlamıyor
Yeterli yer açamadın ve MySQL hala başlamıyor. Bu durumda en hızlı çözüm geçici olarak başka bir partition’dan alan ödünç almak:
# Başka bir yerde boş alan var mı?
df -h
# Sembolik link trick'i (dikkatli kullan)
# Örneğin /home altında yer varsa
mkdir -p /home/mysql_overflow
mv /var/lib/mysql/mysql-bin.* /home/mysql_overflow/
# mysql-bin.index'i de güncelle veya sil
echo "" > /var/lib/mysql/mysql-bin.index
# Ya da mount --bind kullan
mkdir /mnt/mysql_extra
mount --bind /mnt/mysql_extra /var/lib/mysql/some_big_database
Daha temiz bir acil çözüm olarak, filesystem üzerinde yer kaplayan en büyük dosyaları bul ve kritik olmayanları başka yere taşı:
# En büyük dosyaları bul
find /var/lib/mysql -type f -exec du -sh {} ; | sort -rh | head -20
# Log dosyalarını acilen temizle
for f in $(find /var/lib/mysql -name "*.log" -size +1G); do
truncate -s 0 "$f"
echo "Temizlendi: $f"
done
Otomasyon: Bir Daha Olmasın
Krizi atlattın ama önlem almasan bir sonraki sefer yine aynı noktaya gelirsin. Birkaç basit önlem bu işi tamamen ortadan kaldırır.
Binary log rotation için my.cnf‘ye ekle:
[mysqld]
# Binary logları 7 günde sil
expire_logs_days = 7
# MySQL 8.0+ için:
# binlog_expire_logs_seconds = 604800
Disk kullanım izleme scripti yaz ve cron’a ekle:
#!/bin/bash
# /usr/local/bin/check_mysql_disk.sh
THRESHOLD=80
MYSQL_DIR="/var/lib/mysql"
EMAIL="[email protected]"
USAGE=$(df -h "$MYSQL_DIR" | awk 'NR==2{print $5}' | tr -d '%')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
DETAILS=$(du -sh /var/lib/mysql/* | sort -rh | head -10)
echo -e "MySQL disk kullanimi: %${USAGE}nnEn buyuk dosyalar:n${DETAILS}" |
mail -s "UYARI: MySQL Disk Dolmak Uzere - $(hostname)" "$EMAIL"
# Binary logları otomatik temizle
mysql -u root -p"SIFRENIZ" -e "PURGE BINARY LOGS BEFORE DATE(NOW() - INTERVAL 2 DAY);" 2>/dev/null
fi
Cron’a ekle:
# Her saat başı kontrol et
0 * * * * /usr/local/bin/check_mysql_disk.sh
Log rotation için /etc/logrotate.d/mysql dosyasını kontrol et veya oluştur:
/var/lib/mysql/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
/usr/bin/mysqladmin -u root -p'SIFRENIZ' flush-logs
endscript
}
Uzun Vadeli Çözüm: Monitoring ve Kapasite Planlaması
Disk dolması reactive bir problem, onu proaktif hale getirmen gerekiyor. Prometheus + Grafana kullanıyorsan mysql_exporter ile MySQL metriklerini izleyebilirsin. Daha basit bir çözüm istiyorsan Zabbix veya Nagios’un MySQL templatelarını kullanabilirsin.
En azından şu metrikleri izliyor olmalısın:
- Disk kullanım yüzdesi (partition bazında)
- Binary log boyutu ve sayısı
ibdata1büyümesi- Slow query sayısı (log şişmesinin göstergesi)
- Geçici tablo sayısı
Eğer monitoring kurma fırsatın yoksa bile basit bir cron script ile haftalık raporlama yapabilirsin:
#!/bin/bash
# Haftalık MySQL disk raporu
echo "=== MySQL Disk Kullanim Raporu - $(date) ==="
echo ""
echo "Genel disk kullanimi:"
df -h /var/lib/mysql
echo ""
echo "En buyuk dosyalar:"
du -sh /var/lib/mysql/* | sort -rh | head -10
echo ""
echo "Binary log durumu:"
mysql -u root -p'SIFRE' -e "SHOW BINARY LOGS;" 2>/dev/null
echo ""
echo "InnoDB status:"
mysql -u root -p'SIFRE' -e "SELECT * FROM information_schema.INNODB_METRICS WHERE name LIKE '%disk%';" 2>/dev/null
Sonuç
Disk dolması en yaygın ve en can sıkıcı MySQL sorunlarından biri. Ancak doğru adımları bilirsen hem krizi hızla çözebilir hem de bir daha yaşamamak için gerekli önlemleri alabilirsin.
Özetle hatırlamanı istediğim birkaç kritik nokta var. Binary logları asla elle silme, PURGE BINARY LOGS komutunu kullan. ibdata1‘e dokunmadan önce tam backup al ve ne yaptığını iyi anla. Log dosyalarını rm ile değil truncate ile sıfırla. innodb_file_per_table=1 ayarını mutlaka açık tut, bu uzun vadede hayatını kurtarır. Ve en önemlisi disk izlemesini ihmal etme, %80’i geçince uyarı alacak bir mekanizma kur.
Bu adımları uygularsan gece 2’deki panik telefonu geçmişte kalır. En azından bu nedenden değil.
