Veritabanı Yönetimi

    PostgreSQL WAL Arşivleme ve PITR

    WAL arşivleme kurup PITR ile veritabanını istediğiniz saniyeye geri döndürmenin rehberi.

    10 dk okuma Güncellendi: 25 Ağustos 2026

    Gece 03:00'te alınan pg_dump yedeğiniz var ve saat 16:40'ta biri yanlışlıkla DELETE FROM musteriler çalıştırdı. Yedeği geri yüklerseniz 13 saat 40 dakikalık veriyi kaybedersiniz. PostgreSQL WAL arşivleme tam olarak bu boşluğu kapatmak için var: veritabanının yaptığı her değişikliği sıralı bir günlük dosyası olarak biriktirir ve sizi "yedeğin alındığı ana" değil, istediğiniz saniyeye geri döndürebilir. Bu tekniğin adı point-in-time recovery, kısaca PITR.

    Bu rehberde WAL'ın ne olduğunu ve neden mantıksal yedekten farklı bir güvence verdiğini, wal_level, archive_mode ve archive_command ayarlarını sıfırdan yapılandırmayı, pg_basebackup ile temel yedek almayı, bir felaket senaryosunda recovery_target_time kullanarak kurtarmayı ve arşivin diski doldurmasını engelleyen saklama politikasını anlatacağım. Sonda da bu kurulumu ilk kez yapan herkesin düştüğü tuzakları topladım — özellikle archive_command hata döndürdüğünde neden diskiniz sessizce dolmaya başlar, onu mutlaka okuyun.

    WAL Nedir ve pg_dump'tan Farkı Ne#

    WAL (Write-Ahead Log), PostgreSQL'in dayanıklılık mekanizmasının kalbidir. Veritabanı bir satırı değiştirmeden önce o değişikliği sıralı bir günlük dosyasına yazar. Sunucu aniden kapansa bile açılışta bu günlüğü tekrar oynatarak tutarlı duruma döner. Normal şartlarda bu dosyalar checkpoint sonrası geri dönüştürülür; arşivleme yaptığınızda ise silinmeden önce başka bir yere kopyalanır ve elinizde değişikliklerin kesintisiz bir kaydı birikir.

    Bu mekanizmanın pg_dump'tan farkı temeldir. pg_dump mantıksal bir yedektir: belirli bir andaki verinin SQL karşılığını üretir, taşınabilir ve sürümler arası uyumludur, ama alındığı anın dışında hiçbir noktaya dönemezsiniz. WAL arşivleme ise fiziksel bir yedektir: veri dosyalarının bayt düzeyinde kopyası artı sonrasındaki tüm değişiklikler. İkisi rakip değil, tamamlayıcıdır; ciddi bir kurulumda her ikisi de olur.

    Özellikpg_dump (mantıksal)WAL arşivleme + PITR (fiziksel)
    Geri dönülebilecek anSadece yedek anıArşiv aralığındaki herhangi bir saniye
    Tek tabloyu geri yüklemeKolayDoğrudan mümkün değil
    Sürümler arası taşımaDesteklerAynı ana sürüm gerekir
    Yedek alma maliyetiTüm veriyi okur, CPU yoğunTemel yedek bir kez, sonra artımlı
    Tipik veri kaybı (RPO)Yedek periyodu kadarSaniyeler

    Yani soru "hangisi?" değil, "ikisi birden nasıl?" olmalı. Mantıksal yedek tarafını pg_dump ile PostgreSQL yedekleme yazısında ayrıntılı anlatmıştım; burada fiziksel tarafı kuracağız.

    WAL Arşivlemeyi Yapılandırma#

    Arşivlemeyi açmak üç ayarın değişmesini gerektirir ve bunlardan biri sunucu yeniden başlatması ister. Ayarları postgresql.conf içinde ya da conf.d altındaki ayrı bir dosyada tutabilirsiniz; ikincisi paket güncellemelerinde ayarlarınızın ezilmemesi açısından daha temizdir.

    # /etc/postgresql/16/main/conf.d/10-arsiv.conf
    
    # WAL'a kurtarma için yeterli bilgi yazılsın (varsayılan zaten replica'dır)
    wal_level = replica
    
    # Arşivlemeyi aç. Bu ayar YENİDEN BAŞLATMA ister.
    archive_mode = on
    
    # WAL segmenti dolduğunda çalıştırılacak komut.
    # %p = kaynak dosyanın tam yolu, %f = yalnızca dosya adı
    archive_command = 'test ! -f /var/lib/postgresql/wal_arsiv/%f && cp %p /var/lib/postgresql/wal_arsiv/%f'
    
    # Yoğun olmayan sistemlerde segment dolmasa da bu sürede arşive gönder
    archive_timeout = 300
    
    # Kurtarma sırasında kaç paralel süreç okusun (isteğe bağlı, PG 15+)
    max_wal_senders = 5
    

    archive_command içindeki test ! -f ... && kısmı ihmal edilecek bir detay değildir: aynı dosya adının üzerine yazmayı engeller. Bu kontrolü koymazsanız, bir kurtarma sonrası zaman çizgisi karışıklığında arşivdeki geçerli bir segment sessizce ezilebilir ve kurtarma zinciriniz kopar.

    Ayarları yazdıktan sonra dizini hazırlayın ve servisi yeniden başlatın:

    # Arşiv dizini postgres kullanıcısının olmalı
    sudo install -d -o postgres -g postgres -m 0700 /var/lib/postgresql/wal_arsiv
    
    sudo systemctl restart postgresql
    
    # Arşivlemenin gerçekten çalıştığını doğrulayın
    sudo -u postgres psql -c "SELECT * FROM pg_stat_archiver;"
    

    pg_stat_archiver çıktısında archived_count artıyor ve failed_count sıfır kalıyorsa kurulum doğrudur. last_failed_wal dolu geliyorsa komutunuz hata veriyor demektir; sorunu çözmeden ilerlemeyin.

    pg_basebackup ile Temel Yedeği Almak#

    WAL arşivi tek başına işe yaramaz; üzerine oynatılacak bir başlangıç noktası gerekir. Bu başlangıca base backup denir ve veri dizininin fiziksel kopyasıdır. pg_basebackup aracı bunu sunucu çalışırken, kesintisiz alır.

    # Yedek alacak rol; süper kullanıcı olması şart değil
    sudo -u postgres psql -c \
      "CREATE ROLE yedekci WITH LOGIN REPLICATION PASSWORD 'guclu-bir-parola';"
    

    pg_hba.conf dosyasına bu rol için bir replikasyon satırı ekleyin, sonra pg_ctl reload çalıştırın:

    # TYPE  DATABASE        USER      ADDRESS          METHOD
    host    replication     yedekci   185.12.34.56/32  scram-sha-256
    

    Ardından yedeği alın. Sıkıştırılmış tar biçimi hem yer kazandırır hem tek dosyada taşınabilir olur:

    sudo -u postgres pg_basebackup \
      --host=127.0.0.1 --username=yedekci \
      --pgdata=/yedek/base/$(date -u +%Y%m%dT%H%M%SZ) \
      --format=tar --gzip --compress=6 \
      --wal-method=stream \
      --checkpoint=fast \
      --progress --verbose
    

    Parametrelerin her biri bilinçli seçilmiştir: --wal-method=stream yedek sürerken üretilen WAL segmentlerini de paralel olarak çeker, böylece yedek kendi başına tutarlı olur. --checkpoint=fast yedeğin başlamasını bir checkpoint beklemeden hızlandırır ama anlık IO yükü yaratır; yoğun saatlerde spread tercih edin. Yedeğin bittiğini backup_manifest dosyasının oluşmasından anlarsınız ve doğrulamayı pg_verifybackup ile yaparsınız:

    sudo -u postgres pg_verifybackup /yedek/base/20260825T031500Z
    # Çıktı: backup successfully verified
    

    PITR ile Belirli Bir Ana Geri Dönmek#

    Şimdi asıl senaryo. Saat 16:40'ta yanlış DELETE çalıştı, siz 16:52'de fark ettiniz. Hedefiniz veritabanını 16:39:00'a döndürmek. Kurtarma sırası şudur ve adımların sırası önemlidir:

    1. PostgreSQL servisini durdurun. Çalışan bir örneğin veri dizinine dokunmayın.
    2. Mevcut veri dizinini silmeyin, yeniden adlandırın — geri dönmeniz gerekebilir.
    3. Temel yedeği yeni bir veri dizinine açın.
    4. postgresql.conf içine restore_command ve hedef zamanı yazın.
    5. recovery.signal dosyasını oluşturun ve servisi başlatın.
    sudo systemctl stop postgresql
    
    # Adım 2: eskisini sakla
    sudo mv /var/lib/postgresql/16/main /var/lib/postgresql/16/main.bozuk
    
    # Adım 3: temel yedeği aç
    sudo install -d -o postgres -g postgres -m 0700 /var/lib/postgresql/16/main
    sudo -u postgres tar -xzf /yedek/base/20260825T031500Z/base.tar.gz \
         -C /var/lib/postgresql/16/main
    

    Kurtarma ayarları PostgreSQL 12'den itibaren ayrı bir recovery.conf dosyasında değil, doğrudan ana yapılandırmada tutulur:

    # Arşivden WAL segmentlerini geri getiren komut
    restore_command = 'cp /var/lib/postgresql/wal_arsiv/%f %p'
    
    # Nereye kadar oynatılsın
    recovery_target_time = '2026-08-25 16:39:00+03'
    
    # Hedefe ulaşınca ne yapılsın: promote = aç ve yazmaya izin ver
    recovery_target_action = 'promote'
    
    # recovery.signal dosyası kurtarma modunu tetikler
    sudo -u postgres touch /var/lib/postgresql/16/main/recovery.signal
    sudo systemctl start postgresql
    
    # Kurtarma ilerlemesini canlı izleyin
    sudo tail -f /var/log/postgresql/postgresql-16-main.log
    

    Günlükte recovery stopping before commit of transaction ... ve ardından database system is ready to accept connections satırlarını gördüğünüzde iş bitmiştir. Verinin doğru ana döndüğünü kontrol edin; yanlış bir noktaya geldiyseniz recovery_target_time değerini değiştirip aynı temel yedekten tekrar deneyebilirsiniz. recovery_target_action yerine pause yazarsanız sunucu hedefte durur ve yazmaya açmadan önce kontrol etme fırsatı verir; doğruladıktan sonra SELECT pg_wal_replay_resume(); ile devam ettirirsiniz.

    Hedef parametresiNe yaparTipik kullanım
    recovery_target_timeBelirtilen zamana kadar oynatırYanlış silme, hatalı toplu güncelleme
    recovery_target_xidBelirtilen işlem kimliğine kadarİşlem kimliği biliniyorsa
    recovery_target_lsnBelirtilen WAL konumuna kadarHassas replikasyon senaryoları
    recovery_target_namepg_create_restore_point ile konan işaretRiskli göç öncesi işaret koymak
    recovery_target = 'immediate'Tutarlı olur olmaz dururSadece yedeği doğrulamak için

    Riskli bir şema göçünden hemen önce SELECT pg_create_restore_point('gocten_once'); çalıştırmak çok işe yarar; geri dönmeniz gerekirse saati tahmin etmek yerine bu işarete dönersiniz.

    Arşiv Boyutunu Yönetmek ve Saklama Politikası#

    WAL arşivinin sinsi tarafı, hiç kimse silmezse sonsuza kadar büyümesidir. Yoğun bir sistemde günde onlarca gigabayt WAL üretilebilir ve iki hafta içinde diski doldurur. Bu yüzden arşivleme kurulumunun ayrılmaz parçası bir saklama politikasıdır.

    Kural şudur: elinizdeki en eski temel yedekten önceki WAL segmentleri gereksizdir. İki temel yedeğiniz varsa ve en eskisi 10 gün öncesine aitse, o tarihten önceki tüm segmentleri silebilirsiniz. Bunu elle yapmak yerine bir betikle çalıştırın:

    #!/usr/bin/env bash
    # 14 günden eski WAL segmentlerini ve 30 günden eski temel yedekleri temizler
    set -euo pipefail
    ARSIV=/var/lib/postgresql/wal_arsiv
    BASE=/yedek/base
    
    find "$ARSIV" -type f -mtime +14 -delete
    find "$BASE"  -mindepth 1 -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
    
    # Doluluk oranını da raporla
    df -h "$ARSIV" | tail -1
    

    Daha büyük kurulumlarda bu işi elle yönetmek yerine pgBackRest veya Barman gibi araçlara devretmek doğru olur; ikisi de artımlı yedek, sıkıştırma, paralel transfer ve saklama politikasını hazır sunar. Arşivi mutlaka veritabanı sunucusundan başka bir yere yazın: aynı diskte tutulan bir arşiv, disk arızasında yedeğinizle birlikte gider. Uzak bir hedefe yazmak istediğinizde archive_command bir betiği çağırabilir:

    archive_command = '/usr/local/bin/wal-gonder.sh %p %f'
    

    Bu betiğin içinde rsync, rclone veya nesne depolama istemcisi çalıştırabilirsiniz. Yalnız bir kural var: betik başarısızsa sıfırdan farklı bir çıkış kodu döndürmeli. Sebebini bir sonraki bölümde açıklıyorum. Yedeklerinizi ayrı bir konumda tutmak için yedekleme hizmetimiz veya ikinci bir bulut sunucu örneği pratik bir hedef olur.

    Sık Yapılan Hatalar ve Tuzaklar#

    archive_command başarısız olunca disk sessizce dolar. PostgreSQL, arşivlenemeyen bir WAL segmentini silmez; komut başarılı olana kadar pg_wal dizininde tutar. Hedef dizin dolduysa, izinler bozulduysa ya da uzak sunucu erişilemez hale geldiyse pg_wal büyümeye başlar ve sonunda veri diski dolduğu için veritabanı yazmayı reddeder. Bu yüzden pg_stat_archiver içindeki failed_count değerini izleme sisteminize mutlaka ekleyin:

    SELECT archived_count, failed_count, last_failed_wal, last_failed_time
    FROM pg_stat_archiver;
    

    Komutun başarısızlığı gizlemesi. archive_command içinde cp %p /hedef/%f || true gibi bir şey yazarsanız veya scp çıkış kodunu yutan bir betik kullanırsanız, PostgreSQL segmentin arşivlendiğini sanıp siler. Arşiviniz delik deşik olur ve bunu ancak kurtarma denemesinde fark edersiniz. Betiğiniz hata durumunda mutlaka sıfırdan farklı kod döndürmelidir.

    Sadece temel yedeği alıp WAL'ı unutmak. Base backup tek başına yalnızca alındığı ana dönmenizi sağlar; PITR'ın "istediğin saniye" vaadi WAL zincirinin kesintisiz olmasına bağlıdır. Arşivde tek bir eksik segment varsa kurtarma o noktada durur.

    Kurtarmayı hiç denememek. En sık ve en pahalı hata budur. Yedek almak yarım iştir; geri yükleme prosedürünü prova etmediyseniz elinizde yedek değil, yedek olduğunu düşündüğünüz dosyalar vardır. Ayda bir kez ayrı bir sunucuda recovery_target = 'immediate' ile açıp doğrulayın.

    Ana sürüm uyumsuzluğu. Fiziksel yedekler yalnızca aynı PostgreSQL ana sürümüne geri yüklenebilir. PostgreSQL 16 ile alınmış bir base backup'ı 17 üzerine açamazsınız. Sürüm yükseltmelerinde geçiş yolunuz mantıksal yedektir; bu yüzden ikisini birlikte tutmak gerekir.

    Zaman dilimi karışıklığı. recovery_target_time değerini yazarken saat dilimini açıkça belirtin (+03 gibi). Sunucunun timezone ayarı ile sizin varsaydığınız dilim farklıysa üç saatlik bir sapmayla yanlış noktaya kurtarma yaparsınız ve bunu ancak veriye baktığınızda anlarsınız.

    Sıkça Sorulan Sorular#

    WAL arşivleme replikasyonun yerine geçer mi#

    Hayır, ikisi farklı problemleri çözer. Streaming replication verinizin ikinci bir sunucuda canlı kopyasını tutar ve sunucu arızasında hızlı devretme sağlar; ama yanlışlıkla silinen bir tabloyu kurtarmaz, çünkü silme işlemi replikaya da anında yansır. WAL arşivleme ise zamanda geriye gitme imkânı verir. Ciddi bir kurulumda ikisi birlikte kullanılır: replikasyon erişilebilirlik için, arşiv insan hatası ve mantıksal bozulma için.

    PITR ne kadar veri kaybıyla çalışır#

    Kayıp miktarı arşivleme sıklığınıza bağlıdır. Varsayılan davranışta bir WAL segmenti (16 MB) dolmadan arşive gitmez, bu da sakin bir sistemde saatlerce beklemek anlamına gelebilir. archive_timeout ayarını 60-300 saniyeye çekerek en fazla o kadarlık bir pencere bırakırsınız. Sıfır kayıp istiyorsanız senkron replikasyon veya WAL'ı sürekli akıtan bir araç (pgBackRest'in stream modu gibi) gerekir.

    Arşiv dizini ne kadar yer kaplar#

    Tamamen yazma yoğunluğunuza bağlıdır ve tahmin etmek yerine ölçmelisiniz. Bir gün boyunca arşiv dizininin büyümesini izleyin, çıkan rakamı saklama süresiyle çarpın. Kabaca bir referans olarak, orta yoğunlukta bir uygulama veritabanı günde birkaç yüz megabayttan birkaç gigabayta kadar WAL üretir. Sıkıştırılmış arşivleme kullanırsanız bu rakam genelde yarıdan aşağı düşer.

    wal_level ayarını minimal yaparsam ne olur#

    wal_level = minimal ayarı WAL'a yalnızca çökme kurtarması için gereken minimum bilgiyi yazar ve arşivleme ile replikasyonu imkânsız hale getirir; zaten archive_mode = on ile birlikte kullanılamaz, sunucu başlamaz. Toplu veri yükleme işlemlerini hızlandırdığı için cazip görünür ama PITR'dan tamamen vazgeçmek anlamına gelir. Varsayılan olan replica seviyesinde kalın.

    Yanlış bir zamana kurtarma yaptım, tekrar deneyebilir miyim#

    Evet, yeter ki temel yedeğinizi ve WAL arşivinizi silmemiş olun. Kurtarılan örneği durdurun, veri dizinini temizleyin, temel yedeği yeniden açın, recovery_target_time değerini düzeltip tekrar başlatın. Bu yüzden kurtarma yaparken orijinal veri dizinini silmek yerine yeniden adlandırmanız ve temel yedeği asla üzerine yazmamanız gerekir. recovery_target_action = 'pause' kullanmak da yanlış noktada yazmaya açılmayı önler.

    pg_dump almayı bırakabilir miyim#

    Bırakmayın. WAL arşivleme fiziksel bir yedektir ve tek bir tabloyu geri yükleme, farklı bir ana sürüme taşıma ya da başka bir makinede bağımsız kopya oluşturma gibi işlerde işinize yaramaz. İkisinin görev alanı farklıdır: günlük mantıksal yedek taşınabilirlik ve seçici kurtarma için, WAL arşivi ise düşük veri kaybı ve zamanda geri dönüş için. Disk maliyeti, iki yöntemi birlikte tutmanın yanında önemsiz kalır.

    Kapanış#

    WAL arşivleme ve PITR, veritabanı yedekleme stratejinizi "dün geceye dönebilirim"den "on beş dakika öncesine dönebilirim"e yükselten adımdır. Aklınızda kalması gereken dört alışkanlık şunlar: pg_stat_archiver içindeki failed_count değerini izleyin çünkü sessiz bir arşiv hatası diskinizi doldurur, archive_command betiğinizin hata durumunda sıfırdan farklı kod döndürdüğünden emin olun, arşivi kesinlikle veritabanı sunucusunun dışında bir yerde tutun ve kurtarmayı ayda bir gerçekten prova edin.

    Bu düzeni kurmak için yeterli disk alanı ve ayrı bir hedef sunucu gerekiyorsa, arşivi ikinci bir makinede tutmak üzere bulut sunucu veya VDS paketlerimizi kullanabilir, düzenli anlık görüntü ve kopyalar için yedekleme çözümümüzü değerlendirebilirsiniz. Arşivleme kurulumu, izleme ve kurtarma provalarını kendiniz üstlenmek istemiyorsanız sunucu yönetimi hizmetimiz bu döngüyü sizin adınıza işletir.

    PostgreSQLYedeklemePITR

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.