Backup Strategy untuk Development Database

by Marcus Chen
Backup Strategy untuk Development Database

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:

  1. Cron backup 2x sehari — jam 9 pagi dan 6 sore, retensi 7 hari lokal
  2. Pre-migration hook di semua project Rails dan Node — jalan otomatis sebelum migrate
  3. Backup manual sebelum eksperimen besar (refactor schema, import data baru) — disimpan ke folder manual/ dengan nama deskriptif
  4. Weekly sync ke Cloudflare R2 — cron tiap Jumat malam, retensi 4 minggu
  5. 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.