Veritabanı Yedekleme Stratejisi: Sıklık ve Yöntem Seçimi

Yıllar içinde pek çok firmada veritabanı yedekleme sistemleri kurdum ve her seferinde aynı hatalarla karşılaştım. Ya yedek çok seyrek alınıyor, ya yanlış yöntem seçilmiş, ya da “yedek var” zannedilirken aslında bozuk yedekler birikmiş. Bu yazıda size gerçek dünya deneyimlerimden süzülmüş bir veritabanı yedekleme stratejisi anlatacağım.

Neden “Sadece Yedek Al” Yetmez

Veritabanı yedeklemesi, çoğu ekibin düşündüğünden çok daha karmaşık bir konudur. Bir e-ticaret firmasında görev yaparken şöyle bir durumla karşılaştım: Ekip her gece düzenli mysqldump çalıştırıyor, yedekler S3’e gönderiliyordu. Herkes rahat uyuyordu. Ta ki bir gün production veritabanı çöküp, 3 günlük yedeklerin tamamının bozuk olduğu ortaya çıkana kadar. Sorun basitti: Disk dolmuş, mysqldump sessizce yarım dosya yazmış, kimse doğrulama yapmamıştı.

İşte bu yüzden “yedek al” ile “güvenilir yedekleme stratejisi kur” arasında dağlar kadar fark var.

Temel Kavramlar: RTO ve RPO

Yedekleme stratejisi belirlemeden önce iki kavramı netleştirmeniz gerekiyor:

RPO (Recovery Point Objective): Bir arıza durumunda maksimum ne kadar veri kaybını kabul edebilirsiniz? “Son 1 saatlik veriyi kaybedebiliriz” diyorsanız RPO’nuz 1 saattir.

RTO (Recovery Time Objective): Sistemi ne kadar sürede ayağa kaldırmanız gerekiyor? “4 saat içinde online olmalıyız” diyorsanız RTO’nuz 4 saattir.

Bu iki değer, hangi yedekleme yöntemini ve sıklığını seçeceğinizi doğrudan belirler. Günlük yedek alan bir sistem için RPO teorik olarak 24 saattir. Eğer iş gereksinimi “maksimum 1 saatlik veri kaybı” diyorsa, günlük yedek tamamen yetersizdir.

Yedekleme Yöntemleri

Full Backup

En basit ve en güvenilir yöntemdir. Veritabanının tamamını her seferinde yedekler.

# MySQL tam yedek
mysqldump -u root -p 
  --single-transaction 
  --routines 
  --triggers 
  --all-databases 
  --hex-blob 
  | gzip > /backup/full_$(date +%Y%m%d_%H%M%S).sql.gz

# PostgreSQL tam yedek
pg_dumpall -U postgres 
  | gzip > /backup/pgfull_$(date +%Y%m%d_%H%M%S).sql.gz

Avantajları: Restore süreci basittir, tek dosyayla geri dönülür, anlama ve yönetimi kolaydır.

Dezavantajları: Büyük veritabanlarında hem disk hem de zaman maliyeti yüksektir. 500 GB’lık bir veritabanında her gece full backup almak ciddi bir I/O yükü yaratır.

Incremental Backup

Sadece son yedekten bu yana değişen verileri yedekler. MySQL için binary log tabanlı, PostgreSQL için WAL (Write-Ahead Log) tabanlı çalışır.

# MySQL binary log ile incremental yedek
# Önce binary log'ların aktif olduğunu doğrulayın
mysql -u root -p -e "SHOW VARIABLES LIKE 'log_bin';"

# Binary log dosyalarını yedekle
mysqlbinlog 
  --read-from-remote-server 
  --host=localhost 
  --user=backup_user 
  --password=SifreGirin 
  --raw 
  --stop-never 
  mysql-bin.000001 
  --result-file=/backup/binlogs/

PostgreSQL tarafında WAL arşivleme için postgresql.conf dosyasına şunları eklemeniz gerekir:

# postgresql.conf ayarları
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /backup/wal/%f && cp %p /backup/wal/%f'
archive_timeout = 300  # 5 dakikada bir zorunlu WAL rotasyonu

Differential Backup

Son full backup’tan bu yana değişen tüm verileri yedekler. Incremental’a göre restore daha basittir (sadece 2 set gerekir: son full + son differential), ama incremental’a göre daha fazla yer kaplar.

Fiziksel vs Mantıksal Yedekleme

Bu ayrım önemli çünkü restore sürenizi doğrudan etkiler.

Mantıksal yedekleme (mysqldump, pg_dump): SQL ifadeleri olarak saklar. Taşınabilirdir, versiyon bağımsızdır, ancak büyük veritabanlarında restore çok yavaştır.

Fiziksel yedekleme (Percona XtraBackup, pgBackRest): Veri dosyalarını doğrudan kopyalar. Restore çok daha hızlıdır, ancak aynı veritabanı versiyonu gerektirir.

# Percona XtraBackup ile full backup
xtrabackup --backup 
  --user=backup_user 
  --password=SifreGirin 
  --target-dir=/backup/xtrabackup/full/

# Backup'ı hazırlama (prepare) aşaması
xtrabackup --prepare 
  --target-dir=/backup/xtrabackup/full/

# Incremental backup alma
xtrabackup --backup 
  --user=backup_user 
  --password=SifreGirin 
  --target-dir=/backup/xtrabackup/inc1/ 
  --incremental-basedir=/backup/xtrabackup/full/

Sıklık Seçimi: Ne Zaman Ne Almalı?

Bunu bir formülle anlatayım: İş kritikliği + veri değişim hızı = yedek sıklığı.

Düşük trafikli, günde birkaç yüz kayıt eklenen bir iç uygulama için günlük full backup yeterlidir. Ama saniyede yüzlerce işlem gören bir ödeme sistemi için saatlik hatta daha sık yedekleme gerekebilir.

Pratikte çoğu sistem için şu kombinasyon iyi çalışır:

  • Haftalık full backup: Her Pazar gece 02:00’de tam yedek
  • Günlük differential backup: Her gece full değil, sadece haftanın değişiklikleri
  • Sürekli binary log / WAL arşivleme: Dakika bazında veri koruması

Bu strateji ile herhangi bir noktaya geri dönebilirsiniz ve her gece full backup almanın getirdiği yükten de kurtulursunuz.

Cron ile Otomatik Yedekleme

# /etc/cron.d/db-backup dosyası
# Haftalık full backup - Pazar 02:00
0 2 * * 0 backup_user /usr/local/bin/db_full_backup.sh >> /var/log/db_backup.log 2>&1

# Günlük incremental - Pazartesi'den Cumartesi'ye 02:00
0 2 * * 1-6 backup_user /usr/local/bin/db_incremental_backup.sh >> /var/log/db_backup.log 2>&1

Yedekleme scriptiniz şöyle görünebilir:

#!/bin/bash
# /usr/local/bin/db_full_backup.sh

set -euo pipefail

BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/full_${DATE}.sql.gz"
LOG_FILE="/var/log/db_backup.log"
RETENTION_DAYS=30
SLACK_WEBHOOK="https://hooks.slack.com/services/SIZIN_WEBHOOK"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

notify_slack() {
    local message="$1"
    curl -s -X POST -H 'Content-type: application/json' 
        --data "{"text":"${message}"}" 
        "$SLACK_WEBHOOK" > /dev/null
}

# Disk kontrolü - en az 20GB boş alan olmalı
AVAILABLE=$(df -BG "$BACKUP_DIR" | awk 'NR==2 {print $4}' | tr -d 'G')
if [ "$AVAILABLE" -lt 20 ]; then
    log "HATA: Yetersiz disk alanı: ${AVAILABLE}GB"
    notify_slack ":x: DB Backup BAŞARISIZ: Yetersiz disk alanı (${AVAILABLE}GB)"
    exit 1
fi

log "Full backup başlıyor..."

mysqldump -u backup_user -pSifreBuraya 
    --single-transaction 
    --routines 
    --triggers 
    --all-databases 
    --hex-blob 
    | gzip > "$BACKUP_FILE"

# Dosya boyutunu kontrol et (0 byte ise hata var)
FILE_SIZE=$(stat -c%s "$BACKUP_FILE")
if [ "$FILE_SIZE" -lt 1000 ]; then
    log "HATA: Backup dosyası çok küçük veya boş: ${FILE_SIZE} bytes"
    notify_slack ":x: DB Backup BAŞARISIZ: Dosya boyutu şüpheli (${FILE_SIZE} bytes)"
    exit 1
fi

log "Backup tamamlandı: ${BACKUP_FILE} ($(du -sh "$BACKUP_FILE" | cut -f1))"

# Eski yedekleri temizle
find "$BACKUP_DIR" -name "full_*.sql.gz" -mtime +"$RETENTION_DAYS" -delete
log "30 günden eski yedekler temizlendi"

notify_slack ":white_check_mark: DB Full Backup başarılı: $(du -sh "$BACKUP_FILE" | cut -f1)"

Yedek Doğrulama: En Çok Atlanan Adım

Yedek aldınız, güzel. Peki o yedeği hiç restore etmeyi denediniz mi? Çoğu ekip bu adımı atlar ve felaket anında “backup var ama açılmıyor” durumu yaşar.

Ben her hafta otomatik bir restore testi yapan bir sistem kuruyorum. Konsept şu: Bir test sunucusunda veya Docker container’ında yedeği açıp, birkaç temel sorgu çalıştırıp, başarılı/başarısız bilgisini raporla.

#!/bin/bash
# backup_verify.sh - Yedeği test ortamına restore edip doğrular

LATEST_BACKUP=$(ls -t /backup/mysql/full_*.sql.gz | head -1)
TEST_CONTAINER="backup-verify-$(date +%Y%m%d)"

log "Doğrulama başlıyor: ${LATEST_BACKUP}"

# Docker container başlat
docker run -d 
    --name "$TEST_CONTAINER" 
    -e MYSQL_ROOT_PASSWORD=testpass 
    -e MYSQL_DATABASE=testdb 
    mysql:8.0

# Container'ın hazır olmasını bekle
sleep 30

# Yedeği restore et
zcat "$LATEST_BACKUP" | docker exec -i "$TEST_CONTAINER" 
    mysql -u root -ptestpass 2>&1

# Temel doğrulama sorguları
RESULT=$(docker exec "$TEST_CONTAINER" 
    mysql -u root -ptestpass -e 
    "SELECT COUNT(*) FROM information_schema.TABLES;" 2>&1)

if echo "$RESULT" | grep -q "^[0-9]"; then
    log "DOĞRULAMA BAŞARILI: Backup geçerli ve restore edilebilir"
    notify_slack ":white_check_mark: Backup doğrulama başarılı"
else
    log "DOĞRULAMA BAŞARISIZ"
    notify_slack ":x: Backup doğrulama BAŞARISIZ! Manuel kontrol gerekli"
fi

# Temizlik
docker rm -f "$TEST_CONTAINER"

Uzak Depolama: 3-2-1 Kuralı

Yedekleri sadece aynı sunucuda tutmak, sunucu gidince yedekle birlikte gitmek demektir. 3-2-1 kuralı şöyle der:

  • 3 kopya veri
  • 2 farklı medya/ortam
  • 1 offsite (farklı lokasyon)

Bunun pratik uygulaması: Yerel disk + NAS veya başka bir sunucu + S3/Backblaze B2/başka bulut depolama.

# rclone ile S3'e backup gönderme
# Önce rclone yapılandırın: rclone config

rclone copy /backup/mysql/ 
    s3-backup:firma-db-backups/mysql/ 
    --include "*.sql.gz" 
    --min-age 1h 
    --progress 
    --log-file /var/log/rclone_backup.log

# Başarı kontrolü
if [ $? -eq 0 ]; then
    log "S3 sync tamamlandı"
else
    log "HATA: S3 sync başarısız"
    notify_slack ":x: S3 backup sync başarısız!"
fi

S3’e gönderirken şifreli göndermek için:

# GPG ile şifreleyip S3'e gönder
gpg --batch --yes 
    --recipient [email protected] 
    --encrypt "$BACKUP_FILE"

rclone copy "${BACKUP_FILE}.gpg" 
    s3-backup:firma-db-backups/encrypted/

Büyük Veritabanları İçin Özel Durumlar

500 GB’ı aşan veritabanlarında geleneksel mysqldump artık pratik olmaktan çıkar. Burada pgBackRest (PostgreSQL için) veya Percona XtraBackup (MySQL için) kullanmak şart haline gelir.

pgBackRest konfigürasyonu şöyle görünür:

# /etc/pgbackrest/pgbackrest.conf

[global]
repo1-path=/backup/pgbackrest
repo1-retention-full=2
repo1-retention-diff=7
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=GucluBirSifre

start-fast=y
stop-auto=y
log-level-console=info
log-level-file=detail

[main]
pg1-path=/var/lib/postgresql/14/main
pg1-user=postgres
pg1-port=5432

Sonra yedekleme şu komutlarla yapılır:

# Stanza oluşturma (bir kez yapılır)
pgbackrest --stanza=main stanza-create

# Full backup
pgbackrest --stanza=main --type=full backup

# Differential backup
pgbackrest --stanza=main --type=diff backup

# Point-in-time recovery için belirli bir noktaya restore
pgbackrest --stanza=main --type=time 
    "--target=2024-03-15 14:30:00" 
    restore

İzleme ve Alerting

Yedekleme sistemini kurup bırakmak olmaz. Aktif olarak izlemeniz gerekir. Prometheus ile basit bir yedek yaşı monitörü kurabilirsiniz, ama en hızlı çözüm şu betiktir:

#!/bin/bash
# check_backup_age.sh - Nagios/Zabbix uyumlu backup yaşı kontrolü

MAX_AGE_HOURS=25  # 24 saatlik backup için 1 saat tolerans
BACKUP_DIR="/backup/mysql"

LATEST=$(find "$BACKUP_DIR" -name "full_*.sql.gz" -printf '%T@ %pn' 
    | sort -n | tail -1 | cut -d' ' -f2)

if [ -z "$LATEST" ]; then
    echo "CRITICAL: Hiç backup bulunamadı"
    exit 2
fi

AGE_SECONDS=$(( $(date +%s) - $(stat -c %Y "$LATEST") ))
AGE_HOURS=$(( AGE_SECONDS / 3600 ))

if [ "$AGE_HOURS" -gt "$MAX_AGE_HOURS" ]; then
    echo "CRITICAL: Son backup ${AGE_HOURS} saat önce alındı: ${LATEST}"
    exit 2
elif [ "$AGE_HOURS" -gt 20 ]; then
    echo "WARNING: Son backup ${AGE_HOURS} saat önce alındı"
    exit 1
else
    echo "OK: Son backup ${AGE_HOURS} saat önce alındı: $(basename "$LATEST")"
    exit 0
fi

Bu scripti Zabbix external check veya Nagios servisi olarak ekleyebilirsiniz.

Sık Yapılan Hatalar

Deneyimlerimden derlediğim, gerçekten acı veren hatalar:

  • Restore testi yapmamak: Yedek alıyorsunuz ama geri yükleyemiyorsunuz. Ayda en az bir kez test edin.
  • Disk doluyor, backup sessizce başarısız oluyor: Her backup scriptinde önce disk kontrolü yapın.
  • Yedekleri sadece aynı fiziksel makinede tutmak: Makine yanınca yedek de gider.
  • Şifresiz S3 bucket kullanmak: Hassas veriler için hem aktarımda hem depolamada şifreleme zorunlu.
  • Log’ları okumamak: Backup çalışıyor, hata mesajı veriyor ama kimse log’a bakmuyor. Hata loglarına alert kurun.
  • Retention policy olmadan birikmesine izin vermek: Disk sonunda dolar ve yedek sistemi çöker.
  • Tek bir yedek tipine güvenmek: Sadece full backup almak ya da sadece incremental. İkisini birleştirin.

Sonuç

Veritabanı yedekleme stratejisi belirlerken işin özü şu: Kaybedebileceğiniz maksimum veri miktarını ve sistemi ayağa kaldırmanız gereken süreyi iyi tanımlayın. Bunlar size hem yöntemi hem sıklığı söyler.

Benim önerim hemen hemen her senaryo için işe yarayan şu kombinasyondur: Haftalık full backup, günlük differential veya incremental, sürekli WAL/binlog arşivleme. Buna 3-2-1 depolama stratejisini ekleyin ve her hafta otomatik restore testi yapın.

En iyi yedekleme sistemi, test edilmiş ve gerçekten çalıştığını bildiğiniz sistemdir. Gece 03:00’te production çöktüğünde “sanırım yedek vardır” değil, “biliyorum ki X saate kadar restore ederim” diyebilmek için bu altyapıyı baştan doğru kurun. Sonradan pişman olmak, önceden zahmet çekmekten çok daha maliyetlidir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir