Ketika pg_dump Terlambat Satu Jam
Ini kejadian nyata: lagi asik develop fitur baru, tiba-tiba laptop restart paksa karena update Windows yang nggak diminta. Docker volume buat PostgreSQL lokal? Gone. Tiga hari data seed yang udah di-generate manual, ratusan row test case yang susah payah dibuat — semua hilang.
Masalahnya bukan cuma soal data hilang. Masalahnya adalah kamu harus rebuild environment dari nol, dan itu makan waktu yang harusnya bisa dipakai buat nulis kode.
Development database sering diperlakukan sebagai second-class citizen — "ah ini cuma lokal, nggak penting." Padahal waktu yang kamu habiskan buat setup data, seed, dan state tertentu itu mahal. Backup strategy untuk development database bukan paranoia, ini hygiene engineering.
Kenapa Development Database Beda Penanganannya
Backup production database punya playbook yang jelas: WAL archiving, point-in-time recovery, replica, monitoring. Tapi development database punya karakteristik berbeda:
- Sering berubah — schema migration jalan berkali-kali sehari
- Data nggak harus konsisten 100% — yang penting state bisa di-restore ke titik tertentu
- Multiple environment — kamu mungkin punya DB lokal, DB di dev server, DB di staging
- Ukuran lebih kecil — jadi backup bisa lebih sering tanpa bikin disk penuh
Karena karakteristik ini, strategi yang cocok buat development lebih ke arah: snapshot cepat + otomasi ringan + restore yang mudah.
Pilih Tools Dulu Sebelum Nulis Script
Sebelum nulis apapun, tentuin dulu stack kamu:
| Database | Tool backup | Format output |
|---|---|---|
| PostgreSQL | pg_dump |
.sql / .dump |
| MySQL/MariaDB | mysqldump |
.sql |
| SQLite | cp / sqlite3 .backup |
.db |
| MongoDB | mongodump |
BSON directory |
Untuk development, format .sql plain text lebih gampang di-inspect dan di-diff kalau butuh debug. Format binary (-Fc di pg_dump) lebih kecil dan support parallel restore — pilih sesuai kebutuhan.
Setup Backup Otomatis untuk PostgreSQL Lokal
Ini contoh paling umum: PostgreSQL yang jalan di Docker buat development.
Struktur direktori
~/dev-backups/
postgres/
daily/
pre-migration/
manual/
Script backup harian
Buat file ~/scripts/pg-backup.sh:
#!/usr/bin/env bash
# Konfigurasi
DB_NAME="myapp_development"
DB_USER="postgres"
DB_HOST="localhost"
DB_PORT="5432"
BACKUP_DIR="$HOME/dev-backups/postgres/daily"
RETENTION_DAYS=7
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
FILENAME="${DB_NAME}_${TIMESTAMP}.sql.gz"
# Buat direktori kalau belum ada
mkdir -p "$BACKUP_DIR"
# Jalankan backup
PGPASSWORD="yourpassword" pg_dump \
-h "$DB_HOST" \
-p "$DB_PORT" \
-U "$DB_USER" \
-d "$DB_NAME" \
--no-owner \
--no-acl \
| gzip > "$BACKUP_DIR/$FILENAME"
# Cek apakah backup berhasil
if [ $? -eq 0 ]; then
echo "[OK] Backup berhasil: $FILENAME"
else
echo "[ERROR] Backup gagal!" >&2
exit 1
fi
# Hapus backup yang lebih lama dari RETENTION_DAYS
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
echo "[OK] Cleanup selesai, retensi $RETENTION_DAYS hari"
Kasih permission dan test:
chmod +x ~/scripts/pg-backup.sh
~/scripts/pg-backup.sh
Kalau berhasil, tambahkan ke crontab:
crontab -e
Tambahkan line ini (backup tiap jam 9 pagi dan 6 sore):
0 9,18 * * 1-5 /home/kamu/scripts/pg-backup.sh >> /home/kamu/dev-backups/backup.log 2>&1
Pre-Migration Backup: Yang Sering Dilupain
Ini gotcha yang paling sering bikin nyesel: jalanin migration tanpa backup dulu. Migration yang salah nulis bisa drop column yang harusnya dipertahankan, dan kalau nggak ada backup pre-migration, kamu stuck.
Solusinya: integrasikan backup ke dalam workflow migration kamu.
Kalau pakai Rails:
# lib/tasks/db.rake
namespace :db do
desc "Backup database sebelum migration"
task backup_before_migrate: :environment do
db_config = ActiveRecord::Base.connection_db_config.configuration_hash
db_name = db_config[:database]
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
backup_dir = Rails.root.join("tmp", "db-backups", "pre-migration")
FileUtils.mkdir_p(backup_dir)
filename = "#{backup_dir}/#{db_name}_pre_#{timestamp}.sql.gz"
system("pg_dump #{db_name} | gzip > #{filename}")
puts "Backup disimpan: #{filename}"
end
end
# Override db:migrate
Rake::Task["db:migrate"].enhance(["db:backup_before_migrate"])
Sekarang setiap rails db:migrate otomatis backup dulu. Kalau migration jelek, tinggal restore dari folder pre-migration.
Kalau pakai Node.js dengan Prisma atau Knex, bisa buat script serupa yang dipanggil sebelum npx prisma migrate dev atau knex migrate:latest:
#!/usr/bin/env bash
# pre-migrate.sh
echo "Backup sebelum migration..."
~/scripts/pg-backup-premigration.sh
echo "Jalanin migration..."
npx prisma migrate dev
Trus di package.json:
{
"scripts": {
"migrate": "bash ./scripts/pre-migrate.sh"
}
}
Restore yang Cepat dan Nggak Bikin Panik
Backup yang bagus tapi restore-nya susah sama aja bohong. Buat script restore yang simpel:
#!/usr/bin/env bash
# ~/scripts/pg-restore.sh
DB_NAME="myapp_development"
DB_USER="postgres"
DB_HOST="localhost"
BACKUP_FILE="$1"
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 <path-to-backup.sql.gz>"
echo ""
echo "Backup tersedia:"
ls -lht ~/dev-backups/postgres/daily/ | head -10
exit 1
fi
if [ ! -f "$BACKUP_FILE" ]; then
echo "[ERROR] File tidak ditemukan: $BACKUP_FILE"
exit 1
fi
echo "[WARNING] Ini akan DROP dan recreate database $DB_NAME"
read -p "Lanjutkan? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo "Dibatalkan."
exit 0
fi
# Drop dan recreate
PGPASSWORD="yourpassword" psql \
-h "$DB_HOST" \
-U "$DB_USER" \
-c "DROP DATABASE IF EXISTS $DB_NAME;"
PGPASSWORD="yourpassword" psql \
-h "$DB_HOST" \
-U "$DB_USER" \
-c "CREATE DATABASE $DB_NAME;"
# Restore
gunzip -c "$BACKUP_FILE" | PGPASSWORD="yourpassword" psql \
-h "$DB_HOST" \
-U "$DB_USER" \
-d "$DB_NAME"
if [ $? -eq 0 ]; then
echo "[OK] Restore berhasil dari: $BACKUP_FILE"
else
echo "[ERROR] Restore gagal!"
exit 1
fi
Contoh penggunaan:
chmod +x ~/scripts/pg-restore.sh
# Restore dari backup tertentu
~/scripts/pg-restore.sh ~/dev-backups/postgres/daily/myapp_development_20241201_090000.sql.gz
# Atau restore backup terbaru
LATEST=$(ls -t ~/dev-backups/postgres/daily/*.sql.gz | head -1)
~/scripts/pg-restore.sh "$LATEST"
Gotcha yang Perlu Kamu Tahu
1. Password di environment variable, bukan hardcode
Jangan hardcode password di script. Pakai .pgpass atau environment variable:
# ~/.pgpass
# format: hostname:port:database:username:password
localhost:5432:*:postgres:yourpassword
# Set permission yang benar
chmod 600 ~/.pgpass
Setelah ini, kamu nggak perlu PGPASSWORD=... di script.
2. Backup Docker volume langsung vs. pg_dump
Backup Docker volume (docker cp atau tar volume) itu bisa, tapi hasilnya binary dan version-specific. Kalau kamu upgrade PostgreSQL versi, backup volume bisa nggak kompatibel. pg_dump lebih portable.
3. Test restore secara berkala
Backup yang nggak pernah di-test restore itu sama aja nggak ada. Minimal sebulan sekali, coba restore ke database temporary:
# Restore ke DB test, bukan yang aktif
gunzip -c backup.sql.gz | psql -U postgres -d myapp_development_test
4. Jangan backup ke folder yang di-sync cloud otomatis
Kalau backup folder ada di dalam Dropbox atau Google Drive sync, dan kamu punya banyak backup, itu bakal ngabisin quota cloud dan bikin sync lambat. Simpan di folder terpisah di luar sync folder.
5. SQLite punya cara sendiri
Jangan cp file SQLite yang lagi aktif dipakai aplikasi — bisa corrupt. Pakai:
sqlite3 myapp.db ".backup backup_$(date +%Y%m%d).db"
Ini atomic dan aman meski database sedang dipakai.
Backup ke Remote: Opsional tapi Worth It
Kalau kamu paranoid (dan kamu harusnya sedikit paranoid), kirim backup ke remote storage. Untuk development, S3-compatible storage yang murah kayak Cloudflare R2 atau Backblaze B2 bisa jadi pilihan.
Install rclone dan configure remote:
# Install rclone
curl https://rclone.org/install.sh | sudo bash
# Configure (ikuti wizard interaktif)
rclone config
Tambahkan ke script backup:
# Sync ke remote setelah backup lokal berhasil
rclone copy "$BACKUP_DIR/$FILENAME" r2:my-dev-backups/postgres/
echo "[OK] Upload ke remote selesai"
Untuk development, upload seminggu sekali sudah cukup. Nggak perlu real-time sync.
Yang Gue Lakuin Sekarang
Setup yang gue pakai di development sehari-hari:
- Cron backup 2x sehari — jam 9 pagi dan 6 sore, retensi 7 hari lokal
- Pre-migration hook di semua project Rails dan Node — jalan otomatis sebelum migrate
- Backup manual sebelum eksperimen besar (refactor schema, import data baru) — disimpan ke folder
manual/dengan nama deskriptif - Weekly sync ke Cloudflare R2 — cron tiap Jumat malam, retensi 4 minggu
- Test restore setiap awal bulan ke database temporary
Total storage yang dipake? Sekitar 2GB untuk semua project. Biaya R2 untuk ukuran segitu? Basically nol.
Backup strategy untuk development database nggak harus rumit atau mahal. Yang penting: otomatis, sering, dan kamu tahu cara restore-nya sebelum kamu butuh. Mulai dari script sederhana dulu, tambah kompleksitas kalau memang butuh.
Script-script di atas bisa langsung kamu copy, sesuaikan nama database dan password, dan langsung jalan. Nggak ada alasan buat nunda setup ini sampai disaster berikutnya datang.