DEV Community

Timevolt
Timevolt

Posted on

May the Backups Be With You: A Dev-to Guide to Database Backup & Disaster Recovery

The Quest Begins (The "Why")

Honestly, I used to think backups were just a “set it and forget it” checkbox. I’d schedule a simple cp /var/lib/postgresql/data /backup/ every night, call it done, and go grab coffee. Then one Tuesday morning, our staging PostgreSQL cluster decided to corrupt a table after a botched migration. The nightly copy? Useless — files were mid‑write, the WAL was tangled, and restoring gave us a database that wouldn’t start. I felt like I’d just walked into a trap room with no exit. The panic was real, and the lesson hit hard: a backup that isn’t recoverable is no backup at all.

That moment sparked my quest for a bulletproof backup and disaster‑recovery (DR) strategy. I wanted something I could trust at 3 a.m., something that would let me sleep instead of staring at error logs.

The Revelation (The Insight)

The treasure I uncovered wasn’t a mystical artifact; it was a shift in mindset. Good backups aren’t about copying files — they’re about capturing a consistent state of the data and having a tested path to restore it. For relational databases, that means either:

  1. Logical dumps taken with a transaction‑safe flag (--single-transaction for PostgreSQL/MySQL) so you get a snapshot without locking the whole DB.
  2. Physical snapshots (filesystem/LVM, cloud provider snapshots, or volume snapshots) taken while the DB is paused or in hot‑backup mode, guaranteeing the WAL/logs are in sync.

And for DR, the real power comes from off‑site, immutable storage and regular restore drills. If you never practice the restore, you’re just hoping for the best.

Wielding the Power (Code & Examples)

Let’s look at a concrete before/after for a PostgreSQL service running on a vanilla Linux VM.

🚫 The Struggle (Naive file copy)

# cron job – runs at 02:00 every night
0 2 * * * rsync -av /var/lib/postgresql/main/ /mnt/backups/pgsql/$(date +\%F)
Enter fullscreen mode Exit fullscreen mode

Why it hurts:

  • The DB may be writing to pg_wal or updating heap files while rsync reads them.
  • You end up with a mix of old and new pages → crash on restore.
  • No way to verify integrity without actually trying to start the DB.

✅ The Victory (Logical dump + off‑site copy)

#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="/mnt/backups/pgsql"
TIMESTAMP=$(date +%F_%H-%M-%S)
DUMP_FILE="${BACKUP_DIR}/prod_${TIMESTAMP}.sql"
REMOTE_RCLONE="gdrive:pg-backups"   # any rclone remote (S3, GCS, etc.)

# 1️⃣ Take a consistent logical dump
pg_dump -U backup_user -h db-prod.internal -Fc -b -v -f "${DUMP_FILE}" prod_db

# 2️⃣ Compress (optional) and verify
gzip -9 "${DUMP_FILE}"
echo "Dump size: $(du -h ${DUMP_FILE}.gz | cut -f1)"

# 3️⃣ Send to off‑site storage (rclone handles retries & checksums)
rclone copy "${DUMP_FILE}.gz" "${REMOTE_RCLONE}" --progress

# 4️⃣ Keep local copies for 7 days, prune older ones
find "${BACKUP_DIR}" -type f -name "prod_*.sql.gz" -mtime +7 -delete

echo "Backup ${DUMP_FILE}.gz completed and uploaded."
Enter fullscreen mode Exit fullscreen mode

What changed:

  • pg_dump -Fc creates a custom-format archive that’s restorable with pg_restore.
  • The -b flag includes large objects; -v gives us nice logs.
  • Because we’re using a logical dump, the DB stays online; we only need a read‑replica or a brief lock if we want to avoid replication lag.
  • The dump is compressed, uploaded to an immutable remote (Google Drive via rclone, but you could point at an S3 bucket with Object Lock), and we prune old copies locally.

Restore snippet (the “spell” you’ll actually cast)

# Pull the latest backup from remote (choose the timestamp you need)
LATEST=$(rclone lsjson "gdrive:pg-backups/" | jq -r '.[0].Path')
rclone copy "gdrive:pg-backups/${LATEST}" /tmp/latest_backup --progress

# Decompress
gzip -d -c "/tmp/latest_backup" > /tmp/latest.dump

# Restore into a fresh DB (or a replica)
createdb -U backup_user -T template0 prod_db_restore
pg_restore -U backup_user -h db-restore.internal -d prod_db_restore -v "/tmp/latest.dump"

echo "Restore complete. Verify with: SELECT count(*) FROM your_important_table;"
Enter fullscreen mode Exit fullscreen mode

⚠️ Traps to Avoid (the “boss fights”)

Trap Why it’s deadly How to dodge
Copying data files while the DB runs Leads to torn pages, inconsistent WAL, and a DB that won’t start. Use logical dump (pg_dump, mysqldump) with transaction-safe flags or take a filesystem snapshot after pausing writes (pg_start_backup()/pg_stop_backup() in PostgreSQL, FLUSH TABLES WITH READ LOCK in MySQL).
Storing backups on the same volume or same AZ A single hardware failure or ransomware wipe destroys both production and backup. Always copy to a geographically separate bucket, enable object versioning/lock, and test cross‑region restore.
Skipping restore tests You’ll discover the backup is useless exactly when you need it most. Automate a weekly restore to a disposable DB and run a simple sanity check (row count, checksum).

Why This New Power Matters

Adopting this approach turned my backup anxiety into confidence. I can now:

  • Sleep through the night knowing a consistent, verified copy lives off‑site.
  • Spin up a fresh environment in minutes for feature branches or QA, using the same backup pipeline.
  • Pass compliance audits with concrete proof of RTO/RPO numbers (we measure them by timing the restore script).
  • Sleep even deeper when I run the monthly DR game‑day: I blow away the production replica, restore from the latest remote backup, and have the app serving traffic within our SLA window.

It’s like finally having a reliable map and a compass in a dungeon that keeps shifting its walls—you know you’ll find the exit.

Your Turn: Embark on Your Own Backup Quest

Here’s a challenge for you: Pick one of your services (PostgreSQL, MySQL, MongoDB, Redis, etc.) and replace any raw file‑copy backup with a logical dump or snapshot pipeline that pushes to an off‑site, immutable store. Time how long the backup takes, then time a restore to a fresh instance. Share your numbers (or your horror‑story‑turned‑triumph) in the comments—I love hearing how fellow adventurers tame their data dragons!

Now go forth, back up wisely, and may your restores always be swift. 🚀

Top comments (0)