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.
