Referensi cepat backup dan disaster recovery. Aturan 3-2-1, pg_dump, WAL archiving, object storage, RTO/RPO, restore testing, dan runbook. Perfect buat DevOps.
Backup tanpa rencana pemulihan cuma bikin kamu merasa aman, bukan benar-benar aman. Pertanyaan yang harus bisa kamu jawab bukan "apakah aku punya backup", tapi "berapa lama sampai layanan jalan lagi kalau database utama hilang besok pagi". Disaster recovery (DR) adalah disiplin menjawab pertanyaan itu dengan angka, prosedur, dan bukti bahwa prosedurnya pernah dicoba.
Dua angka kunci yang membingkai semuanya:
| Istilah | Arti | Pertanyaan kuncinya |
|---|---|---|
| RPO (Recovery Point Objective) | Maksimal data yang boleh hilang, diukur dari kejadian ke backup terakhir yang bisa dipakai | "Berapa lama data yang bisa hilang?" |
| RTO (Recovery Time Objective) | Maksimal waktu sampai layanan jalan lagi | "Berapa lama downtime yang bisa ditoleransi?" |
Contoh: RPO 15 menit dan RTO 2 jam artinya kamu siap kehilangan maksimal 15 menit data dan layanan harus hidup lagi dalam 2 jam. Angka ini menentukan arsitektur backup kamu, bukan sebaliknya. RPO 0 butuh streaming replication, bukan sekadar pg_dump malam-malam.
Aturan klasik dari fotografer Peter Krogh ini tetap jadi fondasi semua strategi backup:
| Angka | Arti | Contoh implementasi |
|---|---|---|
| 3 | Tiga salinan data (1 primer + 2 backup) | DB utama + dump di disk + dump di object storage |
| 2 | Dua media penyimpanan berbeda | NAS lokal + S3 |
| 1 | Satu salinan offsite, terpisah secara geografis dan otomatis dari primer | Bucket S3 region berbeda |
Varian modern yang makin relevan zaman ransomware:
Contoh susunan nyata untuk satu database PostgreSQL produksi:
Salinan 1 (primer) : PostgreSQL di VPS utama, dengan streaming replica
Salinan 2 (on-site) : pg_dump malaman ke disk terpisah, retensi 7 hari
Salinan 3 (offsite) : dump terenkripsi di S3 (versioning on), retensi 30 hari
Salinan immutable : WAL archive ke bucket terpisah dengan Object Lock 30 hariMusuh utama 3-2-1 bukan teknis, tapi proses: backup lokal di server yang sama dengan disk primer, kredensial S3 yang sama dipakai aplikasi dan skrip backup (kalau aplikasi kena kompromi, attacker bisa hapus bucket juga), dan backup yang tidak pernah diuji restore.
PostgreSQL menyediakan dua keluarga metode backup, dan keduanya menyelesaikan masalah berbeda.
| Aspek | Logical (pg_dump) | Physical (pg_basebackup + WAL) |
|---|---|---|
| Bentuk output | SQL atau format archive berisi schema + data | File data directory utuh + WAL |
| Ukuran | Bisa lebih kecil (hanya data yang ada) | Seukuran data directory + WAL |
| PITR (pulih ke waktu tertentu) | Tidak bisa, hanya snapshot saat dump | Bisa, selama WAL archive lengkap |
| Pindah versi mayor | Bisa (14 ke 16 aman) | Tidak, versi harus sama persis |
| Pindah arsitektur/OS | Bisa | Tidak |
| Restore granularity | Per database, per tabel | Seluruh cluster |
| Blocking | Tidak blok baca/tulis (pakai snapshot MVCC) | Perlu slot replikasi, ikan terengah-engah di WAL |
Kesimpulan praktis: pakai keduanya. Logical dump untuk migrasi, audit, dan restore granular. Physical + WAL untuk skenario bencana yang butuh PITR.
# Dump format custom (compressed, bisa parallel restore)
pg_dump -Fc -d produk -f produk_$(date +%F).dump
# Dump format directory, parallel saat dump (hanya format ini yang dukung -j)
pg_dump -Fd -j 4 -d produk -f dumpdir/
# Dump plain SQL (bisa dibaca manusia, restore pakai psql)
pg_dump -d produk -f produk.sql
# Hanya schema / hanya data
pg_dump --schema-only -d produk -f schema.sql
pg_dump --data-only -d produk -f data.sql
# Satu tabel saja
pg_dump -t transaksi -d produk -f transaksi.sql
# Jangan lupa globals (roles, tablespace): pg_dump tidak menyimpannya
pg_dumpall --globals-only > globals_$(date +%F).sqlRestore dari format custom:
# Restore parallel
pg_restore -d produk -j 4 --clean --if-exists produk_2026-09-19.dump
# Buat index setelah data masuk, lebih cepat untuk DB besar
pg_restore -d produk --section=data dump.fc
pg_restore -d produk --section=index dump.fc
# Lihat isi dump tanpa restore
pg_restore --list produk_2026-09-19.dumpCatatan penting dari dokumentasi resmi PostgreSQL:
pg_dump membuat snapshot konsisten dalam satu transaksi, jadi tidak perlu menghentikan aplikasi.pg_dump -j (parallel) hanya berlaku untuk format directory -Fd, dan hanya meng-copy data paralel; schema tetap serial.--clean --if-exists membuat restore idempotent: objek lama di-drop dulu, error drop diabaikan.pg_dumpall satu-satunya cara membackup roles dan tablespace untuk seluruh cluster.# /etc/cron.d/pg-backup, jalan tiap jam 2 pagi
0 2 * * * postgres /usr/local/bin/pg-nightly-backup.sh
# pg-nightly-backup.sh
#!/bin/bash
set -euo pipefail
BACKUP_DIR=/var/backups/postgres
STAMP=$(date +%F)
mkdir -p "$BACKUP_DIR"
pg_dump -Fc -d produk -f "$BACKUP_DIR/produk_$STAMP.dump"
pg_dumpall --globals-only > "$BACKUP_DIR/globals_$STAMP.sql"
# Enkripsi sebelum kirim (kunci age publik penerima)
sops --encrypt --age age1qyqsz... \
"$BACKUP_DIR/produk_$STAMP.dump" > "$BACKUP_DIR/produk_$STAMP.dump.enc"
# Upload offsite
aws s3 cp "$BACKUP_DIR/produk_$STAMP.dump.enc" \
s3://perusahaan-backup/postgres/produk/$STAMP.dump.enc
# Hapus lokal lebih dari 7 hari
find "$BACKUP_DIR" -name '*.dump' -mtime +7 -delete
find "$BACKUP_DIR" -name '*.dump.enc' -mtime +2 -delete
# Catat hasil agar bisa dimonitor
echo "$(date -Is) dump produk $STAMP OK" >> "$BACKUP_DIR/backup.log"WAL (Write-Ahead Log) adalah catatan semua perubahan data di PostgreSQL. Kalau kamu punya base backup (foto lengkap data directory di titik waktu tertentu) plus semua WAL setelahnya, kamu bisa memutar ulang ke titik waktu berapa pun di antara keduanya. Inilah yang disebut Point-In-Time Recovery (PITR).
Di postgresql.conf:
wal_level = replica # minimal replica untuk archiving
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/archive/%f && cp %p /var/lib/postgresql/archive/%f'
archive_timeout = 300 # paksa switch WAL tiap 5 menit walau belum penuh%p adalah path file WAL yang mau diarsip, %f nama filenya.archive_command harus mengembalikan exit code 0 hanya jika arsip benar-benar tersimpan. Kalau gagal, PostgreSQL akan mencoba lagi dan menumpuk WAL: waspadai disk penuh.archive_timeout menjaga RPO: tanpa ini, WAL hanya di-flush saat 16 MB penuh, dan database sepi bisa meninggalkan data segar hanya di memori.WAL archive harus punya retensi lebih panjang dari base backup terbaru plus margin, kalau tidak PITR tidak bisa jalan. Contoh pola mingguan: base backup tiap Minggu, semua WAL disimpan 10 hari.
Pindahkan arsip ke object storage bisa langsung dari archive_command:
archive_command = 'aws s3 cp %p s3://perusahaan-backup/wal/%f'Untuk produksi serius, pgBackRest atau WAL-G lebih cocok: mereka menangani kompresi, paralel, enkripsi, retensi otomatis, dan verifikasi checksum.
# pg_basebackup: butuh user dengan permission REPLICATION
pg_basebackup -D /var/backups/postgres/base -X stream -P --checkpoint=fast
# Kirim langsung terkompresi ke arsip
pg_basebackup -D - -Ft -X stream | zstd | \
aws s3 cp - s3://perusahaan-backup/base/base_$(date +%F).tar.zst-X stream memastikan WAL selama backup ikut terkumpul, backup jadi konsisten sendiri.pg_verifybackup /var/backups/postgres/base.pgbackrest --stanza=produk backup --type=full untuk full, --type=diff atau --type=incr untuk inkremental.Skenario: sore tadi jam 14.30 ada DROP TABLE transaksi yang tidak disengaja. Kamu ingin pulih ke kondisi tepat sebelum perintah itu jalan.
# 1. Matikan PostgreSQL yang rusak, simpan datanya untuk forensik
sudo systemctl stop postgresql
# 2. Siapkan cluster baru dari base backup terakhir SEBELUM 14.30
rm -rf /var/lib/postgresql/16/main
pg_basebackup -D /var/lib/postgresql/16/main \
--pgdata=- ... # atau: tarik dari arsip pgBackRest / bucket
# 3. Pastikan WAL archive lengkap tersedia lokal
# (misal rsync dari S3 ke /var/lib/postgresql/archive)
# 4. Bersihkan konfigurasi yang bisa mengganggu
rm /var/lib/postgresql/16/main/standby.signal # kalau adaDi postgresql.auto.conf (atau recovery.conf di versi 11 ke bawah):
restore_command = 'cp /var/lib/postgresql/archive/%f %p'
recovery_target_time = '2026-09-19 14:29:00+07'
recovery_target_action = 'promote'# 5. Signal bahwa ini recovery
touch /var/lib/postgresql/16/main/recovery.signal
# 6. Start, PostgreSQL akan memutar WAL sampai target lalu promote
sudo systemctl start postgresql
# 7. Cek log: "recovery stopping before commit of transaction..."
# Verifikasi tabel transaksi masih ada datanyaKalau recovery_target_time meleset, kamu bisa mengulang dari base backup dengan target waktu berbeda. Karena itu penting menandai waktu kejadian dengan presisi sejak insiden dilapor.
Object storage (S3, Cloudflare R2, Backblaze B2, Google Cloud Storage) adalah pilihan paling umum untuk salinan offsite: awet, murah di cold tier, dan API-nya terstandar.
| Fitur | Kenapa penting |
|---|---|
| Versioning | Backup yang tertimpa atau dihapus masih bisa dikembalikan |
| Object Lock / immutability | Ransomware tidak bisa menghapus versi lama, bahkan dengan kredensial admin |
| Encryption at rest (SSE) | Data diam terenkripsi tanpa usaha tambahan |
| Lifecycle rules | Pindahkan backup lama ke tier murah (Glacier) otomatis |
| Cross-region replication | Salinan kedua di region lain untuk skenario region down |
| MFA Delete | Hapus versi butuh MFA, menambah rem buat manusia error |
# Aktifkan versioning
aws s3api put-bucket-versioning --bucket perusahaan-backup \
--versioning-configuration Status=Enabled
# Retensi Object Lock mode governance 30 hari (compliance kalau mau lebih keras)
aws s3api put-object-retention --bucket perusahaan-backup \
--key postgres/produk/2026-09-19.dump.enc \
--retention '{"Mode":"GOVERNANCE","RetainUntilDate":"2026-10-19T00:00:00Z"}'
# Lifecycle: backup 30 hari lalu pindah ke Glacier
aws s3api put-bucket-lifecycle-configuration \
--bucket perusahaan-backup \
--lifecycle-configuration file://lifecycle.json{
"Rules": [
{
"ID": "archive-old-backups",
"Status": "Enabled",
"Filter": { "Prefix": "postgres/" },
"Transitions": [
{ "Days": 30, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 365 }
}
]
}Skrip backup sebaiknya pakai IAM user atau role terpisah yang hanya bisa s3:PutObject ke bucket backup, tidak bisa DeleteObject atau DeleteObjectVersion. Kalau server aplikasi kena kompromi, attacker tidak bisa membersihkan jejak dengan menghapus backup. Ini bagian dari least privilege di cheat sheet /cheat-sheets/secrets-management-devops.
Bukan semua bencana sama. Memetakan skenario membuat angka RTO/RPO kamu realistis, bukan angan-angan.
| Skenario | Metode pemulihan | RPO tipikal | RTO tipikal |
|---|---|---|---|
| Tabel kehapus (DROP/DELETE) | PITR ke menit sebelumnya | 0-5 menit | 30-60 menit |
| Disk server mati | Restore base backup + WAL ke mesin baru | 5-15 menit | 1-3 jam |
| Corrupt transaksi aplikasi | PITR ke sebelum deploy buruk | 0 (rollback penuh) | 1-4 jam |
| Region cloud down | Failover ke replika di region lain, atau restore dari cross-region bucket | 0-5 menit (replika) | 1-8 jam |
| Ransomware | Restore dari backup immutable terbaru yang bersih | Tergantung retensi lock | 4-24 jam |
| Kredensial bocor | Bukan masalah backup: rotasi kredensial | N/A | < 1 jam |
Perhatikan baris terakhir: tidak semua insiden diselesaikan dengan restore. Kebocoran kredensial butuh rotasi, dan itu dibahas di cheat sheet secrets management.
Aturan paling keras di dunia backup: backup yang tidak pernah di-restore ke mesin lain itu belum terbukti. Skrip backup bisa sukses tiap malam sementara dump-nya korup semua.
#!/bin/bash
# restore-drill.sh: restore dump terbaru ke container sekali pakai, verifikasi, laporkan
set -euo pipefail
STAMP=$(date +%F-%H%M)
CONTAINER=drill-$STAMP
# Tarik dump terbaru dari bucket
LATEST=$(aws s3 ls s3://perusahaan-backup/postgres/produk/ --recursive \
| sort | tail -1 | awk '{print $4}')
aws s3 cp "s3://perusahaan-backup/$LATEST" /tmp/drill.dump
# Dekripsi
sops --decrypt /tmp/drill.dump > /tmp/drill.dump.plain
# Jalankan Postgres sekali pakai dan restore
docker run -d --name "$CONTAINER" -e POSTGRES_PASSWORD=drill \
-p 55432:5432 postgres:16
sleep 8
until docker exec "$CONTAINER" pg_isready -U postgres; do sleep 2; done
pg_restore -h localhost -p 55432 -U postgres -d postgres \
--clean --if-exists -j 4 /tmp/drill.dump.plain
# Verifikasi: hitung baris tabel kunci, cocokkan dengan angka harapan
ROW_COUNT=$(docker exec "$CONTAINER" psql -U postgres -d postgres -tAc \
"SELECT count(*) FROM transaksi")
if [ "$ROW_COUNT" -lt 1000000 ]; then
echo "FAIL: transaksi cuma $ROW_COUNT baris, mestinya jutaan"
exit 1
fi
echo "$(date -Is) DRILL PASS: $LATEST restore OK, transaksi=$ROW_COUNT"
docker rm -f "$CONTAINER"
rm /tmp/drill.dump*Jalankan via cron mingguan dan kirim notifikasi kalau gagal. Yang kamu ukur dari tiap drill:
Catat hasil drill. Kalau target RTO 2 jam tapi drill selalu 4 jam, kamu punya dua pilihan: perbaiki prosesnya atau jujur revisi targetnya.
Runbook adalah resep langkah demi langkah yang bisa dijalankan orang yang panik jam 3 pagi. Cirinya: konkret, bernomor, tanpa keputusan yang harus dipikirkan saat itu.
1. Trigger : gejala apa yang memanggil runbook ini
2. Impact : apa yang terkena, estimasi user affected
3. Pre-checks : cara konfirmasi diagnosis (perintah + output yang diharapkan)
4. Keputusan : siapa berwenang mendeklarasikan disaster
5. Langkah restore: perintah konkret, urut, dengan estimasi waktu tiap langkah
6. Verifikasi : query/endpoint apa yang harus hijau sebelum dinyatakan pulih
7. Komunikasi : siapa yang mengabarkan user, status page, channel mana
8. Post-mortem : kapan, siapa, apa template laporannyaRUNBOOK: PITR PostgreSQL Produksi (RB-DR-001)
Terakhir direvisi: 2026-09-19 | Owner: tim infra | Terakhir drill: 2026-09-05 (PASS, 47 menit)
TRIGGER
- Data hilang setelah migrasi/deploy (tabel kosong, row count anjlok)
- DBA melaporkan transaksi rusak yang tidak bisa diperbaiki aplikasi
PRE-CHECKS (5 menit)
1. catatan waktu kejadian: kapan persisnya perintah buruk jalan?
grep "DROP TABLE" /var/log/postgresql/*.log
2. Konfirmasi arsip WAL lengkap:
aws s3 ls s3://perusahaan-backup/wal/ | tail -20
3. Konfirmasi base backup terakhir SEBELUM kejadian ada:
aws s3 ls s3://perusahaan-backup/base/
LANGKAH (estimasi total 45-60 menit)
1. Deklarasikan incident, matikan write di aplikasi (mode maintenance)
2. stop postgresql, arsipkan data directory lama untuk forensik
3. Tarik base backup + WAL sesuai periode
4. Konfigurasikan recovery_target_time = 1 menit sebelum kejadian
5. touch recovery.signal, start postgresql
6. Pantau log sampai "database system is ready to accept connections"
VERIFIKASI
- psql: SELECT count(*) FROM transaksi; cocok dengan snapshot harian
- Aplikasi: smoke test transaksi kecil end-to-end
- Monitor error rate 30 menit pertama
ESKALASI
- Kalau WAL gap (arsip kurang): fallback ke dump malaman terakhir,
laporkan RPO riil ke incident commanderRunbook yang tidak pernah dijalankan di drill akan berhenti jadi benar dalam hitungan bulan: path berubah, kredensial kedaluwarsa, ukuran data membuat estimasi meleset. Jadwalkan drill tiap kuartal dan update tanggal "terakhir drill" di header runbook.
Backup adalah sistem produksi juga. Yang perlu kamu pantau:
Pola sederhana yang bisa dipakai: job backup menulis metrik ke file atau push gateway, dan alerting (misal cron check atau Prometheus) menyala kalau metrik berhenti update. Backup yang gagal diam-diam adalah mode kegagalan terburuk di area ini.
| Tools | Tipe | PITR | Incremental | Enkripsi bawaan | Cocok untuk |
|---|---|---|---|---|---|
| pg_dump / pg_restore | Logical | Tidak | Tidak | Tidak (gabung age/gpg) | Dump rutin, migrasi versi |
| pg_basebackup + archive_command | Physical | Ya | Tidak (full saja) | Tidak | Setup sederhana, belajar PITR |
| pgBackRest | Physical | Ya | Full/diff/incr | Ya | Produksi serius, repo lokal atau S3 |
| WAL-G | Physical | Ya | Ya | Ya | Alternatif pgBackRest, delta restore |
| Barman | Physical | Ya | Ya | Ya | Pengelolaan banyak server PG |
Untuk database kecil sampai menengah (puluhan GB), pg_dump malaman ke S3 plus WAL archiving sederhana sering sudah cukup dan paling gampang dipahami. Di atas itu, atau begitu PITR jadi kebutuhan riil, pgBackRest layak dipelajari sejak awal.
| Istilah | Arti |
|---|---|
| Backup | Salinan data yang disimpan terpisah untuk pemulihan saat data primer rusak |
| Disaster recovery (DR) | Rencana dan prosedur memulihkan layanan setelah bencana |
| RTO | Recovery Time Objective, batas maksimal waktu sampai layanan jalan lagi |
| RPO | Recovery Point Objective, batas maksimal data yang boleh hilang, diukur waktu |
| PITR | Point-In-Time Recovery, memulihkan database ke titik waktu tertentu |
| WAL | Write-Ahead Log, catatan perubahan data PostgreSQL sebelum ditulis ke disk |
| Base backup | Salinan lengkap data directory PostgreSQL jadi fondasi PITR |
| Logical backup | Backup berisi schema dan data dalam bentuk SQL atau format archive |
| Physical backup | Backup berupa file data directory dan WAL apa adanya |
| 3-2-1 rule | Tiga salinan, dua media, satu offsite |
| Immutable backup | Salinan yang tidak bisa diubah atau dihapus selama periode retensi |
| Object Lock | Fitur S3 untuk mengunci versi objek agar tidak terhapus |
| Versioning | Fitur penyimpanan yang menyimpan semua versi objek, bukan hanya terbaru |
| Retention | Lama backup disimpan sebelum dihapus otomatis |
| Lifecycle rule | Aturan otomatis memindahkan atau menghapus objek berdasarkan umur |
| Restore drill | Latihan memulihkan backup ke lingkungan terpisah untuk membuktikan jalan |
| Runbook | Dokumen langkah demi langkah untuk menjalankan prosedur operasi |
| Failover | Pindah otomatis atau manual ke sistem cadangan saat primer gagal |
| Streaming replication | Replikasi real-time dengan mengalirkan WAL ke server standby |
| Archive command | Direktif PostgreSQL yang menentukan cara menyalin WAL ke arsip |
| Recovery signal | File recovery.signal yang memberi tahu PostgreSQL untuk masuk mode pemulihan |
| Checksum | Nilai hash untuk mendeteksi korupsi data pada backup |
| Post-mortem | Laporan setelah insiden: kronologi, akar masalah, tindakan pencegahan |
Login atau daftar akun gratis untuk membaca cheat sheet ini.