Backup yang baik bukan satu perintah besar sekali setahun, melainkan rutinitas kecil yang berjalan sendiri setiap malam, bertahun-tahun, tanpa ditemani siapa pun. Persoalannya: rutinitas tanpa pengawas punya cara tersendiri untuk gagal secara senyap — disk penuh karena backup lama tidak pernah dibersihkan, job pukul 02:00 mati diam-diam sejak Senin, atau backup harian menumpuk di satu server yang sama dengan data produksinya. Chapter ini menyusun jawaban operasionalnya: strategi berlapis yang menggabungkan alat-alat dari chapter-chapter sebelumnya (pg_dump, pg_basebackup, arsip WAL), rotasi retensi yang disiplin, pemantauan yang memberi alarm ketika rutinitas berhenti, dan jadwal cron yang menjahit semuanya — sebelum kita membuktikan di chapter terakhir bahwa seluruh bangunan itu benar-benar bisa dipulihkan.
Strategi Berlapis #
Tidak ada satu alat backup yang menjawab semua kebutuhan pemulihan. Praktik yang mapan menggabungkan empat lapisan dengan frekuensi dan tujuan berbeda:
| Lapisan | Alat | Frekuensi | Menjawab skenario |
|---|---|---|---|
| Backup logis | pg_dump -Fc | harian | pulihkan satu database / sebagian objek dengan cepat |
| Base backup | pg_basebackup -Xs | mingguan | seluruh cluster rusak — bangun ulang server lengkap |
| Arsip WAL | archive_mode + archive_command | terus-menerus | kembali ke menit tertentu (PITR, chapter sebelumnya) |
| Uji restore | drill terjadwal | bulanan/kuartalan | membuktikan tiga lapisan di atas benar-benar bekerja |
Lapisan-lapisan itu saling menutupi kelemahan: pg_dump cepat dipulihkan tetapi tidak menghasil history; base backup menyeluruh tetapi berat diambil tiap hari; arsip WAL melengkapi keduanya hingga menit terakhir; dan tanpa lapisan keempat, Anda hanya punya keyakinan — bukan bukti.
Membangun Simulasi Jadwal di Lab #
Contoh-contoh chapter ini membangun simulasi yang bisa dijalankan ulang: satu dump nyata dari database latihan, ditambah artefak historis ber-stempel waktu tetap. Data ujinya dibuat dulu:
-- Data tetap untuk backup harian latihan.
CREATE TABLE pelanggan (
id int PRIMARY KEY,
nama text NOT NULL,
email text UNIQUE
);
CREATE TABLE pesanan (
id int PRIMARY KEY,
pelanggan_id int REFERENCES pelanggan(id),
total numeric(12,2) NOT NULL
);
INSERT INTO pelanggan VALUES
(1, 'Budi Santoso', '[email protected]'),
(2, 'Siti Aminah', '[email protected]'),
(3, 'Toko Maju Jaya', '[email protected]');
INSERT INTO pesanan VALUES
(101, 1, 250000.00),
(102, 2, 125000.50),
(103, 1, 99000.00);Output yang Dihasilkan:
CREATE TABLE
CREATE TABLE
INSERT 0 3
INSERT 0 3Skrip berikut membangun pohon artefak backup di /tmp/pgtutor-jadwal — satu dump format custom hasil pg_dump nyata untuk “hari ini”, plus enam artefak historis untuk keperluan rotasi:
#!/usr/bin/env bash
# Menyiapkan pohon artefak backup untuk simulasi jadwal dan retensi.
set -euo pipefail
SUMBER=/tmp/pgtutor-jadwal
# Selalu mulai bersih agar skrip bisa dijalankan berulang.
rm -rf "$SUMBER"
mkdir -p "$SUMBER/harian" "$SUMBER/mingguan"
# Backup "hari ini" (2026-10-12): dump nyata format custom
# dari database latihan chapter ini ($PGDATABASE diarahkan harness).
pg_dump -Fc -f "$SUMBER/harian/harian-2026-10-12.dump" "$PGDATABASE"
# Supaya latihan dapat direproduksi, stempel waktu diseragamkan
# ke tanggal tetap -- di produksi, mtime-nya adalah waktu job berjalan.
touch -t 202610120200 "$SUMBER/harian/harian-2026-10-12.dump"
# Artefak historis (isi placeholder, tanggal tetap):
# harian: 3 dump lama; mingguan: 3 dump mingguan.
printf 'placeholder dump\n' > "$SUMBER/harian/harian-2026-10-05.dump"
touch -t 202610050200 "$SUMBER/harian/harian-2026-10-05.dump"
printf 'placeholder dump\n' > "$SUMBER/harian/harian-2026-09-28.dump"
touch -t 202609280200 "$SUMBER/harian/harian-2026-09-28.dump"
printf 'placeholder dump\n' > "$SUMBER/harian/harian-2026-09-14.dump"
touch -t 202609140200 "$SUMBER/harian/harian-2026-09-14.dump"
printf 'placeholder dump mingguan\n' > "$SUMBER/mingguan/mingguan-2026-10-04.dump"
touch -t 202610040200 "$SUMBER/mingguan/mingguan-2026-10-04.dump"
printf 'placeholder dump mingguan\n' > "$SUMBER/mingguan/mingguan-2026-09-06.dump"
touch -t 202609060200 "$SUMBER/mingguan/mingguan-2026-09-06.dump"
printf 'placeholder dump mingguan\n' > "$SUMBER/mingguan/mingguan-2026-08-10.dump"
touch -t 202608100200 "$SUMBER/mingguan/mingguan-2026-08-10.dump"
# Inventaris lengkap sebelum rotasi dijalankan.
find "$SUMBER" -name '*.dump' -printf '%TY-%Tm-%Td %TH:%TM %P\n' | sortOutput yang Dihasilkan:
2026-08-10 02:00 mingguan/mingguan-2026-08-10.dump
2026-09-06 02:00 mingguan/mingguan-2026-09-06.dump
2026-09-14 02:00 harian/harian-2026-09-14.dump
2026-09-28 02:00 harian/harian-2026-09-28.dump
2026-10-04 02:00 mingguan/mingguan-2026-10-04.dump
2026-10-05 02:00 harian/harian-2026-10-05.dump
2026-10-12 02:00 harian/harian-2026-10-12.dumpTiga teknik di dalamnya layak dicatat. Pertama, pg_dump -Fc -f ... "$PGDATABASE" adalah bentuk persis job harian produksi: format custom siap pg_restore, target file bernama tanggal, database diambil dari variabel lingkungan. Kedua, touch -t 202610120200 menyetel mtime ke tanggal-jam tetap — inilah adaptasi determinisme lab: output latihan tidak berubah apa pun tanggal Anda menjalankannya. Ketiga, find -printf '%TY-%Tm-%Td %TH:%TM %P\n' | sort mencetak inventaris terurut — pola yang sama dipakai monitoring di bagian berikutnya.
Rotasi Retensi ala GFS #
Retensi yang lazim mengikuti pola grandfather-father-son: harian (son) disimpan 14 hari, mingguan (father) 8 minggu, dan bulanan (grandfather) 12 bulan — cukup untuk memulihkan kesalahan yang baru sadar terjadi berminggu-minggu kemudian, tanpa menumpuk puluhan ribu file. Skrip berikut menjalankan rotasi atas simulasi kita:
#!/usr/bin/env bash
# Rotasi retensi: harian disimpan 14 hari, mingguan disimpan 8 minggu.
set -euo pipefail
SUMBER=/tmp/pgtutor-jadwal
# Simulasi "hari ini" = 2026-10-12, jadi batas harian = 2026-09-28.
# Di produksi bentuknya relatif: find ... -mtime +14 -delete.
# Di lab ini kita pakai tanggal referensi tetap agar output dapat
# direproduksi apa pun tanggal sesungguhnya saat skrip dijalankan.
echo "menghapus backup harian lebih tua dari 2026-09-28:"
find "$SUMBER/harian" -name '*.dump' ! -newermt '2026-09-28' -print -delete
# Batas mingguan = 8 minggu sebelum "hari ini" = 2026-08-17.
echo "menghapus backup mingguan lebih tua dari 2026-08-17:"
find "$SUMBER/mingguan" -name '*.dump' ! -newermt '2026-08-17' -print -delete
echo "sisa artefak setelah rotasi:"
find "$SUMBER" -name '*.dump' -printf '%TY-%Tm-%Td %TH:%TM %P\n' | sortOutput yang Dihasilkan:
menghapus backup harian lebih tua dari 2026-09-28:
/tmp/pgtutor-jadwal/harian/harian-2026-09-14.dump
menghapus backup mingguan lebih tua dari 2026-08-17:
/tmp/pgtutor-jadwal/mingguan/mingguan-2026-08-10.dump
sisa artefak setelah rotasi:
2026-09-06 02:00 mingguan/mingguan-2026-09-06.dump
2026-09-28 02:00 harian/harian-2026-09-28.dump
2026-10-04 02:00 mingguan/mingguan-2026-10-04.dump
2026-10-05 02:00 harian/harian-2026-10-05.dump
2026-10-12 02:00 harian/harian-2026-10-12.dumpDua file hilang: harian-2026-09-14 dan mingguan-2026-08-10. Perhatikan bahwa harian-2026-09-28 selamat — pembanding ! -newermt '2026-09-28' menghitung usia dari tengah malam tanggal itu, dan file pukul 02:00 masih tergolong “lebih baru”. Di produksi, padanan relatifnya adalah find /var/backup/db -name 'harian-*.dump' -mtime +14 -delete: “lebih tua dari 14 hari dihitung dari sekarang” — pola yang disebut di tabel cron di bawah.
Memantau Keberhasilan Backup #
Backup gagal secara senyap lebih berbahaya daripada server lambat — Anda baru tahu enam bulan kemudian, saat restore dibutuhkan. Dua pemeriksaan murah menutup celah itu. Yang pertama: apakah masih ada backup segar?
#!/usr/bin/env bash
# Memantau kesegaran backup: harus ada backup baru dalam 1x24 jam.
set -euo pipefail
SUMBER=/tmp/pgtutor-jadwal
# Backup terbaru = mtime paling besar. Simulasi "hari ini" = 2026-10-12.
TERBARU=$(find "$SUMBER" -name '*.dump' -printf '%T@ %P\n' | sort -n | tail -1 | cut -d' ' -f2-)
echo "backup terbaru: $TERBARU"
# Segar = dibuat setelah batas 1 hari (2026-10-11 02:00).
if [ -n "$(find "$SUMBER" -name '*.dump' -newermt '2026-10-11 02:00')" ]; then
echo "STATUS: OK - masih ada backup dalam batas 1 hari"
else
echo "STATUS: BAHAYA - tidak ada backup lebih baru dari 1 hari" >&2
exit 1
fiOutput yang Dihasilkan:
backup terbaru: harian/harian-2026-10-12.dump
STATUS: OK - masih ada backup dalam batas 1 hariPemeriksaan kedua: apakah backup itu berisi? File berukuran nol adalah wujud klasik job yang “sukses” menulis file kosong — misalnya karena dump gagal setengah jalan namun skrip tetap menutup file. Skrip berikut sengaja memfasilitasi insiden itu, lalu gagal dengan suara:
#!/usr/bin/env bash
# Pola alarm: backup kosong harus membuat job gagal keras (exit != 0),
# bukan diam-diam "sukses".
set -euo pipefail
SUMBER=/tmp/pgtutor-jadwal
# Simulasi insiden: job backup tadi malam menghasilkan file kosong.
printf '' > "$SUMBER/harian/harian-2026-10-12.dump"
# Kumpulkan semua backup berukuran 0 byte.
KOSONG=$(find "$SUMBER" -name '*.dump' -size 0 -printf '%P\n' | sort)
if [ -n "$KOSONG" ]; then
echo "BAHAYA: ada backup berukuran 0 byte:" >&2
printf '%s\n' "$KOSONG" >&2
rm -rf "$SUMBER"
exit 1
fiOutput yang Dihasilkan:
BAHAYA: ada backup berukuran 0 byte:
harian/harian-2026-10-12.dumpDua keputusan desain di skrip itu penting. Pesan alarm ditulis ke stderr dan skrip ditutup dengan exit 1 — sehingga siapa pun yang menjalankan job (cron, systemd timer, CI) melihat kegagalan, bukan baris “OK” palsu. Dan hasil find ditampung dulu ke variabel KOSONG, bukan dipipakan ke grep -q, supaya find selalu selesai dibaca — pola yang sama menghindari kejutan pipefail di skrip Chapter sebelumnya. Di produksi, angka minimum yang lebih realistis adalah -size -100k: dump database sungguhan di bawah 100 KB hampir pasti tanda salah sasaran.
Menjadwalkan dengan cron #
Penjahit klasik seluruh rutinitas ini adalah cron. Bentuk lengkapnya — sebagai contoh terdokumentasi, dengan YOUR_DATABASE sebagai penampung:
# crontab -e (jalankan sebagai user yang berhak backup)
[email protected]
# 1. Backup logis harian pukul 02:00
0 2 * * * pg_dump -Fc -f /var/backup/db/harian-$(date +\%F).dump YOUR_DATABASE
# 2. Base backup mingguan, Minggu pukul 03:00
0 3 * * 0 pg_basebackup -D /var/backup/db/base-$(date +\%F) -Fp -Xs --checkpoint=spread
# 3. Pembersihan retensi harian, Senin pukul 04:00
0 4 * * 1 find /var/backup/db -name 'harian-*.dump' -mtime +14 -deleteTiga hal di contoh itu sering menjatuhkan pemula. Pertama, tanda % pada $(date +\%F) wajib di-escape dengan backslash — % adalah karakter khusus cron untuk baris baru; tanpa escape, perintah terpotong aneh. Kedua, MAILTO membuat cron mengirim email setiap kali job menghasilkan output atau exit code bukan nol — pasangan sempurna dengan pola exit 1 di skrip alarm di atas. Ketiga, jadwal menghindari jam sibuk: pukul 02:00–04:00 memberi ruang bagi checkpoint spread dan --max-rate agar backup tidak berebut I/O dengan beban siang.
Kompresi Backup #
Ukuran adalah uang dalam urusan backup, dan PostgreSQL 17 memberi tiga tuas kompresi. Format custom pg_dump -Fc sudah mengompresi secara bawaan dengan zlib; tingkatnya bisa diatur sampai -Z 9, atau dimatikan dengan -Z 0 (berguna bila dump hendak dikompresi oleh pipeline lain). Sejak PostgreSQL 17, opsi -Z/--compress menerima metode dan detail: --compress=zstd:19 untuk rasio terbaik dengan kecepatan dekompresi yang tetap tinggi, atau --compress=lz4 untuk kompresi cepat. Di sisi physical backup, pg_basebackup --compress=client-zstd mengompresi aliran backup di sisi klien — sangat berguna saat tujuan backup dipisahkan jaringan dari server. Pilihan metode selalu kompromi CPU versus ukuran; ukur dulu dengan satu dataset sampel sebelum mengunci kebijakan.
Jebakan Penjadwalan yang Sering Terjadi #
| Jebakan | Gejala | Pencegahan |
|---|---|---|
| Backup di jam sibuk | latensi query melonjak tiap backup berjalan | jendela malam, --max-rate, checkpoint spread |
| Job gagal senyap | tidak ada email, tidak ada alarm | exit != 0 + MAILTO + monitoring kesegaran |
| Retensi tak pernah jalan | disk penuh, server berhenti total | job cleanup terjadwal + alert penggunaan disk |
| Backup dan data satu disk | kehilangan disk = kehilangan keduanya | aturan 3-2-1, salinan off-site |
| Tanpa uji restore | kepercayaan tanpa bukti | drill berkala — chapter berikutnya |
Salinan Keluar Site: Aturan 3-2-1 #
Seluruh jadwal di atas mengelola salinan di satu tempat saja. Risiko yang tersisa bersifat fisik: server terbakar, ransomware mengenkripsi seluruh volume, atau admin menghapus folder yang salah. Aturan 3-2-1 — tiga salinan, dua media berbeda, satu di luar site — menutup risiko itu: rsync/rclone berkala ke storage objek atau kantor kedua, dengan enkripsi sisi klien sebelum data meninggalkan server. Konsep RPO dan RTO yang menentukan seberapa sering salinan itu disinkronkan sudah kita bahas di chapter Konsep Backup
.
Rutinitas yang terjadwal, berretensi, terpantau, dan tersalin keluar — semua kondisi itu punya satu kelemahan bersama: semuanya bisa tampak hijau di monitoring padahal restore-nya gagal. Chapter penutup section ini mengerjakan persis bagian yang sering dilupakan: membuktikan secara rutin bahwa prosedur recovery Anda benar-benar bekerja.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
alarm-kosong.sh
Unduh berkas#!/usr/bin/env bash
# Pola alarm: backup kosong harus membuat job gagal keras (exit != 0),
# bukan diam-diam "sukses".
set -euo pipefail
SUMBER=/tmp/pgtutor-jadwal
# Simulasi insiden: job backup tadi malam menghasilkan file kosong.
printf '' > "$SUMBER/harian/harian-2026-10-12.dump"
# Kumpulkan semua backup berukuran 0 byte.
KOSONG=$(find "$SUMBER" -name '*.dump' -size 0 -printf '%P\n' | sort)
if [ -n "$KOSONG" ]; then
echo "BAHAYA: ada backup berukuran 0 byte:" >&2
printf '%s\n' "$KOSONG" >&2
rm -rf "$SUMBER"
exit 1
fi
data-uji.sql
Unduh berkas-- Data tetap untuk backup harian latihan.
CREATE TABLE pelanggan (
id int PRIMARY KEY,
nama text NOT NULL,
email text UNIQUE
);
CREATE TABLE pesanan (
id int PRIMARY KEY,
pelanggan_id int REFERENCES pelanggan(id),
total numeric(12,2) NOT NULL
);
INSERT INTO pelanggan VALUES
(1, 'Budi Santoso', '[email protected]'),
(2, 'Siti Aminah', '[email protected]'),
(3, 'Toko Maju Jaya', '[email protected]');
INSERT INTO pesanan VALUES
(101, 1, 250000.00),
(102, 2, 125000.50),
(103, 1, 99000.00);
monitoring.sh
Unduh berkas#!/usr/bin/env bash
# Memantau kesegaran backup: harus ada backup baru dalam 1x24 jam.
set -euo pipefail
SUMBER=/tmp/pgtutor-jadwal
# Backup terbaru = mtime paling besar. Simulasi "hari ini" = 2026-10-12.
TERBARU=$(find "$SUMBER" -name '*.dump' -printf '%T@ %P\n' | sort -n | tail -1 | cut -d' ' -f2-)
echo "backup terbaru: $TERBARU"
# Segar = dibuat setelah batas 1 hari (2026-10-11 02:00).
if [ -n "$(find "$SUMBER" -name '*.dump' -newermt '2026-10-11 02:00')" ]; then
echo "STATUS: OK - masih ada backup dalam batas 1 hari"
else
echo "STATUS: BAHAYA - tidak ada backup lebih baru dari 1 hari" >&2
exit 1
fi
retensi.sh
Unduh berkas#!/usr/bin/env bash
# Rotasi retensi: harian disimpan 14 hari, mingguan disimpan 8 minggu.
set -euo pipefail
SUMBER=/tmp/pgtutor-jadwal
# Simulasi "hari ini" = 2026-10-12, jadi batas harian = 2026-09-28.
# Di produksi bentuknya relatif: find ... -mtime +14 -delete.
# Di lab ini kita pakai tanggal referensi tetap agar output dapat
# direproduksi apa pun tanggal sesungguhnya saat skrip dijalankan.
echo "menghapus backup harian lebih tua dari 2026-09-28:"
find "$SUMBER/harian" -name '*.dump' ! -newermt '2026-09-28' -print -delete
# Batas mingguan = 8 minggu sebelum "hari ini" = 2026-08-17.
echo "menghapus backup mingguan lebih tua dari 2026-08-17:"
find "$SUMBER/mingguan" -name '*.dump' ! -newermt '2026-08-17' -print -delete
echo "sisa artefak setelah rotasi:"
find "$SUMBER" -name '*.dump' -printf '%TY-%Tm-%Td %TH:%TM %P\n' | sort
siapkan-artefak.sh
Unduh berkas#!/usr/bin/env bash
# Menyiapkan pohon artefak backup untuk simulasi jadwal dan retensi.
set -euo pipefail
SUMBER=/tmp/pgtutor-jadwal
# Selalu mulai bersih agar skrip bisa dijalankan berulang.
rm -rf "$SUMBER"
mkdir -p "$SUMBER/harian" "$SUMBER/mingguan"
# Backup "hari ini" (2026-10-12): dump nyata format custom
# dari database latihan chapter ini ($PGDATABASE diarahkan harness).
pg_dump -Fc -f "$SUMBER/harian/harian-2026-10-12.dump" "$PGDATABASE"
# Supaya latihan dapat direproduksi, stempel waktu diseragamkan
# ke tanggal tetap -- di produksi, mtime-nya adalah waktu job berjalan.
touch -t 202610120200 "$SUMBER/harian/harian-2026-10-12.dump"
# Artefak historis (isi placeholder, tanggal tetap):
# harian: 3 dump lama; mingguan: 3 dump mingguan.
printf 'placeholder dump\n' > "$SUMBER/harian/harian-2026-10-05.dump"
touch -t 202610050200 "$SUMBER/harian/harian-2026-10-05.dump"
printf 'placeholder dump\n' > "$SUMBER/harian/harian-2026-09-28.dump"
touch -t 202609280200 "$SUMBER/harian/harian-2026-09-28.dump"
printf 'placeholder dump\n' > "$SUMBER/harian/harian-2026-09-14.dump"
touch -t 202609140200 "$SUMBER/harian/harian-2026-09-14.dump"
printf 'placeholder dump mingguan\n' > "$SUMBER/mingguan/mingguan-2026-10-04.dump"
touch -t 202610040200 "$SUMBER/mingguan/mingguan-2026-10-04.dump"
printf 'placeholder dump mingguan\n' > "$SUMBER/mingguan/mingguan-2026-09-06.dump"
touch -t 202609060200 "$SUMBER/mingguan/mingguan-2026-09-06.dump"
printf 'placeholder dump mingguan\n' > "$SUMBER/mingguan/mingguan-2026-08-10.dump"
touch -t 202608100200 "$SUMBER/mingguan/mingguan-2026-08-10.dump"
# Inventaris lengkap sebelum rotasi dijalankan.
find "$SUMBER" -name '*.dump' -printf '%TY-%Tm-%Td %TH:%TM %P\n' | sort