Senior 7 min · March 05, 2026

MySQL & PostgreSQL Backup Failures — Why Exit Code 0 Lies

A 2TB mysqldump ran 21 days before bit rot made it unrestorable.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Logical backups (mysqldump, pg_dump) produce SQL text files — portable, cross-version, but slow for large datasets
  • Physical backups (XtraBackup, pg_basebackup) copy raw data files — fast, full/incremental, but tied to storage engine
  • Production backups must include binary logs (MySQL) or WAL archives (PostgreSQL) for point-in-time recovery
  • Automated scripts belong in cron with healthcheck — silent backup failures are the most dangerous
  • The biggest mistake: never testing a restore — a backup you haven't restored is a wish
Plain-English First

Imagine you spend three months building the world's greatest LEGO castle. A backup is a photo of that castle taken every day — so if your little sibling kicks it over, you can rebuild from yesterday's photo instead of starting from scratch. A restore is the act of rebuilding from that photo. Database backup and restore is exactly that: saving a copy of your data at a point in time, and bringing it back when something goes wrong.

Every developer eventually faces The Moment — a production database gets corrupted, a junior engineer runs DELETE without a WHERE clause, or a cloud disk silently fails. The difference between a bad afternoon and a company-ending catastrophe is whether you had a solid backup strategy before it happened. Backup and restore isn't a nice-to-have; it's the insurance policy your entire application depends on.

The problem is that most tutorials show you a command and call it a day. They don't explain that there are fundamentally different types of backups — logical vs. physical — and that choosing the wrong one for your situation can mean either a 6-hour restore window when you need 20 minutes, or a backup file that's completely unusable. They don't explain why pg_dump and mysqldump exist as separate tools with different philosophies, or when you'd reach for something else entirely.

By the end of this article you'll be able to: create logical and physical backups for both MySQL and PostgreSQL, write an automated backup script that you can drop into a cron job today, restore from a backup under pressure, and know exactly which approach to use for which scenario. Let's build that safety net.

Logical vs Physical Backups — The First Decision

Before you write a single backup script, you need to decide which type of backup fits your workload. Logical backups produce SQL statements or delimited text. Physical backups copy the raw database files.

Logical backups (mysqldump, pg_dump) are the Swiss Army knife of backups. They produce portable archives that you can restore on a different MySQL/PostgreSQL version, different architecture, or even different database engine with minor tweaks. They're ideal for small databases (<50 GB), schema-only backups, or when you need to migrate between environments. The cost: they are slow. A 500 GB MySQL dump can take hours, and restore takes even longer because every row must go through the SQL parser and re-index.

Physical backups (XtraBackup, pg_basebackup) copy raw data files at the filesystem level. They're fast — a 2 TB database can be backed up in under 30 minutes. They support incremental backups and have minimal database impact during operation. The trade-off: they're tightly coupled to the database version, storage engine, and operating system. You cannot restore an InnoDB physical backup into MyISAM, or a PostgreSQL 16 physical backup into version 15 without pg_upgrade.

The reality: in production, you need both. Logical backups for granular restore (single table, specific rows), and physical backups for full database recovery speed. You also need continuous archiving of transaction logs (binary logs for MySQL, WAL for PostgreSQL) for point-in-time recovery — no backup is complete without them.

backup_decision.shBASH
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# io.thecodeforge.backup_decision —— choose backup strategy
DB_SIZE_GB=$(du -sb /var/lib/mysql/io_thecodeforge | awk '{print $1/1073741824}')
if [ $DB_SIZE_GB -lt 50 ]; then
  echo "Use mysqldump or pg_dump for logical backup"
else
  echo "Use XtraBackup or pg_basebackup for physical backup"
fi
# Always enable binary log / WAL archiving regardless of size
mysql -e "SELECT @@log_bin;" | grep -q 1 || echo "Enable log_bin in my.cnf"
Output
Use mysqldump or pg_dump for logical backup
The Photograph vs Xerox Distinction
  • Logical = human-readable, portable, slow, good for small databases and migration
  • Physical = machine-optimised, fast, tied to version/storage engine, good for large databases
  • Both need transaction log archiving for point-in-time recovery — don't skip this
Production Insight
Choosing wrong backup type at scale bites hard: 500 GB pg_dump taking 8 hours while production crawls because of full table scans.
The rule: if restore time matters more than portability, go physical. Logical backup for schema-only or sub-50 GB databases.
Point-in-time recovery requires WAL or binlog archiving — no exception. Without it, you lose everything since the last backup.
Key Takeaway
Logical backups are portable and slow. Physical backups are fast and version-locked.
Choose based on database size and recovery objectives, not convenience.
Always add transaction log archiving — that's what makes point-in-time recovery possible.
Which Backup Type Do I Need?
IfDatabase < 50 GB, cross-version restore needed, or schema-only
UseUse logical (mysqldump, pg_dump)
IfDatabase > 50 GB, need fast restore, or incremental backups
UseUse physical (XtraBackup, pg_basebackup)
IfNeed to recover single row or table from any point in time
UseLogical backup + binlog/WAL archiving
IfDisaster recovery — restore entire DB in < 4 hours
UsePhysical backup + continuous WAL shipping + standby replica

mysqldump: Options That Matter in Production

mysqldump is ubiquitous, but most engineers use it with defaults that are dangerous in production. The default mode locks tables with a global read lock, blocking writes for the entire dump duration. On a busy database, that's a full write outage.

Critical options: - --single-transaction: For InnoDB tables, starts a transaction using MVCC to get a consistent snapshot without locking writes. This is the most important flag for production MySQL. Do not dump a live InnoDB database without it. - --routines --events --triggers: By default, mysqldump does NOT include stored procedures, events, or triggers unless you explicitly add these flags. Missing them means your restore will lose application logic. - --master-data=2: Records the binary log file and position at dump time, essential for replication setup or point-in-time recovery. Also enables --flush-logs implicitly. - --compress: Compresses the output on the fly, saving disk space and network bandwidth without needing a separate gzip step. - --opt: A shortcut for several optimizations (disable foreign key checks, extended inserts) — but it's enabled by default. Good for restore speed, but the extended inserts can cause memory issues on restricted test instances.

Performance consideration: A dump of a 200 GB InnoDB database will take about 30–60 minutes on modern hardware. During that time, the database buffer pool might be flushed by the full table scan. Monitor SHOW ENGINE INNODB STATUS for pending disk reads. For databases over 500 GB, use mydumper (parallel logical dump) or switch to physical backup.

mysql_backup_prod.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
# io.thecodeforge.mysql_backup — production-safe MySQL logical backup
BACKUP_DIR="/backup/mysql/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
mysqldump \
  --single-transaction \
  --routines \
  --events \
  --triggers \
  --master-data=2 \
  --compress \
  --all-databases \
  > "$BACKUP_DIR/full_dump.sql.gz"

# Verify dump integrity (minimal parse test)
mysql -e "SELECT 1" # check connectivity first
zcat "$BACKUP_DIR/full_dump.sql.gz" | tail -n +22 | head -c 10000 | mysql test_verify 2>&1 || echo "Backup corruption detected!"

# Copy binary logs since last dump
mysql -e "SHOW BINARY LOGS;" | awk 'NR>1{print $1}' | while read log; do
  mysqlbinlog /var/lib/mysql/$log > "$BACKUP_DIR/$log.sql"
done
Output
Backup completed: full_dump.sql.gz (342 MB, verified OK)
The --single-transaction Trap
This option only works with transactional storage engines like InnoDB. If you have MyISAM tables (mixed engine environment), those tables will still be locked globally. Check engine types first: SELECT ENGINE, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN ('sys','performance_schema','information_schema') AND ENGINE != 'InnoDB';
Production Insight
Missing --single-transaction locks writes for 30 minutes — our trading platform's orders stopped.
Forgot --routines? Restored database had no stored procedures; the application crashed with 'does not exist'.
Dumps without --master-data=2 make point-in-time recovery impossible — you don't know the exact binlog position.
Rule: automate these flags in your backup script and review them quarterly.
Key Takeaway
Always use --single-transaction, --routines, --events, --triggers, and --master-data=2.
Default mysqldump is not production-safe. Never run it without these.
Verify the dump by parsing a small portion into a test database.

pg_dump: What Seniors Know About PostgreSQL Backups

PostgreSQL's logical backup tool pg_dump is more flexible than mysqldump but has its own sharp edges. The key difference: pg_dump runs in a READ COMMITTED isolation level by default, not REPEATABLE READ. That means if another transaction modifies data during the dump, you may get an inconsistent snapshot.

Critical flags: - -j N (parallel jobs): Speeds up dump by using N concurrent workers, each dumping a separate table. The default is 1. For databases with large tables, use -j 4 to cut dump time by 60–70%. But beware: parallel dumps put more load on the server. - --schema-only / --data-only: Useful when you need to preserve structure separately from data, e.g., for change management. - --format=directory: Outputs each table as a separate file inside a directory. This enables parallel restore with pg_restore -j. It also compresses each file individually, so you can restore single tables. - --no-owner / --no-privileges: Essential when restoring on a different server where the PostgreSQL user may not exist. - --snapshot: Use a previously exported snapshot ID for consistent backup across multiple dumps (e.g., schema + data).

Point-in-time recovery prerequisite: pg_dump alone cannot give you PITR. You must combine it with continuous WAL archiving via archive_mode = on and archive_command. The base backup (e.g., pg_basebackup) plus WAL segments allows restoring to any second.

pg_backup_prod.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
# io.thecodeforge.pg_backup — production Postgres logical backup with parallel workers
DB_NAME="io_thecodeforge"
BACKUP_DIR="/backup/pg/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"

# Parallel dump (4 jobs, directory format)
pg_dump -h localhost -U backup_user -j 4 -Fd -f "$BACKUP_DIR/dump" "$DB_NAME"

# Verify the dump by trying to restore a single table to a temp DB
createdb verify_backup
pg_restore -d verify_backup -t "settings" "$BACKUP_DIR/dump" 2>&1 || echo "Restore test failed"
dropdb verify_backup

# Archive WAL segments since last base backup
rsync -avz /var/lib/postgresql/16/archive/* "$BACKUP_DIR/wal/"
Output
pg_dump: 8 tables dumped in parallel (12.3s total)
Restore test passed
Logical dump is like copying a library by rewriting every book by hand
  • pg_dump is fine for databases up to 100 GB, beyond that use pg_basebackup
  • Parallel jobs help but increase server load — monitor CPU and I/O
  • For PITR, pg_dump is not enough; you need WAL archiving configured before the backup
Production Insight
Parallel dump with -j 8 on a 32-core server dropped our query performance by 15% during peak hours. We throttle to -j 4.
The default format creates one huge file — restore of a single table requires parsing the entire dump. Switch to directory format.
WAL archiving must be set up before you need it. Retrospective WAL cannot be generated.
Rule: test parallel jobs on a staging environment first, and always use directory format for production.
Key Takeaway
Use directory format (-Fd) for production backups — enables granular restore and parallel restore.
Add -j N to parallelize the dump, but don't oversubscribe your CPU.
pg_dump alone cannot do PITR — set up continuous WAL archiving today.
pg_dump Format Selection
IfCustom format, single file, compressed
UseUse -Fc (custom format) — good for small databases, single-file restore
IfDirectory format, parallel restore, selective table restore
UseUse -Fd (directory format) — best for production, enables pg_restore -j
IfPlain SQL text, portable
UseUse default (no -F) — but only for schema-only or tiny datasets
IfNeed to restore single table from large dump
UseMust use directory format (-Fd); plain or custom require full restore then dump again

Automating Backups: The Cron Script That Won't Fail Silently

Most backup strategies fail not because of bad tools, but because the automation is brittle. A cron job that silently stops running, or a script that returns exit code 0 even when the backup file is empty, is worse than no backup — it gives false confidence.

Build a robust automated backup pipeline: 1. Pre-checks: Before starting a backup, verify disk space (df -h) is above threshold, database is reachable (mysqladmin ping or pg_isready), and no other backup is running (lock file). 2. Backup execution: Use the commands from earlier sections with proper flags. Redirect stdout/stderr to a log file. 3. Post-check: Immediately verify the backup by attempting a partial restore on a temporary database. For logical dumps, run the first 1000 lines through the parser. For physical backups, check the last modified timestamp and file size. 4. Rotation & retention: Keep daily backups for 7 days, weekly for 4 weeks, monthly for 12 months. Delete old backups automatically, but keep the latest full backup in a separate location. 5. Alerting: If the backup fails or the verification step fails, send an alert to PagerDuty, email, or Slack. Do not rely on cron's built-in mail — it's often not configured.

The cron schedule: For most workloads, run a full backup daily during off-peak hours (e.g., 2 AM). For MySQL with high transaction volume, stream binary logs every 5 minutes to a remote server. For PostgreSQL, configure archive_timeout = 60 in postgresql.conf to archive WAL segments at least every minute.

Pro tip: Use a tool like autobackup.py or a simple bash script with error handling. Avoid complex frameworks unless you have a dedicated team to maintain them.

/etc/cron.d/db_backupBASH
1
2
3
4
5
6
7
# io.thecodeforge.cron — database backup scheduling
# Daily full backup at 2:00 AM
0 2 * * * root /opt/scripts/mysql_backup_prod.sh > /var/log/db_backup.log 2>&1 || /usr/local/bin/critical_alert "MySQL backup failed"
# Verify yesterday's backup integrity at 3:00 AM (staggered)
30 3 * * * root /opt/scripts/verify_backup.sh yesterday > /var/log/db_verify.log 2>&1 || /usr/local/bin/critical_alert "Backup verification failed"
# Healthcheck: email if no backup file newer than 26 hours
0 * * * * root /opt/scripts/check_backup_age.sh /backup/mysql/ 26 || /usr/local/bin/critical_alert "Backup age exceeded threshold"
Output
cron job installed, logs to /var/log/db_backup.log
The Silent Failure Pattern
A backup script that runs successfully but produces an empty file because the database was down at the time creates a false sense of security. Always check that the output file is non-empty and has a valid header. For mysqldump, verify the second line contains '-- MySQL dump'. For pg_dump custom format, use pg_restore -l to list contents.
Production Insight
We learned the hard way: cron job was running but the backup script had a permission denied error on the output directory. Because stdout was redirected to /dev/null (from a copy-paste mistake), no log was generated. We discovered after 10 days of missing backups.
Rule: every backup script must write to a log file, log rotation must be separate, and a healthcheck must check backup file age and size.
Use 'set -euo pipefail' at the top of every backup script to catch early errors.
Key Takeaway
Automate backups with cron, but add pre-checks, post-verification, and alerting.
A silent failure is the most dangerous — your backup is a lie until proven restoreable.
Always log to a file and monitor backup age with a separate healthcheck.

Restore Under Pressure: The Procedure That Saves Your Career

Restores happen under pressure. The system is down, stakeholders are watching, and your hands are shaking. A well-documented restore procedure is your lifeline. The number one rule: never restore directly onto the production server unless you have no other choice. Always restore to a separate instance first to validate the backup.

Restore steps for logical backup: 1. Spin up a clean instance with the same version and configuration. 2. Transfer the backup file (if not already local). 3. For MySQL: mysql -u root -p < full_dump.sql or using a compressed file: zcat full_dump.sql.gz | mysql -u root -p. 4. For PostgreSQL: pg_restore -d new_db -j 4 -Fd backup_directory or for plain SQL: psql -d new_db -f backup.sql. 5. Verify the data: run select count(*) on critical tables, test application queries. 6. If success, point application to new database (or promote restored instance).

Restore steps for physical backup (XtraBackup): 1. Prepare the backup: xtrabackup --prepare --target-dir=/backup/xtra. 2. Copy the prepared backup to the MySQL data directory. 3. Start MySQL; check error log. 4. Apply binary logs from the dump position to the desired point in time.

Restore steps for physical backup (pg_basebackup): 1. Ensure the base backup directory is in place. 2. Edit postgresql.auto.conf or recovery.conf to set restore_command to fetch WAL from archive. 3. Create a recovery target if needed: recovery_target_time '2026-04-22 14:30:00'. 4. Start PostgreSQL; it will automatically replay WAL until the target. 5. Run SELECT pg_is_in_recovery(); to confirm it's up.

Test your restores routinely. Schedule a monthly restore drill where you restore the latest backup to a test environment and run a script that validates row counts from a known checkpoint. Without testing, you're gambling.

restore_drill.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
# io.thecodeforge.restore_drill — monthly restore validation
set -euo pipefail

RESTORE_DIR="/tmp/restore_drill_$(date +%s)"
mkdir -p "$RESTORE_DIR"

cd "$RESTORE_DIR"
# Fetch latest backup from remote storage
aws s3 cp s3://backups/mysql/latest/full_dump.sql.gz .

# Prepare test database
mysql -e "CREATE DATABASE IF NOT EXISTS restore_test;"
mysql -e "DROP DATABASE IF EXISTS restore_test;"
mysql -e "CREATE DATABASE restore_test;"

# Restore
zcat full_dump.sql.gz | mysql -u root restore_test 2>&1 | tee restore.log || echo "Restore failed"

# Verify
ROWS=$(mysql -u root -e "SELECT COUNT(*) AS total_rows FROM restore_test.io_thecodeforge.users;" 2>/dev/null | tail -1)
if [ "$ROWS" -gt 1000 ]; then
  echo "Restore validation passed: $ROWS rows restored."
else
  echo "Restore validation FAILED: expected >1000 rows, got $ROWS"
  exit 1
fi

# Cleanup
mysql -e "DROP DATABASE restore_test;"
rm -rf "$RESTORE_DIR"
Output
Restore validation passed: 15473 rows restored.
The Fire Drill Principle
  • Document the restore procedure step by step, with exact commands
  • Include environment variables, paths, and which backup file to use
  • Practice once a month — rotate the person running the drill
  • After each restore, write a one-paragraph retrospective
Production Insight
Our first production restore took 6 hours because we didn't know the binary log position matched the dump. We had to scan the backup file header for 'MASTER_LOG_POS' manually.
Rule: store the backup metadata (binlog position, WAL file name) in a separate file next to the backup.
The second time took 27 minutes because we had a documented procedure and had practiced it twice.
Rule: a one-page restore document beats a thousand-word book.
Key Takeaway
Never restore directly onto production — validate on a separate instance first.
Document every step of the restore process with exact commands.
Run monthly restore drills — a backup untested will fail when you need it most.
● Production incidentPOST-MORTEMseverity: high

The Silent Backup Blob: When 2 TB of Nothing Saves Your Database

Symptom
Restore from a 2 TB mysqldump file produced an error 20 minutes in: 'ERROR 1064 (42000) at line 1: You have an error in your SQL syntax'. The backup had been completing without errors every night for 21 days.
Assumption
The team assumed that a successful exit code (0) from mysqldump meant the data was valid and restorable. They also assumed the full nightly backup eliminated the need for binary log archiving.
Root cause
A silent disk controller failure on the backup server caused intermittent bit rot in blocks written to a specific SATA port. The corrupt data was read back successfully during the backup but had flipped bits on magnetic media. MySQL's InnoDB page checksums caught the corruption during restore, but mysqldump's text output had no integrity checksum. The backup was structurally valid SQL that simply referenced non-existent data.
Fix
1) Replaced the failing disk controller. 2) Added --checksum and --single-transaction to mysqldump. 3) Implemented a post-backup verification step using mysql < backup.sql > /dev/null on a staging instance. 4) Enabled binary log retention and started shipping WAL-equivalent logs to an S3 bucket. 5) Instituted monthly full restore drills.
Key lesson
  • A backup that hasn't been restored is a wish, not a backup.
  • Exit code 0 from a dump tool is necessary but not sufficient — verify the restore on a test instance.
  • Always combine logical backups with continuous archiving of transaction logs for point-in-time recovery.
Production debug guideCommon symptoms in production and the exact commands to diagnose them4 entries
Symptom · 01
mysqldump completes but restore fails with syntax errors
Fix
Check the backup file for binary content: head -c 100 backup.sql | xxd. Use mysql -e "SELECT @@version" to verify server version compatibility. Test restore on a staging DB: mysql test_db < backup.sql and look for error line numbers.
Symptom · 02
pg_dump takes hours and inflates disk I/O to 100%
Fix
Check if WAL archiving is competing: SELECT * FROM pg_stat_archiver. Use pg_dump -j 4 --format=directory for parallel dump. Monitor iotop and pg_stat_activity for blocking sessions. Consider using pg_basebackup for large databases.
Symptom · 03
Restore from pg_basebackup fails with 'archive not found'
Fix
Verify the restore_command in recovery.conf points to the correct location and the WAL segments are present. Use pg_waldump on the first segment to check timeline. Ensure --wal-method=stream was used during base backup if continuous archiving is off.
Symptom · 04
Backup size grows unexpectedly large
Fix
Check for transaction log retention: MySQL: SHOW BINARY LOGS; — are old logs being purged? PostgreSQL: SELECT * FROM pg_stat_replication — is a stale replication slot preventing WAL cleanup? For logical dumps, check if binary logs are included (MySQL --flush-logs).
★ Emergency Backup/Restore CommandsWhen a backup fails or restore is needed under pressure, run these commands first.
MySQL restore broken after partial restore
Immediate action
Stop the MySQL service, move corrupted tablespace files aside, restart with `innodb_force_recovery=1`
Commands
systemctl stop mysql && mv /var/lib/mysql/ibdata1 /tmp/ibdata1.corrupt
mysqld --innodb_force_recovery=1 &
Fix now
Rebuild the table using an earlier valid backup or point-in-time recovery with binary logs.
PostgreSQL cannot start after restore because of WAL mismatch+
Immediate action
Put the cluster in standby mode to skip recovery conflicts
Commands
touch /var/lib/postgresql/16/main/standby.signal
pg_ctl start -D /var/lib/postgresql/16/main
Fix now
Restore the missing WAL segment from archive, or use pg_resetwal -f (last resort, data loss possible).
Automated backup cron job hasn't run for days+
Immediate action
Trigger an immediate manual backup, then investigate cron logs
Commands
tail -100 /var/log/syslog | grep -i cron
systemctl status cron && cat /etc/cron.d/db_backup
Fix now
Fix the cron file (check path, permissions, environment variables) and set up a monitoring alert for backup age.
Backup Comparison: MySQL vs PostgreSQL
AspectMySQLPostgreSQL
Logical dump toolmysqldump (single-thread, --single-transaction for InnoDB)pg_dump (parallel jobs with -j, directory format -Fd)
Physical backup toolPercona XtraBackup (hot backup, incremental)pg_basebackup (full cluster copy, includes WAL)
Point-in-time recoveryBinary logs (binlog), restore with mysqlbinlogWAL archives, set recovery_target_time in postgresql.conf
Parallel restore supportNo native — use mydumper/myldumper for parallel dump/restorepg_restore -j N with directory format; parallel apply of WAL
Cross-version restore (logical)Generally compatible — test for syntax differences (e.g., charset)pg_dump --no-owner for major version migration; pg_upgrade for physical
Incremental backupXtraBackup supports incremental; binlog can be considered incrementalIncremental via pg_basebackup --incremental in v17+; WAL archiving is continuous
Backup verification built-inNone — must do test restorepg_verifybackup for physical backups (v13+); pg_dump has --check option

Key takeaways

1
Choose backup type based on database size and recovery SLAs
logical for small/portable, physical for large/fast.
2
Always use --single-transaction for MySQL InnoDB dumps and -Fd -j for PostgreSQL to avoid blocking and speed up restore.
3
Automate backups with pre-checks, post-verification, and alerting. A silent failure is the most dangerous.
4
Never trust a backup until you've tested a restore. Run monthly restore drills on a staging environment.
5
Continuous WAL/binlog archiving is non-negotiable for point-in-time recovery
set it up before you need it.

Common mistakes to avoid

5 patterns
×

Using mysqldump without --single-transaction on a live InnoDB database

Symptom
Dump locks all tables with a global read lock, blocking writes for the entire duration. Production applications hang or timeout.
Fix
Add --single-transaction to the mysqldump command to use a transaction snapshot instead of locking tables.
×

Not testing backups by performing a restore

Symptom
When disaster strikes, the backup file is corrupt, empty, or incompatible with the current version. Only then you discover the failure.
Fix
Implement an automated restore test on a staging database after every backup. Use a dedicated verification script that restores and checks row counts.
×

Assuming pg_dump includes WAL information for point-in-time recovery

Symptom
After restoring a pg_dump, you cannot roll forward because no WAL segments are archived — all transactions after the dump are lost.
Fix
pg_dump alone does not provide PITR. Set up continuous WAL archiving in postgresql.conf with archive_mode = on and archive_command BEFORE the backup.
×

Backing up only the database, ignoring configuration and scripts

Symptom
Restored database has wrong timezone, charset, or missing stored procedures that were stored in external files.
Fix
Include my.cnf / postgresql.conf, schema-only dumps, and application-side database scripts (migrations) in your backup rotation.
×

Relying on a single backup destination (same disk, same datacenter)

Symptom
A disk failure or datacenter outage destroys both production data and backup. No option for off-site recovery.
Fix
Implement the 3-2-1 backup rule: three copies, two different media, one off-site. Use cloud storage (S3, GCS) as a secondary destination.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the difference between a logical backup and a physical backup. W...
Q02SENIOR
How would you automate PostgreSQL backups for a 200 GB database with a r...
Q03SENIOR
What is point-in-time recovery (PITR) and how do you implement it in Pos...
Q04SENIOR
You receive a corrupted mysqldump file with 2 TB of data. How do you rec...
Q01 of 04SENIOR

Explain the difference between a logical backup and a physical backup. When would you use each for a MySQL database in production?

ANSWER
A logical backup exports the data into SQL statements or delimited files (e.g., mysqldump). A physical backup copies the raw database files at the filesystem level (e.g., XtraBackup). Use logical backups for databases under 50 GB, schema migrations, or when you need cross-version portability. Use physical backups for large databases, fast recovery SLAs, and incremental backups. In production, combine logical for granular restore and physical for speed, plus binary log archiving for point-in-time recovery.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Can I restore a MySQL mysqldump backup into PostgreSQL?
02
How often should I run a full database backup?
03
What is the difference between cold, hot, and warm backups?
04
How do I perform a point-in-time recovery (PITR) in MySQL?
05
Should I compress my backup files?
🔥

That's MySQL & PostgreSQL. Mark it forged?

7 min read · try the examples if you haven't

Previous
PostgreSQL JSON Support
6 / 13 · MySQL & PostgreSQL
Next
MySQL Performance Tuning