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:

LapisanAlatFrekuensiMenjawab skenario
Backup logispg_dump -Fcharianpulihkan satu database / sebagian objek dengan cepat
Base backuppg_basebackup -Xsmingguanseluruh cluster rusak — bangun ulang server lengkap
Arsip WALarchive_mode + archive_commandterus-meneruskembali ke menit tertentu (PITR, chapter sebelumnya)
Uji restoredrill terjadwalbulanan/kuartalanmembuktikan 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:

sql data-uji.sql
-- 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:

text
CREATE TABLE
CREATE TABLE
INSERT 0 3
INSERT 0 3

Skrip 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' | sort

Output yang Dihasilkan:

text
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.dump

Tiga 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:

bash retensi.sh
#!/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

Output yang Dihasilkan:

text
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.dump

Dua 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?

bash monitoring.sh
#!/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

Output yang Dihasilkan:

text
backup terbaru: harian/harian-2026-10-12.dump
STATUS: OK - masih ada backup dalam batas 1 hari

Pemeriksaan 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:

bash alarm-kosong.sh
#!/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

Output yang Dihasilkan:

text
BAHAYA: ada backup berukuran 0 byte:
harian/harian-2026-10-12.dump

Dua 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:

text
# 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 -delete

Tiga 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 #

JebakanGejalaPencegahan
Backup di jam sibuklatensi query melonjak tiap backup berjalanjendela malam, --max-rate, checkpoint spread
Job gagal senyaptidak ada email, tidak ada alarmexit != 0 + MAILTO + monitoring kesegaran
Retensi tak pernah jalandisk penuh, server berhenti totaljob cleanup terjadwal + alert penggunaan disk
Backup dan data satu diskkehilangan disk = kehilangan keduanyaaturan 3-2-1, salinan off-site
Tanpa uji restorekepercayaan tanpa buktidrill 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
bash
#!/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
sql
-- 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
bash
#!/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
bash
#!/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
bash
#!/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