Veritabanı yedekleme konusuna gelince, çoğu sysadmin “bir şeyler yapıyoruz” modunda çalışır. Haftada bir tam yedek alınıyor, bir yerlere kopyalanıyor, tamam. Ama production ortamında gerçek bir felaket anında o yedeğin işe yarayıp yaramayacağını kaç kişi test etti? İşte bu yazıda MySQL ve MariaDB için iki temel yedekleme yaklaşımını ele alacağız: klasik mysqldump ve Percona’nın güçlü silahı XtraBackup. Her ikisinin de ne zaman kullanılacağını, nasıl yapılandırılacağını ve gerçek ortamlarda nasıl uygulandığını adım adım geçeceğiz.
mysqldump: Basit Ama Güçlü
mysqldump MySQL ile birlikte gelen, SQL tabanlı mantıksal yedekleme aracıdır. Küçük ve orta ölçekli veritabanları için hala birincil tercih olmaya devam ediyor. Çıktısı düz SQL olduğu için taşınabilirliği mükemmel, farklı MySQL versiyonları arasında, hatta PostgreSQL’e bile veri taşıyabilirsiniz.
Temel Kullanım
En basit haliyle tek bir veritabanını yedeklemek:
mysqldump -u root -p mydb > /backup/mydb_$(date +%Y%m%d_%H%M%S).sql
Tüm veritabanlarını tek seferde yedeklemek için:
mysqldump -u root -p
--all-databases
--single-transaction
--routines
--triggers
--events
> /backup/full_backup_$(date +%Y%m%d).sql
Bu komuttaki parametreleri açıklayalım:
- –all-databases: Sistemdeki tüm veritabanlarını dahil eder
- –single-transaction: InnoDB tablolar için dump başlamadan önce consistent snapshot alır, tabloları kilitlemez
- –routines: Stored procedure ve fonksiyonları dahil eder
- –triggers: Trigger tanımlarını dahil eder
- –events: Event Scheduler tanımlarını dahil eder
Sıkıştırma ile Disk Tasarrufu
SQL dump dosyaları çok yer kaplar ama sıkıştırmaya da çok iyi gelir. Pipe kullanarak anında sıkıştırabilirsiniz:
mysqldump -u root -p
--single-transaction
--quick
--lock-tables=false
mydb | gzip -9 > /backup/mydb_$(date +%Y%m%d_%H%M%S).sql.gz
–quick parametresi büyük tablolarda çok önemli. Normalde mysqldump tüm tabloyu önce belleğe alır, --quick ile satır satır okur. 10GB’lık bir tabloda bu farkı çok net hissedersiniz.
Geri Yükleme
# Sıkıştırılmamış
mysql -u root -p mydb < /backup/mydb_20240115.sql
# Sıkıştırılmış
gunzip -c /backup/mydb_20240115.sql.gz | mysql -u root -p mydb
# Progress görmek istiyorsanız pv ile
gunzip -c /backup/mydb_20240115.sql.gz | pv | mysql -u root -p mydb
mysqldump’ın Sınırları
mysqldump ile production ortamında karşılaştığım en büyük sorun şu: 50GB üzerindeki veritabanlarında yedek alma süresi uzadıkça uzuyor ve bu süre zarfında sistem üzerindeki yük gözle görülür şekilde artıyor. Ayrıca geri yükleme süresi de en az yedek alma süresi kadar, bazen daha fazla. 100GB bir veritabanını mysqldump ile restore etmeye çalıştığınızda sabahı getirebilirsiniz.
Percona XtraBackup: Production’ın Gerçek Dostu
XtraBackup, InnoDB ve XtraDB için tasarlanmış fiziksel hot backup aracıdır. En büyük avantajı: tabloları kilitlemeden, servis kesintisi olmadan yedek alabilirsiniz. Büyük veritabanlarında mysqldump’ın yetersiz kaldığı her senaryoda XtraBackup devreye girer.
Kurulum
Ubuntu/Debian üzerinde:
# Percona repository ekle
wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb
dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb
apt-get update
# MySQL 8.0 için
percona-release enable-only tools release
apt-get install percona-xtrabackup-80
# Versiyon kontrolü
xtrabackup --version
RHEL/CentOS/Rocky Linux üzerinde:
yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
percona-release enable-only tools release
yum install percona-xtrabackup-80
Tam Yedek Alma (Full Backup)
xtrabackup --backup
--user=xtrabackup_user
--password=SecurePass123
--target-dir=/backup/full
--datadir=/var/lib/mysql
--parallel=4
--compress
--compress-threads=4
Burada dikkat edilmesi gereken birkaç nokta var. XtraBackup için ayrı bir MySQL kullanıcısı oluşturmanızı tavsiye ederim:
mysql -u root -p <<EOF
CREATE USER 'xtrabackup_user'@'localhost' IDENTIFIED BY 'SecurePass123';
GRANT BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'xtrabackup_user'@'localhost';
GRANT SELECT ON performance_schema.log_status TO 'xtrabackup_user'@'localhost';
FLUSH PRIVILEGES;
EOF
Incremental Backup: Disk Alanını Verimli Kullanmak
Gerçek production senaryolarında incremental backup olmadan büyük veritabanları yönetilemez hale gelir. Örneğin 200GB’lık bir veritabanı var, her gece full backup almak hem zaman hem disk açısından çılgınlık olur.
Strateji şu şekilde olabilir: Pazar günü full backup, pazartesiden cumartesiye incremental backup.
# Pazar: Full backup
xtrabackup --backup
--user=xtrabackup_user
--password=SecurePass123
--target-dir=/backup/full_sunday
--parallel=4
# Pazartesi: İlk incremental
xtrabackup --backup
--user=xtrabackup_user
--password=SecurePass123
--target-dir=/backup/inc_monday
--incremental-basedir=/backup/full_sunday
--parallel=4
# Salı: Önceki incrementale dayalı incremental
xtrabackup --backup
--user=xtrabackup_user
--password=SecurePass123
--target-dir=/backup/inc_tuesday
--incremental-basedir=/backup/inc_monday
--parallel=4
Geri Yükleme Süreci
XtraBackup ile restore işlemi birkaç aşamadan oluşur. Bu adımları atlamayın, aksi halde tutarsız bir veritabanıyla karşılaşırsınız.
# 1. Adım: Full backup'ı prepare et
xtrabackup --prepare
--apply-log-only
--target-dir=/backup/full_sunday
# 2. Adım: Her incremental'ı sırayla uygula (son hariç)
xtrabackup --prepare
--apply-log-only
--target-dir=/backup/full_sunday
--incremental-dir=/backup/inc_monday
xtrabackup --prepare
--apply-log-only
--target-dir=/backup/full_sunday
--incremental-dir=/backup/inc_tuesday
# 3. Adım: Son incremental'ı --apply-log-only olmadan uygula
xtrabackup --prepare
--target-dir=/backup/full_sunday
--incremental-dir=/backup/inc_wednesday
# 4. Adım: MySQL'i durdur ve data dizinini temizle
systemctl stop mysql
mv /var/lib/mysql /var/lib/mysql_old
# 5. Adım: Restore et
xtrabackup --copy-back
--target-dir=/backup/full_sunday
--datadir=/var/lib/mysql
# 6. Adım: İzinleri düzelt ve servisi başlat
chown -R mysql:mysql /var/lib/mysql
systemctl start mysql
--apply-log-only parametresini son incremental dışında kullanmak zorundayız. Bu parametre redo log’ları uygular ama rollback yapmaz, böylece bir sonraki incremental’ın üstüne eklenebilir halde kalır.
Otomasyon: Gerçek Ortam Script’i
İşte production’da kullandığım, biraz sadeleştirilmiş bir yedekleme script’i:
#!/bin/bash
# mysql_backup.sh - Production MySQL Backup Script
set -euo pipefail
# Degiskenler
BACKUP_USER="xtrabackup_user"
BACKUP_PASS="SecurePass123"
BACKUP_BASE="/backup/mysql"
RETENTION_DAYS=7
LOG_FILE="/var/log/mysql_backup.log"
DATE=$(date +%Y%m%d_%H%M%S)
DAY_OF_WEEK=$(date +%u) # 1=Pazartesi, 7=Pazar
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
send_alert() {
# Kendi alert mekanizmanizi buraya ekleyin
# Ornegin: curl ile Slack webhook, mail komutu vs.
echo "ALERT: $1" >> "$LOG_FILE"
}
# Pazar gunu full backup, diger gunler incremental
if [ "$DAY_OF_WEEK" -eq 7 ]; then
BACKUP_TYPE="full"
BACKUP_DIR="$BACKUP_BASE/full_$DATE"
log "Full backup basliyor: $BACKUP_DIR"
xtrabackup --backup
--user="$BACKUP_USER"
--password="$BACKUP_PASS"
--target-dir="$BACKUP_DIR"
--parallel=4
--compress
--compress-threads=4 2>> "$LOG_FILE"
# Symlink guncelle, incremental backup bu linki kullanacak
ln -sfn "$BACKUP_DIR" "$BACKUP_BASE/latest_full"
else
BACKUP_TYPE="incremental"
BACKUP_DIR="$BACKUP_BASE/inc_$DATE"
if [ ! -L "$BACKUP_BASE/latest_full" ]; then
send_alert "Full backup bulunamadi, incremental alinamaz!"
exit 1
fi
BASEDIR=$(readlink -f "$BACKUP_BASE/latest_full")
# Onceki incremental varsa onu baz al
if [ -L "$BACKUP_BASE/latest_inc" ]; then
BASEDIR=$(readlink -f "$BACKUP_BASE/latest_inc")
fi
log "Incremental backup basliyor: $BACKUP_DIR (baz: $BASEDIR)"
xtrabackup --backup
--user="$BACKUP_USER"
--password="$BACKUP_PASS"
--target-dir="$BACKUP_DIR"
--incremental-basedir="$BASEDIR"
--parallel=4
--compress
--compress-threads=4 2>> "$LOG_FILE"
ln -sfn "$BACKUP_DIR" "$BACKUP_BASE/latest_inc"
fi
# Backup boyutunu logla
BACKUP_SIZE=$(du -sh "$BACKUP_DIR" | cut -f1)
log "$BACKUP_TYPE backup tamamlandi. Boyut: $BACKUP_SIZE, Dizin: $BACKUP_DIR"
# Eski yedekleri temizle
log "Eski yedekler temizleniyor (>${RETENTION_DAYS} gun)..."
find "$BACKUP_BASE" -maxdepth 1 -type d -mtime +"$RETENTION_DAYS" -exec rm -rf {} ; 2>/dev/null || true
log "Backup sureci tamamlandi."
Bu script’i cron’a eklemek için:
# /etc/cron.d/mysql-backup
0 2 * * * root /usr/local/bin/mysql_backup.sh
Yedeklerin Uzak Sunucuya Transferi
Yedeği aldığınız sunucuda tutmak felaket senaryolarında işe yaramaz. Hem local hem remote kopyaya ihtiyacınız var:
#!/bin/bash
# Yedekleri remote sunucuya rsync ile gonder
REMOTE_USER="backup"
REMOTE_HOST="backup-server.example.com"
REMOTE_PATH="/mnt/backup/mysql/$(hostname)"
LOCAL_BACKUP="/backup/mysql"
rsync -avz
--delete
--exclude="*.tmp"
-e "ssh -i /root/.ssh/backup_key -o StrictHostKeyChecking=no"
"$LOCAL_BACKUP/"
"$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/"
if [ $? -eq 0 ]; then
echo "Remote transfer basarili: $(date)" >> /var/log/backup_transfer.log
else
echo "HATA: Remote transfer basarisiz: $(date)" >> /var/log/backup_transfer.log
# Alert gonder
fi
Binary Log ile Point-in-Time Recovery
Hem mysqldump hem XtraBackup fiziksel yedeklerini binary log ile tamamlayarak dakika bazında kurtarma yapabilirsiniz. Önce binary log’u aktifleyin:
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
expire_logs_days = 14
max_binlog_size = 500M
Senaryo: Bugün sabah 02:00’da yedek aldınız. Saat 14:30’da birileri yanlışlıkla önemli bir tabloyu truncate etti. XtraBackup yedeğini geri yükledikten sonra binary log’larla o ana kadar geri getirebilirsiniz:
# Binary log'lardan SQL uret, 02:00'dan 14:29'a kadar
mysqlbinlog
--start-datetime="2024-01-15 02:00:00"
--stop-datetime="2024-01-15 14:29:00"
/var/log/mysql/mysql-bin.000045
/var/log/mysql/mysql-bin.000046
> /tmp/recovery.sql
# Geri yukle
mysql -u root -p < /tmp/recovery.sql
Yedek Dogrulama: En Cok Atlanan Adim
Yedek aldınız, güzel. Peki çalışıyor mu? Production’da onlarca kez gördüm, “yedek alıyoruz” denilen sistemlerde restore denendiğinde dosyalar bozuk, eksik veya uyumsuz çıkıyor.
Basit bir doğrulama script’i:
#!/bin/bash
# backup_verify.sh - Yedek dogrulama
BACKUP_DIR="$1"
TEST_DATADIR="/tmp/mysql_verify_$$"
TEST_PORT=3307
if [ -z "$BACKUP_DIR" ]; then
echo "Kullanim: $0 <backup_dizini>"
exit 1
fi
echo "Yedek dogrulaniyor: $BACKUP_DIR"
# Prepare et
xtrabackup --prepare --target-dir="$BACKUP_DIR" 2>/dev/null
if [ $? -ne 0 ]; then
echo "HATA: Prepare asamasi basarisiz!"
exit 1
fi
# Gecici dizine copy-back
mkdir -p "$TEST_DATADIR"
xtrabackup --copy-back
--target-dir="$BACKUP_DIR"
--datadir="$TEST_DATADIR" 2>/dev/null
chown -R mysql:mysql "$TEST_DATADIR"
# Gecici MySQL instance baslat
mysqld --user=mysql
--datadir="$TEST_DATADIR"
--port="$TEST_PORT"
--socket=/tmp/mysql_verify.sock
--skip-networking=OFF
--daemonize
sleep 5
# Temel sorgu calistir
RESULT=$(mysql
--socket=/tmp/mysql_verify.sock
-u root
--skip-password
-e "SHOW DATABASES;" 2>/dev/null)
if echo "$RESULT" | grep -q "information_schema"; then
echo "BASARILI: Yedek dogrulandı, veritabanları erisilebilir durumda."
else
echo "HATA: Yedek dogrulanamadi!"
fi
# Temizlik
mysqladmin --socket=/tmp/mysql_verify.sock -u root --skip-password shutdown 2>/dev/null
rm -rf "$TEST_DATADIR"
Ne Zaman Hangisini Kullanmalısınız?
Pratikte ikisini birlikte kullanmak en sağlıklısı. Ama seçim yapmak gerekirse:
mysqldump tercih edin:
- Veritabanı 10GB altındaysa
- Farklı MySQL versiyonlarına veya farklı bir RDBMS’e migration yapıyorsanız
- Belirli tabloları veya satırları yedeklemeniz gerekiyorsa
- Basit setup, minimal bağımlılık istiyorsanız
- MyISAM ağırlıklı bir yapınız varsa
XtraBackup tercih edin:
- Veritabanı 10GB üzerindeyse
- Servis kesintisi kabul edilemiyorsa (7/24 production)
- Hızlı restore süresi kritikse
- Incremental backup stratejisi kuruyorsanız
- InnoDB ağırlıklı yapınız varsa
- Replication slave kurulumu yapıyorsanız
Monitoring ve Alerting
Yedekleme sistemi kurulduktan sonra izlenmesi de şart. En azından şunlara bakılmalı:
- Son yedek ne zaman alındı: 24 saati geçtiyse alert
- Yedek boyutu: Normalden %50 küçük veya büyükse anomali var demektir
- Remote transfer durumu: Transfer başarısız mı?
- Disk kullanımı: Backup dizini doluyorsa yeni yedek alınamaz
Bunu basit bir cron job ile kontrol edebilirsiniz:
# Backup yas kontrolu - /usr/local/bin/check_backup_age.sh
#!/bin/bash
MAX_AGE_HOURS=25
BACKUP_BASE="/backup/mysql"
LATEST=$(find "$BACKUP_BASE" -maxdepth 1 -type d -name "full_*" | sort | tail -1)
if [ -z "$LATEST" ]; then
echo "CRITICAL: Hic full backup bulunamadi!"
exit 2
fi
BACKUP_AGE=$(( ($(date +%s) - $(stat -c %Y "$LATEST")) / 3600 ))
if [ "$BACKUP_AGE" -gt "$MAX_AGE_HOURS" ]; then
echo "WARNING: Son backup ${BACKUP_AGE} saat once alindi: $LATEST"
exit 1
else
echo "OK: Son backup ${BACKUP_AGE} saat once alindi."
exit 0
fi
Sonuç
MySQL yedekleme stratejisi oluştururken tek bir araçla yetinmek yerine ihtiyaca göre doğru kombinasyonu bulmak gerekiyor. Küçük veritabanları için mysqldump hala son derece geçerli ve yönetimi kolay bir çözüm. Ama veritabanınız büyüdükçe, uptime gereksinimleriniz arttıkça ve restore sürenizin dakikalarla ölçülmesi gerektiğinde Percona XtraBackup vazgeçilmez hale geliyor.
En kritik nokta şu: Almak değil, geri yüklemek backup sayılır. Ne kadar gelişmiş bir yedekleme sistemi kurarsanız kurun, eğer restore prosedürünüzü test etmiyorsanız ve doğrulama yapmıyorsanız, bir felaket anında sürprizle karşılaşma ihtimaliniz yüksek. En az ayda bir, tercihen haftada bir, yedekten restore yapın. Staging ortamınız yoksa geçici bir Docker container bile bu iş için yeterli. Ve binary log’ları aktifleştirip point-in-time recovery kapısını açık tutun, çünkü “yanlışlıkla silinen tablo” senaryosu düşündüğünüzden daha sık yaşanıyor.