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.
Bu runbook kimin için?
- •Backup'ı düzenli alan ama hiç restore etmemiş ekipler
- •
pg_dump,pg_basebackupveya 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ışı
Adım 0 — Güvenlik kuralları (atlamayın)
Tatbikat asla üretim sunucusuna, üretim veri dizinine veya üretimin WAL arşivine dokunmaz.
| Kural | Neden |
|---|---|
| Tatbikat ayrı bir host, VM veya container'da yapılır | Yanlış -h veya -D parametresi üretimi ezebilir |
Tatbikat instance'ında archive_mode = off | Aksi halde restore edilen instance, üretimin WAL arşivine yeni timeline yazabilir |
Physical restore'da max_logical_replication_workers = 0 | Restore 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 tabi | Restore 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 backup | Dü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 RTO | 2 saat |
| Hedef RPO | 5 dakika |
| Başarı kriterleri | Instance açılır, kritik 10 tablonun satır sayısı referansla tutarlı, uygulamanın smoke sorgusu çalışır |
| Sorumlu | Tatbikatı yürüten + gözlemci (iki kişi) |
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:
-- 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:
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:
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ı:
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:16127.0.0.1 üzerinde açılır. Tatbikat ortamı ağa açılmaz.Saati kaydedin. RTO ölçümü burada başlar:
date -Iseconds | tee /drill/rto_start.txtAdım 4A — Logical backup (pg_dump) restore
▸4A.1 Arşivin okunabildiğini kontrol edin
pg_restore --list /backups/orders-prod_2026-10-13.dump > /drill/toc.txt
wc -l /drill/toc.txtBu 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:
# Ü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▸4A.3 Veritabanını oluşturup restore edin
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- •
--jobsyalnızca custom (-Fc) ve directory (-Fd) formatlarında çalışır. Plain SQL dump'larpsqlile tek iş parçacığında yüklenir. - •
--exit-on-errorilk hatada durur. Hatalar log'da sessizce kaybolmaz.
Restore sonrası planner istatistiklerini güncelleyin:
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:
pg_verifybackup /backups/base_2026-10-13pg_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:
pgbackrest --stanza=main verifyverify 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:
sudo -u postgres cp -a /backups/base_2026-10-13/. /drill/pgdata/
sudo chmod 700 /drill/pgdatapgBackRest için (tatbikat host'unda, pg1-path tatbikat dizinini gösteren ayrı bir yapılandırmayla):
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:
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 = 55433Recovery modunu başlatın ve instance'ı açın:
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
SELECT pg_is_in_recovery(); -- false olmalı (promote edildi)
SELECT pg_last_wal_replay_lsn(); -- recovery'nin vardığı son LSNrecovery.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.
-- 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:
psql -p 55433 -d orders_drill -c "CREATE EXTENSION IF NOT EXISTS amcheck;"
pg_amcheck -p 55433 -d orders_drill --heapallindexed --jobs=4Son 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
date -Iseconds | tee /drill/rto_end.txt| Metrik | Nasıl hesaplanır |
|---|---|
| Ölçülen RTO | rto_end - rto_start (ortam hazırlığı dahil, uçtan uca) |
| Ölçülen RPO | Hedef 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:
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.
Adım 7 — Tatbikat kaydını yazın
Aşağıdaki şablonu her tatbikatta doldurun ve ekiple paylaşın:
## 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ımlanacakAdım 8 — Ortamı güvenli şekilde temizleyin
sudo -u postgres pg_ctl -D /drill/pgdata stop
docker rm -f pg-restore-drill
sudo rm -rf /drill/pgdata /drill/dataTatbikat ortamı gerçek kişisel veri içerir. Silme işlemini kayda ekleyin.
Sık karşılaşılan hatalar
| Belirti | Olası neden | Çözüm |
|---|---|---|
role "app_user" does not exist | Global nesneler restore edilmedi | Adım 4A.2 |
could not access file "$libdir/..." | Eklenti tatbikat ortamında yüklü değil | Aynı eklenti sürümünü kurun |
requested timeline ... is not a child of this server's history | Yanlış timeline'a ait WAL veya hedef | Arşivdeki .history dosyalarını kontrol edin |
could not open file ".../0000000100000000000000xx" | WAL arşivinde boşluk | Arş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
- •İlk tatbikattan sonra sıklığı belirleyin: kritik sistemlerde aylık, diğerlerinde çeyreklik.
- •Hangi backup yaklaşımının ortamınıza uygun olduğunu değerlendirmek için: PostgreSQL Backup Yaklaşımını Nasıl Seçmelisiniz? (dataxtools.com)
- •Tatbikatı ekip süreci haline getirmek için kontrol listesi ve şablonlar: Backup Başarısından Kurtarma Hazırlığına (dataopsphere.com)
Kaynaklar
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.
PostgreSQL Backup Yaklaşımını Nasıl Seçmelisiniz?
pg_dump, pg_basebackup, pgBackRest, Barman ve WAL-G: veri boyutu, RPO/RTO, PITR ve depolama gereksinimlerine göre araç seçim kılavuzu.
Backup Başarısından Kurtarma Hazırlığına: Restore Tatbikatı
Teknik tatbikatı ekip pratiğine dönüştürme: Kurtarma olgunluk seviyeleri, RACI rolleri, öncesi/sonrası kontrol listeleri ve yönetim rapor şablonu.