MySQL Yedekleme Stratejileri: mysqldump ve Percona XtraBackup Kullanımı

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.

Yorum yapın