Monitoring backup bisa hijau sempurna selama setahun — file lahir tiap malam pukul 02:00, ukurannya sehat, retensi berjalan — dan pada hari bencana yang sebenarnya, restore tetap saja gagal: extension belum terpasang di server pengganti, role aplikasi tidak ikut terbawa, atau dump ternyata rusak di tengah file. Selisih antara “punya backup” dan “bisa memulihkan” hanya bisa ditutup satu cara: menjalankan pemulihan sungguhan, di lingkungan terpisah, secara berkala, dan mencatat hasilnya. Latihan semacam ini disebut restore drill, dan prinsip kerjanya diwariskan dari dunia pemadam kebakaran: prosedur yang belum pernah dilatih akan gagal persis saat dibutuhkan. Chapter penutup section ini mengerjakan drill dua jenis pemulihan secara utuh — logis dengan pg_dump/pg_restore, fisik dengan pg_basebackup — lengkap dengan verifikasi checksum, pengukuran waktu, dan pola kegagalan yang paling sering terjadi.

Mengapa Drill Wajib #

Backup menguji jalur menulis; drill menguji jalur membaca. Tiga kelompok kegagalan hanya muncul di jalur kedua. Pertama, kegagalan format dan dependensi: pg_restore membutuhkan server tujuan yang sudah memuat extension dan versi mayor yang sama dengan dump. Kedua, kegagalan cakupan: pg_dump menyimpan satu database — role, tablespace, dan definisi extension hanya ikut lewat pg_dumpall; tanpa drill, kekosongan ini baru terlihat saat aplikasi menolak login di server hasil pulih. Ketiga, kegagalan senyap: media penyimpanan korup diam-diam, sehingga satu blok di tengah arsip sudah tidak terbaca. Ketiganya tidak menampilkan gejala apa pun di monitoring backup — hanya drill yang membukanya.

Kerangka Drill Rutin #

Drill yang bernilai punya lima sifat sekaligus:

SifatArtinya
Berkalaterjadwal — kuartalan adalah titik awal yang lazim, bulanan untuk sistem kritis
Lingkungan segardi mesin/container/data directory baru, bukan menimpa cluster produksi
Berwaktudurasi diukur dan dibandingkan dengan RTO yang dijanjikan ke bisnis
Terverifikasikesamaan data dibuktikan dengan checksum, bukan dilihat sepintas
Tercatatlog drill diarsipkan sebagai bukti audit — kapan, oleh siapa, hasil apa

Tanpa sifat kedua, drill berubah menjadi bencana tersendiri; sisanya tanpa sifat kelima hanya menghabiskan CPU tanpa menghasilkan bukti.

Drill 1: Restore Logis End-to-End #

Drill logis mengikuti hidup satu database dari lahir sampai mati: data dibuat, di-checksum, di-backup, dijatuhkan, dipulihkan, lalu di-checksum ulang. Kesamaan dua checksum adalah bukti matematis bahwa pemulihan tidak kehilangan satu byte pun:

bash drill-logis.sh
#!/usr/bin/env bash
# Drill restore logis end-to-end: backup, jatuhkan, pulihkan, bandingkan.
set -euo pipefail

DB_LATIHAN=probe_drill_bk7
BERKAS=/tmp/pgtutor-drill/logis.dump
PENGECEKAN="SELECT md5(string_agg(id::text || ':' || item || ':' || jumlah::text, ',' ORDER BY id)) FROM penjualan;"

rm -rf /tmp/pgtutor-drill
mkdir -p /tmp/pgtutor-drill

# 1. Siapkan "database korban" dengan data tetap.
psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" \
    -c "CREATE DATABASE $DB_LATIHAN;"
psql -X -q -d "$DB_LATIHAN" \
    -c "CREATE TABLE penjualan (id int PRIMARY KEY, item text, jumlah int);" \
    -c "INSERT INTO penjualan VALUES (1,'Kopi Torabika',12),(2,'Teh Melati',8),(3,'Gula Pasir',25);"

# 2. Checksum kondisi sebelum "bencana".
SEBELUM=$(psql -X -t -A -d "$DB_LATIHAN" -c "$PENGECEKAN")
echo "checksum sebelum : $SEBELUM"

# 3. Backup yang ingin kita uji (job harian versi chapter sebelumnya).
pg_dump -Fc -f "$BERKAS" "$DB_LATIHAN"

# 4. Bencana: database hilang total.
psql -X -q -d postgres -c "DROP DATABASE $DB_LATIHAN;" \
    -c "CREATE DATABASE $DB_LATIHAN;"

# 5. Pemulihan dari backup.
pg_restore -d "$DB_LATIHAN" "$BERKAS"

# 6. Checksum kondisi sesudah pemulihan.
SESUDAH=$(psql -X -t -A -d "$DB_LATIHAN" -c "$PENGECEKAN")
echo "checksum sesudah: $SESUDAH"

# 7. Putusan drill.
if [ "$SEBELUM" = "$SESUDAH" ]; then
    echo "HASIL DRILL: DATA IDENTIK"
else
    echo "HASIL DRILL: DATA BERBEDA" >&2
    exit 1
fi

# 8. Bersih-bersih.
psql -X -q -d postgres -c "DROP DATABASE $DB_LATIHAN;"
rm -rf /tmp/pgtutor-drill

Output yang Dihasilkan:

text
checksum sebelum : 86d94fdb45f4e5f7cf4a2b203c0db584
checksum sesudah: 86d94fdb45f4e5f7cf4a2b203c0db584
HASIL DRILL: DATA IDENTIK

Kita bedah bagian-bagian yang menentukan:

  1. Baris PENGECEKAN=...: kunci verifikasi. md5(string_agg(... ORDER BY id)) merangkai seluruh baris tabel dalam urutan tetap lalu meringkasnya menjadi satu sidik jari 32 karakter — satu perubahan data mana pun mengubah hasilnya. Di produksi, jalankan pola yang sama untuk tabel-tabel kritis Anda.
  2. Baris SEBELUM=$(psql -X -t -A ...): opsi -t -A menghasilkan nilai polos tanpa bingkai tabel, sehingga sidik jari bisa disimpan di variabel dan dibandingkan dengan [ "$SEBELUM" = "$SESUDAH" ].
  3. Baris pg_dump -Fc lalu pg_restore -d: pasangan backup-pulih yang diuji adalah persis job harian dari chapter Backup Scheduling — drill harus menguji alat yang benar-benar dipakai, bukan alat versi khusus latihan.
  4. Baris exit 1 pada cabang gagal: drill yang menemukan perbedaan data harus gagal keras, agar tercatat sebagai insiden — bukan berlalu sebagai catatan kaki.

Drill 2: Restore Fisik sebagai Instance Baru #

Drill fisik menguji jalur pemulihan tingkat cluster: base backup -Xs dari chapter Base Backup dinyalakan sebagai server kedua di port 54330, lengkap dengan pengecekan bahwa WAL yang menyertainya benar-benar bisa direplay:

bash drill-fisik.sh
#!/usr/bin/env bash
# Drill restore fisik: base backup dinyalakan sebagai instance terpisah.
set -euo pipefail

DB_LATIHAN=probe_drill_bk7
TUJUAN=/tmp/pgtutor-drill/fisik
PORT_BARU=54330
# pg_ctl hanya ada di direktori biner server (bukan paket klien) di lab ini.
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin
PENGECEKAN="SELECT md5(string_agg(id::text || ':' || item || ':' || jumlah::text, ',' ORDER BY id)) FROM penjualan;"

bersihkan() {
    "$PGBIN/pg_ctl" -D "$TUJUAN" stop >/dev/null 2>&1 || true
    psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" >/dev/null 2>&1 || true
    rm -rf /tmp/pgtutor-drill
}
trap bersihkan EXIT

rm -rf /tmp/pgtutor-drill
mkdir -p /tmp/pgtutor-drill

# Port drill harus benar-benar bebas.
if pg_isready -q -h /tmp -p "$PORT_BARU"; then
    echo "port $PORT_BARU sedang dipakai, drill dibatalkan" >&2
    exit 1
fi

# 1. Siapkan "database korban" di server utama.
psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" \
    -c "CREATE DATABASE $DB_LATIHAN;"
psql -X -q -d "$DB_LATIHAN" \
    -c "CREATE TABLE penjualan (id int PRIMARY KEY, item text, jumlah int);" \
    -c "INSERT INTO penjualan VALUES (1,'Kopi Torabika',12),(2,'Teh Melati',8),(3,'Gula Pasir',25);"

SEBELUM=$(psql -X -t -A -d "$DB_LATIHAN" -c "$PENGECEKAN")
echo "checksum di server utama      : $SEBELUM"

# 2. Base backup mandiri (-Xs): seluruh cluster termasuk database korban.
pg_basebackup -D "$TUJUAN" -Fp -Xs --checkpoint=fast --no-password

# 3. Nyalakan salinan sebagai instance baru di port 54330.
#    Keluaran pg_ctl penuh titik-titik tunggu, dibuang supaya deterministik.
"$PGBIN/pg_ctl" -D "$TUJUAN" -l /tmp/pgtutor-drill/fisik.log \
    -o "-p $PORT_BARU -k /tmp -c listen_addresses=''" start >/dev/null 2>&1

for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
    if pg_isready -q -h /tmp -p "$PORT_BARU"; then break; fi
    sleep 1
done
pg_isready -h /tmp -p "$PORT_BARU"

# 4. Verifikasi data: koneksi ke instance baru, database yang sama.
SESUDAH=$(psql -X -t -A -h /tmp -p "$PORT_BARU" -d "$DB_LATIHAN" -c "$PENGECEKAN")
echo "checksum di instance restore  : $SESUDAH"

# 5. Verifikasi status: replay WAL selesai, server jadi primary baru.
psql -X -t -A -h /tmp -p "$PORT_BARU" -d postgres -c "SELECT pg_is_in_recovery();"
psql -X -t -A -h /tmp -p "$PORT_BARU" -d postgres -c "SELECT timeline_id FROM pg_control_checkpoint();"

if [ "$SEBELUM" = "$SESUDAH" ]; then
    echo "HASIL DRILL: DATA IDENTIK"
else
    echo "HASIL DRILL: DATA BERBEDA" >&2
    exit 1
fi

# 6. Matikan instance drill; trap bersihkan() menuntaskan sisanya.
"$PGBIN/pg_ctl" -D "$TUJUAN" stop >/dev/null 2>&1

Output yang Dihasilkan:

text
checksum di server utama      : 86d94fdb45f4e5f7cf4a2b203c0db584
/tmp:54330 - accepting connections
checksum di instance restore  : 86d94fdb45f4e5f7cf4a2b203c0db584
f
1
HASIL DRILL: DATA IDENTIK

Beberapa keputusan teknis di skrip itu layak dicermati:

  1. Baris trap bersihkan EXIT: sejak dini, skrip memasang pembersih otomatis — instance drill dimatikan dan artefak dihapus baik saat drill sukses maupun saat set -e menghentikan skrip di tengah jalan. Tanpa trap, satu drill yang gagal bisa meninggalkan server kedua menyala diam-diam.
  2. Baris pg_isready -q sebagai penjaga port: bila 54330 sudah dijawab server lain, drill membatalkan dirinya — salah sasaran verifikasi lebih buruk daripada batal berlatih.
  3. Bagian for _ in 1 2 ... 20: start instance tidak seketika; loop menunggu hingga 20 detik sebelum verifikasi dilanjutkan, dan pg_isready tanpa -q kemudian mencetak status koneksi sebagai baris output drill.
  4. Dua query status terakhir: pg_is_in_recovery() menjawab f — replay WAL hasil -Xs sudah tuntas dan instance bekerja sebagai primary — sementara timeline_id masih 1 karena pemulihan ini tidak melalui recovery.signal (bedakan dari percabangan timeline di chapter Point-in-Time Recovery ).

Di balik layar, log instance drill memuat cerita pemulihan yang utuh — potongan nyata dari log latihan kami:

text
2026-10-14 16:36:06.057 UTC [14437] LOG:  starting backup recovery with redo LSN 2/9E000028, checkpoint LSN 2/9E000080, on timeline ID 1
2026-10-14 16:36:06.058 UTC [14437] LOG:  redo starts at 2/9E000028
2026-10-14 16:36:06.058 UTC [14437] LOG:  completed backup recovery with redo LSN 2/9E000028 and end LSN 2/9E000120
2026-10-14 16:36:06.058 UTC [14437] LOG:  consistent recovery state reached at 2/9E000120

Empat baris itu adalah WAL di tempat kerjanya: server membaca backup_label, mulai replay dari redo LSN, menyelesaikannya di LSN akhir backup, lalu menyatakan keadaan konsisten — persis mekanisme yang dibahas di chapter Write-Ahead Logging . Stempel waktu, PID, dan alamat LSN Anda pasti berbeda; urutan kejadiannyalah yang harus selalu sama.

Mengukur Waktu: Melawan RTO #

RTO yang dijanjikan ke bisnis (“database kembali online dalam 2 jam”) hanya bermakna bila pernah diukur. Skrip berikut memasang time pada komponen-komponen terberat drill. Angka waktunya bervariasi antar mesin dan antar eksekusi — di lab kami, restore logis basis data tiga baris memang seceks kilat, sedangkan base backup ikut menyalin seluruh cluster:

bash waktu-drill.sh
#!/usr/bin/env bash
# Mengukur durasi komponen drill -- angka waktu bervariasi antar mesin
# dan antar eksekusi; yang stabil hanya orde besarannya.
set -euo pipefail

DB_LATIHAN=probe_drill_waktu
BERKAS=/tmp/pgtutor-drill/waktu.dump
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin

bersihkan() {
    "$PGBIN/pg_ctl" -D /tmp/pgtutor-drill/fisik-waktu stop >/dev/null 2>&1 || true
    psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" >/dev/null 2>&1 || true
    rm -rf /tmp/pgtutor-drill
}
trap bersihkan EXIT

rm -rf /tmp/pgtutor-drill
mkdir -p /tmp/pgtutor-drill

# Siapkan database korban (sama dengan drill logis).
psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" \
    -c "CREATE DATABASE $DB_LATIHAN;"
psql -X -q -d "$DB_LATIHAN" \
    -c "CREATE TABLE penjualan (id int PRIMARY KEY, item text, jumlah int);" \
    -c "INSERT INTO penjualan VALUES (1,'Kopi Torabika',12),(2,'Teh Melati',8),(3,'Gula Pasir',25);"
pg_dump -Fc -f "$BERKAS" "$DB_LATIHAN"

echo "== restore logis (drop + create + pg_restore):"
psql -X -q -d postgres -c "DROP DATABASE $DB_LATIHAN;" -c "CREATE DATABASE $DB_LATIHAN;"
time pg_restore -d "$DB_LATIHAN" "$BERKAS"

echo "== base backup + start instance baru:"
time pg_basebackup -D /tmp/pgtutor-drill/fisik-waktu -Fp -Xs --checkpoint=fast --no-password
time "$PGBIN/pg_ctl" -D /tmp/pgtutor-drill/fisik-waktu \
    -l /tmp/pgtutor-drill/waktu.log \
    -o "-p 54330 -k /tmp -c listen_addresses=''" start >/dev/null 2>&1
"$PGBIN/pg_ctl" -D /tmp/pgtutor-drill/fisik-waktu stop >/dev/null 2>&1

Output yang Dihasilkan:

text
NOTICE:  database "probe_drill_waktu" does not exist, skipping
== restore logis (drop + create + pg_restore):

real	0m0.010s
user	0m0.000s
sys	0m0.003s
== base backup + start instance baru:

real	0m0.612s
user	0m0.011s
sys	0m0.575s

real	0m0.108s
user	0m0.004s
sys	0m0.004s

Tiga angka real di lab kami — 0,010 detik untuk pg_restore, 0,612 detik untuk pg_basebackup, 0,108 detik untuk start instance — adalah hasil dari data berukuran mainan; database produksi ber-gigabyte akan menggesernya ke menit atau jam. Nilai latihan ini pada pola: ukur tiap komponen, jumlahkan, lalu bandingkan dengan RTO — dan simpan trennya per drill, karena durasi yang merambat naik antar kuartal adalah sinyal database Anda tumbuh lebih cepat daripada kapasitas pemulihan Anda.

Daftar Periksa Verifikasi #

Data identik hanyalah satu dari sekian jawaban yang harus dicatat sebelum drill dinyatakan lulus:

PemeriksaanCaraBukti
Jumlah baris tabel kritisSELECT count(*) sebelum vs sesudahangka sama
Sidik jari datamd5(string_agg(...)) sebelum vs sesudahhash sama
Role login aplikasi\du atau pg_roles di server pulihdaftar lengkap
Extension terpasang\dx atau pg_available_extensionsversi sama
Koneksi aplikasi nyatastring koneksi aplikasi diarahkan ke instance drilllogin sukses
Durasi totalkomponen drill dijumlahkanmasih di bawah RTO

Dua baris tengah sering jadi kejutan pertama: pg_dump tidak memuat role, dan pg_restore gagal begitu menemui definisi extension yang belum terpasang di server tujuan. Temuan semacam itu di drill adalah kemenangan; temuan yang sama saat bencana adalah bencana kedua.

Mendokumentasikan Runbook #

Setiap drill yang lulus harus mengendap menjadi dokumen yang bisa dijalankan orang lain — runbook. Bentuk minimalnya: daftar langkah bernomor dari “server mati” sampai “aplikasi tersambung kembali”, nama file dan lokasi backup yang dipakai, perintah verifikasi beserta nilai checksum yang diharapkan, keputusan-keputusan yang butuh manusia (kapan boleh promote, siapa yang berhak), dan catatan durasi tiap drill. Simpan runbook bersama kode operasional lain di repositori — ia ikut di-review, dan setiap perubahan topologi (server baru, port baru, lokasi arsip baru) otomatis memicu review runbook. Runbook yang tidak pernah dipakai drill adalah fiksi; runbook yang tiap kuartal dijalankan ulang adalah asuransi yang benar-benar dibayar.

Pola Kegagalan yang Sering Muncul #

Tiga kisah klasik menutup section ini karena semuanya pernah menjatuhkan tim yang merasa sudah punya backup. Pertama, backup kosong: job berjalan “sukses” selama berbulan-bulan, tetapi variabel nama database di skrip cron salah ketik, sehingga pg_dump mencadangkan database kosong — pola alarm -size 0 di chapter Backup Scheduling lahir dari kisah ini. Kedua, restore berhenti di extension: dump memuat CREATE EXTENSION, sementara paket binary extension tidak terpasang di server pengganti; solusinya adalah memasang paket yang sama sebelum restore, dan drill-lah satu-satunya cara tahu daftar paket itu. Ketiga, role tidak ikut: pg_restore menyelesaikan data dengan sempurna, aplikasi tetap menolak login karena pg_dump memang tidak menyimpan role global — kunci yang hilang ada di pg_dumpall --globals-only, yang semestinya ikut dijadwalkan. Semua pola ini punya satu obat yang sama: drill rutin, terdokumentasi, dan berwaktu.

Kemampuan memulihkan satu server adalah fondasi; tetapi server yang sama juga bisa jadi titik kegagalan tunggal bagi seluruh aplikasi. Section berikutnya menyusun lapisan berikutnya di atas backup: Replication dan High Availability — menyalin data secara berkelanjutan ke server kedua agar bencana di satu mesin tidak berarti bencana bagi ketersediaan layanan.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

drill-fisik.sh

Unduh berkas
bash
#!/usr/bin/env bash
# Drill restore fisik: base backup dinyalakan sebagai instance terpisah.
set -euo pipefail

DB_LATIHAN=probe_drill_bk7
TUJUAN=/tmp/pgtutor-drill/fisik
PORT_BARU=54330
# pg_ctl hanya ada di direktori biner server (bukan paket klien) di lab ini.
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin
PENGECEKAN="SELECT md5(string_agg(id::text || ':' || item || ':' || jumlah::text, ',' ORDER BY id)) FROM penjualan;"

bersihkan() {
    "$PGBIN/pg_ctl" -D "$TUJUAN" stop >/dev/null 2>&1 || true
    psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" >/dev/null 2>&1 || true
    rm -rf /tmp/pgtutor-drill
}
trap bersihkan EXIT

rm -rf /tmp/pgtutor-drill
mkdir -p /tmp/pgtutor-drill

# Port drill harus benar-benar bebas.
if pg_isready -q -h /tmp -p "$PORT_BARU"; then
    echo "port $PORT_BARU sedang dipakai, drill dibatalkan" >&2
    exit 1
fi

# 1. Siapkan "database korban" di server utama.
psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" \
    -c "CREATE DATABASE $DB_LATIHAN;"
psql -X -q -d "$DB_LATIHAN" \
    -c "CREATE TABLE penjualan (id int PRIMARY KEY, item text, jumlah int);" \
    -c "INSERT INTO penjualan VALUES (1,'Kopi Torabika',12),(2,'Teh Melati',8),(3,'Gula Pasir',25);"

SEBELUM=$(psql -X -t -A -d "$DB_LATIHAN" -c "$PENGECEKAN")
echo "checksum di server utama      : $SEBELUM"

# 2. Base backup mandiri (-Xs): seluruh cluster termasuk database korban.
pg_basebackup -D "$TUJUAN" -Fp -Xs --checkpoint=fast --no-password

# 3. Nyalakan salinan sebagai instance baru di port 54330.
#    Keluaran pg_ctl penuh titik-titik tunggu, dibuang supaya deterministik.
"$PGBIN/pg_ctl" -D "$TUJUAN" -l /tmp/pgtutor-drill/fisik.log \
    -o "-p $PORT_BARU -k /tmp -c listen_addresses=''" start >/dev/null 2>&1

for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
    if pg_isready -q -h /tmp -p "$PORT_BARU"; then break; fi
    sleep 1
done
pg_isready -h /tmp -p "$PORT_BARU"

# 4. Verifikasi data: koneksi ke instance baru, database yang sama.
SESUDAH=$(psql -X -t -A -h /tmp -p "$PORT_BARU" -d "$DB_LATIHAN" -c "$PENGECEKAN")
echo "checksum di instance restore  : $SESUDAH"

# 5. Verifikasi status: replay WAL selesai, server jadi primary baru.
psql -X -t -A -h /tmp -p "$PORT_BARU" -d postgres -c "SELECT pg_is_in_recovery();"
psql -X -t -A -h /tmp -p "$PORT_BARU" -d postgres -c "SELECT timeline_id FROM pg_control_checkpoint();"

if [ "$SEBELUM" = "$SESUDAH" ]; then
    echo "HASIL DRILL: DATA IDENTIK"
else
    echo "HASIL DRILL: DATA BERBEDA" >&2
    exit 1
fi

# 6. Matikan instance drill; trap bersihkan() menuntaskan sisanya.
"$PGBIN/pg_ctl" -D "$TUJUAN" stop >/dev/null 2>&1

drill-logis.sh

Unduh berkas
bash
#!/usr/bin/env bash
# Drill restore logis end-to-end: backup, jatuhkan, pulihkan, bandingkan.
set -euo pipefail

DB_LATIHAN=probe_drill_bk7
BERKAS=/tmp/pgtutor-drill/logis.dump
PENGECEKAN="SELECT md5(string_agg(id::text || ':' || item || ':' || jumlah::text, ',' ORDER BY id)) FROM penjualan;"

rm -rf /tmp/pgtutor-drill
mkdir -p /tmp/pgtutor-drill

# 1. Siapkan "database korban" dengan data tetap.
psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" \
    -c "CREATE DATABASE $DB_LATIHAN;"
psql -X -q -d "$DB_LATIHAN" \
    -c "CREATE TABLE penjualan (id int PRIMARY KEY, item text, jumlah int);" \
    -c "INSERT INTO penjualan VALUES (1,'Kopi Torabika',12),(2,'Teh Melati',8),(3,'Gula Pasir',25);"

# 2. Checksum kondisi sebelum "bencana".
SEBELUM=$(psql -X -t -A -d "$DB_LATIHAN" -c "$PENGECEKAN")
echo "checksum sebelum : $SEBELUM"

# 3. Backup yang ingin kita uji (job harian versi chapter sebelumnya).
pg_dump -Fc -f "$BERKAS" "$DB_LATIHAN"

# 4. Bencana: database hilang total.
psql -X -q -d postgres -c "DROP DATABASE $DB_LATIHAN;" \
    -c "CREATE DATABASE $DB_LATIHAN;"

# 5. Pemulihan dari backup.
pg_restore -d "$DB_LATIHAN" "$BERKAS"

# 6. Checksum kondisi sesudah pemulihan.
SESUDAH=$(psql -X -t -A -d "$DB_LATIHAN" -c "$PENGECEKAN")
echo "checksum sesudah: $SESUDAH"

# 7. Putusan drill.
if [ "$SEBELUM" = "$SESUDAH" ]; then
    echo "HASIL DRILL: DATA IDENTIK"
else
    echo "HASIL DRILL: DATA BERBEDA" >&2
    exit 1
fi

# 8. Bersih-bersih.
psql -X -q -d postgres -c "DROP DATABASE $DB_LATIHAN;"
rm -rf /tmp/pgtutor-drill

waktu-drill.sh

Unduh berkas
bash
#!/usr/bin/env bash
# Mengukur durasi komponen drill -- angka waktu bervariasi antar mesin
# dan antar eksekusi; yang stabil hanya orde besarannya.
set -euo pipefail

DB_LATIHAN=probe_drill_waktu
BERKAS=/tmp/pgtutor-drill/waktu.dump
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin

bersihkan() {
    "$PGBIN/pg_ctl" -D /tmp/pgtutor-drill/fisik-waktu stop >/dev/null 2>&1 || true
    psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" >/dev/null 2>&1 || true
    rm -rf /tmp/pgtutor-drill
}
trap bersihkan EXIT

rm -rf /tmp/pgtutor-drill
mkdir -p /tmp/pgtutor-drill

# Siapkan database korban (sama dengan drill logis).
psql -X -q -d postgres -c "DROP DATABASE IF EXISTS $DB_LATIHAN;" \
    -c "CREATE DATABASE $DB_LATIHAN;"
psql -X -q -d "$DB_LATIHAN" \
    -c "CREATE TABLE penjualan (id int PRIMARY KEY, item text, jumlah int);" \
    -c "INSERT INTO penjualan VALUES (1,'Kopi Torabika',12),(2,'Teh Melati',8),(3,'Gula Pasir',25);"
pg_dump -Fc -f "$BERKAS" "$DB_LATIHAN"

echo "== restore logis (drop + create + pg_restore):"
psql -X -q -d postgres -c "DROP DATABASE $DB_LATIHAN;" -c "CREATE DATABASE $DB_LATIHAN;"
time pg_restore -d "$DB_LATIHAN" "$BERKAS"

echo "== base backup + start instance baru:"
time pg_basebackup -D /tmp/pgtutor-drill/fisik-waktu -Fp -Xs --checkpoint=fast --no-password
time "$PGBIN/pg_ctl" -D /tmp/pgtutor-drill/fisik-waktu \
    -l /tmp/pgtutor-drill/waktu.log \
    -o "-p 54330 -k /tmp -c listen_addresses=''" start >/dev/null 2>&1
"$PGBIN/pg_ctl" -D /tmp/pgtutor-drill/fisik-waktu stop >/dev/null 2>&1