Skip to main content

Live Replication Edge Cases

This is one of the most dangerous rsync scenarios: trying to rsync live database files directly from /var/lib/mysql/ or /var/lib/postgresql/ instead of using proper export tools. This page explains why it fails, when it's safe, and what to use instead.

Critical Warning

Never rsync live database data directories. Database files are continuously being written to, locked, and updated by the database engine. Rsyncing them while the database is running produces a corrupt, unrestorable backup in most cases.

Why Rsync Fails on Live Databases

How Database Storage Works

Databases don't just write data to files — they maintain a complex system of buffers, journals, and locks:

flowchart TD
APP["Application Writes"] --> BUF["Memory Buffer Pool<br/>(data in RAM, not yet on disk)"]
BUF --> WAL["Write-Ahead Log<br/>(transaction journal)"]
WAL --> DATA["Data Files on Disk<br/>(.ibd, .frm, base/)"]
BUF -.-> DATA

RSYNC["Rsync reads HERE "] --> DATA

style RSYNC fill:#ff4444,color:#fff
style BUF fill:#ff9900,color:#000

When rsync copies database files, it sees:

  • Partially written data — a file might be mid-update
  • Stale data — the buffer pool hasn't been flushed to disk yet
  • Inconsistent state — different files represent different points in time

The Result

ScenarioWhat HappensOutcome
Rsync during active writesFiles copied at different timestampsCorrupt backup — relationships between tables are broken
Rsync during transactionTransaction log and data files don't matchUnrecoverable — database can't replay partial transactions
Rsync with InnoDB tablesBuffer pool not flushedMissing data — recent writes exist only in RAM

Database File Types (What You're Rsyncing)

MySQL / MariaDB

FilePurposeSafe to Rsync Live?
ibdata1InnoDB shared tablespaceNo — actively locked
*.ibdInnoDB per-table tablespaceNo — constantly updated
*.frmTable structure definitionsMaybe — rarely changes but depends on others
ib_logfile*InnoDB redo logsNo — critical for crash recovery
mysql-bin.*Binary log (replication)Yes — append-only, safe to copy
*.MYD / *.MYIMyISAM data/indexRisky — less dangerous than InnoDB but still unsafe

PostgreSQL

File/DirectoryPurposeSafe to Rsync Live?
base/Database data filesNo
pg_wal/Write-ahead logsNo — essential for consistency
pg_xact/Transaction statusNo
postmaster.pidLock fileNo — causes conflicts

When Rsync on Database Files IS Safe

There is exactly one scenario where rsyncing raw database files is safe:

Cold Backup (Database Stopped)

# Step 1: Stop the database
sudo systemctl stop mysql # or: sudo systemctl stop postgresql

# Step 2: Rsync the data directory
rsync -av /var/lib/mysql/ /backup/mysql-cold/

# Step 3: Start the database immediately
sudo systemctl start mysql # or: sudo systemctl start postgresql

Downtime required: The database is unavailable during the rsync. For a 10 GB database, this could be 2-5 minutes (local) or 10-30 minutes (remote).

warning

Cold backups require planned downtime. For most production environments, this is unacceptable. Use the alternatives below instead.

Safe Alternatives for Live Databases

Tier 1: Export Tools (Simple, Reliable)

Best for: Small to medium databases, most use cases.

# MySQL / MariaDB — locking-free export
mysqldump -u root --single-transaction \
--routines --triggers --events \
--all-databases | gzip > db_$(date +%F).sql.gz

# PostgreSQL
pg_dump -U postgres -Fc my_database > db_$(date +%F).dump

# Then rsync the dump file
rsync -avzP /var/backups/db/ user@backup:/offsite/db/
ProsCons
Simple and reliableSlower for very large databases (100 GB+)
No downtime (with --single-transaction)Full export each time (not incremental)
Portable — works across DB versionsCPU-intensive during export

Tier 2: Hot Backup Tools (Production-Grade)

Best for: Large databases (50 GB+), minimal impact on production.

Percona XtraBackup (MySQL / MariaDB)

# Full hot backup (no locking, no downtime)
xtrabackup --backup --target-dir=/backup/xtrabackup/full/

# Prepare the backup for restoration
xtrabackup --prepare --target-dir=/backup/xtrabackup/full/

# Rsync the prepared backup to remote
rsync -avzP /backup/xtrabackup/full/ user@backup:/offsite/xtrabackup/

Incremental Backup with XtraBackup

# Full backup (baseline)
xtrabackup --backup --target-dir=/backup/base/

# Incremental backup (only changes since last full)
xtrabackup --backup --target-dir=/backup/inc1/ \
--incremental-basedir=/backup/base/

pg_basebackup (PostgreSQL)

# Stream a full backup
pg_basebackup -U postgres -D /backup/pg-basebackup/ \
-Ft -z -P --wal-method=stream

# Rsync to remote
rsync -avzP /backup/pg-basebackup/ user@backup:/offsite/pg/
ToolDatabaseHot Backup?Incremental?
mysqldumpMySQL / MariaDBYes (with --single-transaction)Full only
xtrabackupMySQL / MariaDBYesYes
mariabackupMariaDBYesYes
pg_dumpPostgreSQLYesFull only
pg_basebackupPostgreSQLYesWith WAL archiving

Tier 3: Native Replication (High Availability)

Best for: Real-time sync, high availability, zero downtime failover.

This is not rsync — it's the database's built-in replication:

MySQL Replication

-- On the primary server
CHANGE MASTER TO
MASTER_HOST='replica.server.com',
MASTER_USER='replication_user',
MASTER_PASSWORD='secure_password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;

START SLAVE;

PostgreSQL Streaming Replication

/etc/postgresql/16/main/postgresql.conf
# Primary server
wal_level = replica
max_wal_senders = 3
FeatureRsyncmysqldumpXtraBackupReplication
Live database safe
Zero downtime
Real-time sync
Incremental(files)
Point-in-time recovery
Simple setup

Combined Approach: Rsync + Database Export

The correct pattern uses rsync for files and dedicated tools for databases:

#!/bin/bash
# complete-backup.sh — Files via rsync, DB via export
set -e

TIMESTAMP=$(date +%F_%H%M)
BACKUP="/backup/$TIMESTAMP"
REMOTE="user@backup-server:/offsite/$TIMESTAMP"

mkdir -p "$BACKUP/db" "$BACKUP/files"

# Database: Export (NOT rsync on live files)
mysqldump -u root --single-transaction --all-databases \
| gzip > "$BACKUP/db/all_databases.sql.gz"

# Files: Rsync (safe for application files)
rsync -az --exclude='cache/' /var/www/html/ "$BACKUP/files/www/"
rsync -a /etc/nginx/ "$BACKUP/files/nginx/"

# Transfer everything to offsite
rsync -avzP "$BACKUP/" "$REMOTE/"

echo " Backup complete: $TIMESTAMP"

Common Pitfalls

PitfallRisk LevelPrevention
Rsyncing /var/lib/mysql/ while MySQL is runningCritical — corrupt backupAlways use mysqldump or xtrabackup
Assuming cold backup = instantModerate — longer downtime than expectedCheck data size with du -sh /var/lib/mysql/ first
Using mysqldump without --single-transactionModerate — locks tablesAlways include --single-transaction for InnoDB
Not testing restore after backupCritical — unknown backup qualityPeriodically import dumps on a test server
Using rsync for real-time DB syncCritical — data lossUse native replication for real-time sync

Decision Guide

flowchart TD
START["Need to Back Up Database?"] --> LIVE{"Is the database running?"}

LIVE -->|Yes| SIZE{"Database size?"}
LIVE -->|No - offline| COLD[" Cold Backup with Rsync<br/>rsync /var/lib/mysql/ /backup/"]

SIZE -->|"< 50 GB"| DUMP[" mysqldump + rsync<br/>Simple, reliable"]
SIZE -->|"> 50 GB"| HOT[" XtraBackup / pg_basebackup<br/>Hot backup, incremental"]

START --> REALTIME{"Need real-time sync?"}
REALTIME -->|Yes| REPL[" Native Replication<br/>MySQL/PostgreSQL built-in"]

What's Next