dataiotech Labs
dbaguidedbaguideDBAdataxtoolsdataopsphere
PostgreSQL / Backup & Recovery / Runbooks
Teknik Uygulama RehbericriticalHazırlık 1 saat + tatbikat 1–4 saat (veri boyutuna bağlı)
postgresql (14–18)

PostgreSQL Backup'ınızı Doğrulayın: İlk Restore Tatbikatı

Backup job'unun 'başarılı' bitmesi yalnızca dosyanın yazıldığını gösterir. Geri dönülebildiğini gösteren tek kanıt, o backup'tan izole bir ortama gerçekten restore edip veriyi doğrulamaktır. Bu runbook ilk tatbikatınızı uçtan uca yürütür ve sonunda elinizde ölçülmüş bir RTO, ölçülmüş bir RPO ve imzalı bir tatbikat kaydı olur.

Gerekli Ön Koşullar & Güvenlik İlkeleri:
İzole tatbikat ortamı (ayrı host, VM veya container, üretim ağına kapalı)Kaynak veritabanı ile aynı major PostgreSQL sürümü ve eklentileriBackup deposuna salt okunur (read-only) erişim yetkisiBackup boyutunun en az 2 katı boş disk alanı (WAL replay için ek pay)Karşılaştırma ve doğrulama için üretim referans metrikleri ve cutoff sayımları
Özet: Backup job'unun "başarılı" bitmesi yalnızca dosyanın yazıldığını gösterir. Geri dönülebildiğini gösteren tek kanıt, o backup'tan izole bir ortama gerçekten restore edip veriyi doğrulamaktır. Bu runbook ilk tatbikatınızı uçtan uca yürütür ve sonunda elinizde ölçülmüş bir RTO, ölçülmüş bir RPO ve imzalı bir tatbikat kaydı olur.

Bu runbook kimin için?

  • •Backup'ı düzenli alan ama hiç restore etmemiş ekipler
  • •pg_dump, pg_basebackup veya pgBackRest/Barman/WAL-G kullanan PostgreSQL 14–18 ortamları
  • •Denetimde "backup'larınız çalışıyor mu?" sorusuna kanıtla cevap vermesi gereken DBA, SRE ve platform ekipleri

Yönetilen servislerde (RDS, Cloud SQL, Azure Database for PostgreSQL vb.) restore her zaman yeni bir instance oluşturur. Adım 4'ten itibaren doğrulama ve kayıt bölümleri aynen uygulanır.

Tatbikatın akışı

Mimari Akış & Topoloji Şeması (Mermaid)
Mermaid şeması çiziliyor...

Adım 0 — Güvenlik kuralları (atlamayın)

Tatbikat asla üretim sunucusuna, üretim veri dizinine veya üretimin WAL arşivine dokunmaz.

KuralNeden
Tatbikat ayrı bir host, VM veya container'da yapılırYanlış -h veya -D parametresi üretimi ezebilir
Tatbikat instance'ında archive_mode = offAksi halde restore edilen instance, üretimin WAL arşivine yeni timeline yazabilir
Physical restore'da max_logical_replication_workers = 0Restore edilen subscription'lar üretimdeki publisher'a bağlanıp veri çekmeye başlayabilir
Zamanlanmış işler (ör. pg_cron) ve dış sistemlere yazan bileşenler kapalıTatbikat kopyası müşteriye e-posta atmamalı, kuyruğa mesaj basmamalı
Tatbikat ortamı üretimle aynı erişim kontrollerine tabiRestore edilen veri gerçek kişisel veridir (KVKK). Tatbikat sonunda silinir

Adım 1 — Kapsamı ve başarı kriterlerini yazın

Tatbikata başlamadan önce aşağıdakileri bir sayfaya yazın. Sonuç bu kriterlere göre "başarılı" veya "başarısız" sayılır.

AlanÖrnek
Kaynak veritabanıorders-prod (PostgreSQL 16.x)
Kullanılacak backupDünkü gece backup'ı, en yeni değil (gerçekçi senaryo)
Senaryo"Dün 14:30'da yanlış bir DELETE çalıştı, 14:29 durumuna dön"
Hedef RTO2 saat
Hedef RPO5 dakika
Başarı kriterleriInstance açılır, kritik 10 tablonun satır sayısı referansla tutarlı, uygulamanın smoke sorgusu çalışır
SorumluTatbikatı yürüten + gözlemci (iki kişi)
İpucu: İlk tatbikatta en yeni backup yerine birkaç günlük bir backup seçin. Gerçek bir olayda en yeni backup'ın da bozuk çıkma ihtimali vardır. Retention zincirinin geriye doğru çalıştığını da görmüş olursunuz.

Adım 2 — Referans değerleri üretimden alın

Doğrulama, karşılaştırılacak bir referans olmadan yapılamaz. Backup zamanına yakın bir anda (ya da tatbikat öncesinde, read replica üzerinden) şu değerleri kaydedin:

sql
-- Sürüm ve eklentiler SELECT version(); SELECT extname, extversion FROM pg_extension ORDER BY extname; -- Şema başına nesne sayıları SELECT n.nspname AS schema, c.relkind, count(*) FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT LIKE 'pg_toast%' GROUP BY 1, 2 ORDER BY 1, 2; -- Veritabanı boyutu SELECT pg_size_pretty(pg_database_size(current_database()));

Kritik tablolar için, sabit bir zaman sınırının öncesine ait satırları sayın. Böylece referans, backup alındıktan sonra yapılan yazmalardan etkilenmez:

sql
SELECT count(*) AS orders_before_cutoff FROM orders WHERE created_at < '2026-10-13 00:00:00+03';

Küçük ve kritik referans tablolarında (ör. currencies, tax_rates) içerik özeti de alın:

sql
SELECT md5(string_agg(t::text, '|' ORDER BY t.id)) AS content_hash FROM tax_rates t;

Adım 3 — İzole tatbikat ortamını hazırlayın

  • •Kaynakla aynı major sürüm (logical restore'da daha yeni bir major da kullanılabilir, physical restore'da aynı major şarttır)
  • •Kaynaktaki eklentilerin aynı sürümleri yüklü
  • •Backup boyutunun en az 2 katı boş disk (physical restore'da WAL replay için ek alan)
  • •Backup deposuna salt okunur erişim

Container ile hızlı bir tatbikat ortamı:

bash
docker run -d --name pg-restore-drill \ -e POSTGRES_PASSWORD='<güçlü-parola>' \ -p 127.0.0.1:55432:5432 \ -v /drill/data:/var/lib/postgresql/data \ postgres:16
Port yalnızca 127.0.0.1 üzerinde açılır. Tatbikat ortamı ağa açılmaz.

Saati kaydedin. RTO ölçümü burada başlar:

bash
date -Iseconds | tee /drill/rto_start.txt

Adım 4A — Logical backup (pg_dump) restore

▸4A.1 Arşivin okunabildiğini kontrol edin

bash
pg_restore --list /backups/orders-prod_2026-10-13.dump > /drill/toc.txt wc -l /drill/toc.txt

Bu komut yalnızca arşivin içindekiler tablosunu (TOC) okur. Hata vermemesi gerekir, ama restore yerine geçmez. Birçok "verification" aracı yalnızca bu adımı yapar.

▸4A.2 Global nesneleri (roller) geri yükleyin

pg_dump rolleri ve tablespace'leri içermez. Roller olmadan sahiplik ve yetki (GRANT) satırları hata verir. Global nesneler ayrıca alınmalıdır:

bash
# Üretimde, backup ile birlikte alınmış olmalı: # pg_dumpall --globals-only --no-role-passwords > globals.sql psql -h 127.0.0.1 -p 55432 -U postgres -f /backups/globals_2026-10-13.sql
İlk tatbikatlarda en sık bulgu: Global nesnelerin hiç yedeklenmediği ortaya çıkar. Bu bulgu tek başına tatbikatı değerli kılar.

▸4A.3 Veritabanını oluşturup restore edin

bash
createdb -h 127.0.0.1 -p 55432 -U postgres orders_drill pg_restore -h 127.0.0.1 -p 55432 -U postgres \ --dbname=orders_drill \ --jobs=4 \ --exit-on-error \ --verbose \ /backups/orders-prod_2026-10-13.dump 2> /drill/restore.log
  • •--jobs yalnızca custom (-Fc) ve directory (-Fd) formatlarında çalışır. Plain SQL dump'lar psql ile tek iş parçacığında yüklenir.
  • •--exit-on-error ilk hatada durur. Hatalar log'da sessizce kaybolmaz.

Restore sonrası planner istatistiklerini güncelleyin:

bash
psql -h 127.0.0.1 -p 55432 -U postgres -d orders_drill -c "ANALYZE;"

Adım 4B — Physical backup + PITR restore

▸4B.1 Backup bütünlüğünü doğrulayın

pg_basebackup ile alınmış (PostgreSQL 13+) backup'larda manifest vardır:

bash
pg_verifybackup /backups/base_2026-10-13

pg_verifybackup dosya listesini, boyutları ve checksum'ları manifest ile karşılaştırır, ardından gereken WAL'ı kontrol eder. Plain format en geniş sürüm desteğine sahiptir. Tar formatındaki backup'lar için kullandığınız sürümün belgesini kontrol edin.

pgBackRest kullanıyorsanız depo bütünlüğü için:

bash
pgbackrest --stanza=main verify
verify dosyaları doğrular, restore'u kanıtlamaz. Bir sonraki adım yine zorunludur.

▸4B.2 Tatbikat dizinine geri yükleyin

pg_basebackup (plain format) için:

bash
sudo -u postgres cp -a /backups/base_2026-10-13/. /drill/pgdata/ sudo chmod 700 /drill/pgdata

pgBackRest için (tatbikat host'unda, pg1-path tatbikat dizinini gösteren ayrı bir yapılandırmayla):

bash
pgbackrest --stanza=main \ --type=time \ --target="2026-10-13 14:29:00+03" \ --target-action=promote \ restore

▸4B.3 Recovery ayarlarını yapın (pg_basebackup yolu)

/drill/pgdata/postgresql.auto.conf dosyasına ekleyin:

ini
restore_command = 'cp /backups/wal_archive/%f %p' recovery_target_time = '2026-10-13 14:29:00+03' recovery_target_action = 'promote' # Tatbikat güvenliği archive_mode = off max_logical_replication_workers = 0 port = 55433

Recovery modunu başlatın ve instance'ı açın:

bash
sudo -u postgres touch /drill/pgdata/recovery.signal sudo -u postgres pg_ctl -D /drill/pgdata -l /drill/recovery.log start

▸4B.4 Recovery'nin bittiğini doğrulayın

sql
SELECT pg_is_in_recovery(); -- false olmalı (promote edildi) SELECT pg_last_wal_replay_lsn(); -- recovery'nin vardığı son LSN

recovery.log içinde recovery stopping before commit of transaction ... ve database system is ready to accept connections satırlarını arayın. Hedef zamana ulaşılamadıysa log, eksik WAL dosyasının adını verir. Bu da arşivde boşluk olduğunu gösteren kritik bir bulgudur.

Adım 5 — Doğrulama

Instance'ın açılması başarı değildir. Aşağıdaki kontrollerin hepsi Adım 2'deki referanslarla karşılaştırılır.

sql
-- 1. Nesne sayıları referansla aynı mı? SELECT n.nspname AS schema, c.relkind, count(*) FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT LIKE 'pg_toast%' GROUP BY 1, 2 ORDER BY 1, 2; -- 2. Eklentiler ve sürümleri aynı mı? SELECT extname, extversion FROM pg_extension ORDER BY extname; -- 3. Kritik tablolarda cutoff öncesi satır sayısı tutuyor mu? SELECT count(*) FROM orders WHERE created_at < '2026-10-13 00:00:00+03'; -- 4. Referans tablo içeriği birebir aynı mı? SELECT md5(string_agg(t::text, '|' ORDER BY t.id)) FROM tax_rates t; -- 5. Geçersiz (yarım kalmış) index var mı? SELECT indexrelid::regclass FROM pg_index WHERE NOT indisvalid;

Fiziksel bozulma kontrolü için amcheck:

bash
psql -p 55433 -d orders_drill -c "CREATE EXTENSION IF NOT EXISTS amcheck;" pg_amcheck -p 55433 -d orders_drill --heapallindexed --jobs=4

Son olarak uygulamanın gözünden test edin: uygulamanın en kritik okuma sorgusunu (ör. bir müşterinin son siparişleri) tatbikat veritabanında çalıştırın ve sonucun anlamlı olduğunu kontrol edin.

Adım 6 — RTO ve RPO'yu ölçün

bash
date -Iseconds | tee /drill/rto_end.txt
MetrikNasıl hesaplanır
Ölçülen RTOrto_end - rto_start (ortam hazırlığı dahil, uçtan uca)
Ölçülen RPOHedef zaman ile restore edilen veride bulunan en son işlem zamanı arasındaki fark

Restore edilen veride en son işlemin zamanını bulmak için:

sql
SELECT max(created_at) AS last_tx_in_restore FROM orders;

PITR'de bu değer hedef zamana çok yakın olmalıdır. Arada büyük bir fark varsa WAL arşivlemesinde gecikme veya boşluk var demektir.

Gerçekçi beklenti: İlk tatbikatta ölçülen RTO çoğu zaman hedefin üzerinde çıkar. Sebep genellikle restore'un kendisi değil; erişim izinleri, eksik roller, yanlış yol ve belgesiz adımlardır. Bunları bulmak tatbikatın asıl amacıdır.

Adım 7 — Tatbikat kaydını yazın

Aşağıdaki şablonu her tatbikatta doldurun ve ekiple paylaşın:

markdown
## Restore Tatbikatı Kaydı — <tarih> - Kaynak: orders-prod (PostgreSQL 16.x) - Backup: 2026-10-13 gece full + WAL arşivi - Senaryo: 2026-10-13 14:29 durumuna PITR - Yürüten / Gözlemci: <ad> / <ad> | Kriter | Hedef | Sonuç | Durum | |---|---|---|---| | RTO | 2 saat | 2 saat 47 dk | Başarısız | | RPO | 5 dk | 40 sn | Başarılı | | Nesne sayıları | Referansla aynı | Aynı | Başarılı | | Kritik tablo sayımları | Referansla aynı | Aynı | Başarılı | | amcheck | Hata yok | Hata yok | Başarılı | ### Bulgular ve aksiyonlar 1. Global nesneler yedeklenmiyordu → pg_dumpall --globals-only backup job'una eklendi (sorumlu, tarih) 2. Backup deposu erişim izni 50 dk sürdü → tatbikat rolü için kalıcı salt okunur erişim tanımlanacak

Adım 8 — Ortamı güvenli şekilde temizleyin

bash
sudo -u postgres pg_ctl -D /drill/pgdata stop docker rm -f pg-restore-drill sudo rm -rf /drill/pgdata /drill/data

Tatbikat ortamı gerçek kişisel veri içerir. Silme işlemini kayda ekleyin.

Sık karşılaşılan hatalar

BelirtiOlası nedenÇözüm
role "app_user" does not existGlobal nesneler restore edilmediAdım 4A.2
could not access file "$libdir/..."Eklenti tatbikat ortamında yüklü değilAynı eklenti sürümünü kurun
requested timeline ... is not a child of this server's historyYanlış timeline'a ait WAL veya hedefArşivdeki .history dosyalarını kontrol edin
could not open file ".../0000000100000000000000xx"WAL arşivinde boşlukArşivleme izlemesini kurun, archive_command hatalarını alarm yapın
Restore çok yavaş--jobs kullanılmadı, disk yavaşParalel restore, tatbikat ortamında yeterli IOPS

Sonraki adım

Kaynaklar

Topluluk Katkısı & Benchmark

Restore Tatbikatı Deneyiminizi veya Sonuçlarınızı Paylaşın

Altyapınızdaki gerçek RTO/RPO sürelerini, PostgreSQL sürümünüzü, tercih ettiğiniz yaklaşımı ve karşılaştığınız engelleri anonim veya isimli olarak paylaşın. Sektör benchmark'ına katkıda bulunun.

İlgili Karar Matrisleri ve Ekip Kılavuzları