PostgreSQL'de Continuous Archiving Kurmak
Selamlar, bu yazımda PostgreSQL'de continuous archiving (sürekli arşivleme) kurulumuna bakacağız. Klasik pg_dump ile günlük yedek almak çoğu zaman iş görüyor, ama 'sabah 09:47'deki o talihsiz DELETE FROM'dan bir saniye öncesine dönmek istersek dump tek başına yetmez. WAL'ları (Write-Ahead Log) bir kenara biriktirmiyorsak point-in-time recovery (PITR) kapısı kapalı demektir. Hadi o kapıyı açalım.
WAL shipping nedir, neden uğraşıyoruz?
PostgreSQL her transaction'ı önce WAL segment'lerine yazar, sonra data file'larına işler. Bu segment'ler pg_wal/ altında 16 MB'lık dosyalar halinde dolup taşar. Continuous archiving dediğimiz şey aslında çok basit: PostgreSQL bir WAL segment'ini doldurduğu anda, bizim verdiğimiz archive_command'i çalıştırıp o dosyayı güvenli bir yere kopyalıyor. Sonra silinmesine izin veriyor. Yani siz uyurken dahi yedek üretmeye devam ediyor.
İşin güzel tarafı şu: elinizde haftalık bir base backup ve aradaki tüm WAL'lar varsa, o aralıktaki herhangi bir saniyeye geri dönebiliyorsunuz. Bence prod'da koşan her ciddi PostgreSQL kurulumunun bu özelliği aktif olması gerek; siz tek başına pg_dump'a güvenirken müşteriniz bir anlık panik DELETE'i sonrası 6 saatlik veriyi geri istediğinde elinizde sadece dünkü dump kalıyor.
Temel yapilandirma
İlk önce postgresql.conf üzerinde üç ayarı doğru hizaya getirmemiz gerekiyor:
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/archive/%f && cp %p /var/lib/postgresql/archive/%f'
archive_timeout = 300
Burada wal_level = replica arşivleme için yeterli. archive_mode = on bayrağı PostgreSQL'e 'arşivlemeyi başlat' diyor. archive_command ise her segment dolduğunda çalışacak komut: %p segment'in tam yolu, %f ise dosya adı. Komutun başındaki test ! -f ... kontrolü kritik, çünkü mevcut bir dosyanın üzerine yazmak demek arşivinizi sessizce bozmak demek. PostgreSQL exit kodu sıfırı 'arşivlendi, segment'i silebilirsin' olarak yorumluyor; başarısızlıkta yeniden deniyor.
archive_timeout = 300 ise düşük trafikli sistemler için. Yoğun bir uygulamada WAL segment'leri zaten birkaç dakikada doluyor, ama trafik az olunca 16 MB'lık segment yarım saat kapanmayabilir. Timeout sayesinde 5 dakikada bir yarım dolu segment'i de zorla kapatıp arşive yolluyoruz; böylece RPO'nuz (recovery point objective) kontrol altında kalıyor.
archive_mode değişikliği sonrası restart şart, sadece reload yetmez:
sudo systemctl restart postgresql
Sonrasında pg_stat_archiver görünümünden kontrol edebilirsiniz:
SELECT archived_count, last_archived_wal, last_archived_time, failed_count
FROM pg_stat_archiver;
archived_count artıyor ve failed_count sıfırda kalıyorsa işler yolunda.
S3'e archive atmak
Lokal disk geçici bir çözüm; tek makineli bir kurulumda disk patladığında hem veritabanını hem de yedeği aynı anda kaybediyorsunuz. Şahsi tercihim her zaman uzak bir storage'a, çoğu zaman da S3'e arşivlemek olmuştur. AWS CLI ile aslında işin püf noktası tek satırda biter:
archive_command = 'aws s3 cp %p s3://my-backups/wal/%f --only-show-errors'
Ama gerçek hayatta hem sıkıştırmak hem de hata loglarını düzgün almak için küçük bir wrapper script tercih ederim:
#!/bin/bash
# /usr/local/bin/archive_wal.sh
set -euo pipefail
WAL_PATH="$1"
WAL_NAME="$2"
S3_DEST="s3://my-backups/wal/${WAL_NAME}.gz"
gzip -c "$WAL_PATH" | aws s3 cp - "$S3_DEST" --only-show-errors
postgresql.conf'ta da:
archive_command = '/usr/local/bin/archive_wal.sh %p %f'
set -euo pipefail burada hayat kurtarıyor, çünkü gzip | aws zincirinde aws tarafı hata verirse pipefail olmadan komut sıfır exit kodu döner ve PostgreSQL 'arşivlendi' sanır. Sonra restore zamanı segment yok, panik kaçınılmaz.
Base backup ile birlikte
WAL'ları biriktirmek tek başına anlamsız; bir başlangıç noktasına ihtiyacınız var. O da pg_basebackup ile alınan tam veritabanı kopyası:
pg_basebackup \
-D /var/backups/pg/base_$(date +%Y%m%d) \
-Ft -z -Xs -P \
-U replicator
-Xs parametresi base backup süresince üretilen WAL'ları stream ederek aynı klasöre koyuyor; böylece backup tutarlı olarak başlatılabiliyor. Bizim asıl arşivimiz buna ek olarak akmaya devam ediyor.
PITR için recovery anında ihtiyacınız olan üç şey: base backup tarball'ı, base backup zamanından sonraki tüm WAL'lar ve restore_command. Restore tarafında ise:
restore_command = 'aws s3 cp s3://my-backups/wal/%f.gz - | gunzip > %p'
recovery_target_time = '2026-05-17 09:46:00+00'
Sik karsilasilan tuzaklar
- archive_command'in idempotent olmamasi: Aynı segment iki kez denenirse ikinci sefer fail dönmemeli, sessizce başarılı dönmeli.
test ! -fkontrolü ya da S3 tarafında object'in var olup olmadığını kontrol etmek gerek. Yoksa restart sonrası ilk failure tetiklenip arşivleme tıkanır. - failed_count'a bakmamak:
pg_stat_archiver.failed_countsürekli artıyorsapg_wal/şişiyor demektir. Ben bunu Prometheus'a yediripfailed_counttürevi 0'dan büyükse alarm yakıyorum. - archive_timeout'u cok dusuk vermek: 30 saniye gibi bir değer az trafikli sistemlerde gereksiz yere yarı boş segment üretir, S3 maliyetiniz fırlar. 5 dakika tipik olarak iyi bir orta yol.
- wal_level'i degistirmek icin restart unutmak:
archive_modevewal_levelSIGHUP ile değişmez. Bunları değiştirip reload yaparsanız hiç arşivlemediğinizi anlamanız için saatler geçebilir. - Eski WAL'lari temizlememek: Disk veya S3 dolar. Base backup aldıktan sonra
pg_archivecleanupile o backup'tan eski WAL'ları temizlemek gerek; aksi halde maliyet zamanla katlanır.
Kapanis
Bu yazıda WAL shipping mantığını, archive_command yazarken dikkat etmeniz gereken küçük ama yıkıcı detayları ve S3'e arşivlemek için kullanılabilir bir setup'ı gördük. Bence kurulumu yaptıktan sonra bir kez gerçekten restore denemesi yapmadan rahat uyumayın; yedek aldığınızı sandığınız zaman ile aslında alamadığınızı fark ettiğiniz zaman arasındaki o boşluk, kariyerinizin en uzun saati olabiliyor. Umarım faydalı olur, görüşmek üzere.
