Cross-Datacenter Replication Strategies
Cross-datacenter replication ensures your data is available across multiple geographic locations, providing both disaster recovery and improved availability. This comprehensive guide covers database replication, storage synchronization, and failover strategies for distributed infrastructure.
Table of Contents
- Replication Architecture Overview
- MySQL Replication
- PostgreSQL Replication
- Storage Synchronization
- Network Considerations
- Monitoring Replication
- Failover Strategies
- Troubleshooting
- Conclusion
Replication Architecture Overview
Cross-datacenter replication architecture typically includes:
- Primary datacenter: Hosts the main database and application servers
- Secondary datacenter: Receives replicated data with minimal lag
- Network: Dedicated replication link with sufficient bandwidth
- Monitoring: Real-time synchronization status and alerts
# Network topology validation
validate_replication_network() {
local primary_ip="10.0.1.10"
local secondary_ip="10.0.2.10"
local replication_port=3306
echo "Validating replication network"
# Test connectivity
if nc -zv "$primary_ip" "$replication_port" 2>/dev/null; then
echo "✓ Primary datacenter reachable"
else
echo "✗ Cannot reach primary datacenter"
return 1
fi
# Measure latency
latency=$(ping -c 5 "$secondary_ip" 2>/dev/null | tail -1 | awk -F'/' '{print $5}')
echo "✓ Network latency: ${latency}ms"
# Recommended: < 50ms for synchronous replication
if (( $(echo "$latency < 50" | bc -l) )); then
echo "✓ Latency acceptable for synchronous replication"
else
echo "⚠ High latency - consider asynchronous replication"
fi
}
validate_replication_network
MySQL Replication
MySQL replication uses binary logs (binlog) to replicate data changes from primary to replica servers.
Primary Server Configuration
# Configure MySQL primary server
cat > /etc/mysql/mysql.conf.d/replication.cnf << 'EOF'
[mysqld]
# Enable binary logging
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
binlog_row_image = FULL
# Server ID (must be unique across replication topology)
server_id = 1
# Replication user
create_replication_user = 1
# Binary log retention (7 days)
expire_logs_days = 7
# Optional: Semi-synchronous replication (stronger consistency)
plugin_load_add = semisync_master.so
rpl_semi_sync_master_enabled = 1
rpl_semi_sync_master_timeout = 10000
# Replication settings
binlog_cache_size = 32768
max_binlog_size = 1073741824
EOF
# Restart MySQL
systemctl restart mysql
# Verify replication is enabled
mysql -u root -e "SHOW VARIABLES LIKE 'log_bin%';"
mysql -u root -e "SHOW VARIABLES LIKE 'server_id';"
Create Replication User
# Create dedicated replication user with proper permissions
mysql -u root << 'EOF'
CREATE USER 'repl_user'@'10.0.2.%' IDENTIFIED BY 'secure_replication_password';
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'10.0.2.%';
FLUSH PRIVILEGES;
-- Show grants to verify
SHOW GRANTS FOR 'repl_user'@'10.0.2.%';
EOF
Create Base Backup
# Create consistent backup for replica initialization
mysql -u root << 'EOF'
-- Create backup directory user if needed
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'backup_password';
GRANT SELECT, RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'backup'@'localhost';
EOF
# Take backup with mysqlbackup (or mysqldump)
mysqldump \
-u backup \
-p \
--all-databases \
--single-transaction \
--master-data=2 \
--quick \
> /tmp/mysql-replication-base.sql
# Get binary log position from backup
grep -i "CHANGE MASTER" /tmp/mysql-replication-base.sql
# Compress and transfer to secondary datacenter
gzip -c /tmp/mysql-replication-base.sql | \
ssh -C [email protected] 'zcat > /tmp/mysql-replication-base.sql'
Secondary Server Configuration
# Configure MySQL secondary (replica) server
cat > /etc/mysql/mysql.conf.d/replication.cnf << 'EOF'
[mysqld]
# Enable binary logging on replica (for chained replication)
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
# Unique server ID
server_id = 2
# Read-only mode (optional but recommended for replicas)
read_only = 1
super_read_only = 1
# Parallel replication (increase throughput)
slave_parallel_workers = 8
slave_parallel_type = LOGICAL_CLOCK
slave_preserve_commit_order = 1
# Semi-synchronous replication
plugin_load_add = semisync_slave.so
rpl_semi_sync_slave_enabled = 1
# Relay log
relay_log = /var/log/mysql/mysql-relay-bin
relay_log_index = /var/log/mysql/mysql-relay-bin.index
EOF
systemctl restart mysql
Initialize Replica
# On secondary server: import base backup
mysql -u root < /tmp/mysql-replication-base.sql
# Configure replication connection
mysql -u root << 'EOF'
CHANGE MASTER TO
MASTER_HOST='10.0.1.10',
MASTER_USER='repl_user',
MASTER_PASSWORD='secure_replication_password',
MASTER_PORT=3306,
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=12345;
-- Start replication
START SLAVE;
-- Check replication status
SHOW SLAVE STATUS\G
EOF
# Monitor replication status
watch_replica_status() {
while true; do
mysql -u root -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_State|Seconds_Behind_Master|Last_Error"
sleep 5
done
}
MySQL Replication Monitoring
# Check replication lag
check_replication_lag() {
mysql -u root << 'EOF'
SHOW SLAVE STATUS\G | grep -E "Seconds_Behind_Master|Master_Server_Id|Slave_IO_Running|Slave_SQL_Running"
EOF
}
# Alert on replication lag
monitor_mysql_lag() {
local lag_threshold=10 # seconds
lag=$(mysql -u root -sNe "SHOW SLAVE STATUS\G" | grep "Seconds_Behind_Master:" | awk '{print $NF}')
if [ -z "$lag" ] || [ "$lag" = "NULL" ]; then
echo "ERROR: Replication not running"
return 1
fi
if [ "$lag" -gt "$lag_threshold" ]; then
echo "WARNING: Replication lag is ${lag}s (threshold: ${lag_threshold}s)"
fi
}
# Create monitoring alert
cat > /usr/local/bin/mysql-replication-monitor.sh << 'EOF'
#!/bin/bash
ALERT_EMAIL="[email protected]"
LAG_THRESHOLD=60
ERROR_CHECK_SQL="SHOW SLAVE STATUS\G | grep Last_Error"
check_replica_health() {
local status=$(mysql -u root -sNe "SHOW SLAVE STATUS\G")
# Check if replication is running
if ! echo "$status" | grep -q "Slave_IO_Running: Yes"; then
echo "CRITICAL: Replication IO thread is stopped" | mail -s "MySQL Replication Alert" "$ALERT_EMAIL"
fi
if ! echo "$status" | grep -q "Slave_SQL_Running: Yes"; then
echo "CRITICAL: Replication SQL thread is stopped" | mail -s "MySQL Replication Alert" "$ALERT_EMAIL"
fi
# Check lag
local lag=$(echo "$status" | grep "Seconds_Behind_Master:" | awk '{print $NF}')
if [ "$lag" -gt "$LAG_THRESHOLD" ]; then
echo "WARNING: Replication lag is ${lag}s" | mail -s "MySQL Replication Alert" "$ALERT_EMAIL"
fi
}
check_replica_health
EOF
chmod +x /usr/local/bin/mysql-replication-monitor.sh
PostgreSQL Replication
PostgreSQL uses WAL (Write-Ahead Logging) archiving and streaming replication for cross-datacenter redundancy.
Primary Server Configuration
# Configure PostgreSQL primary for replication
cat > /etc/postgresql/14/main/postgresql.conf << 'EOF'
# Replication settings
wal_level = replica
max_wal_senders = 5
max_replication_slots = 5
hot_standby = on
hot_standby_feedback = on
# WAL archiving
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/wal_archive/%f && cp %p /var/lib/postgresql/wal_archive/%f'
archive_timeout = 300
# Connection settings
listen_addresses = '*'
EOF
# Create WAL archive directory
mkdir -p /var/lib/postgresql/wal_archive
chown postgres:postgres /var/lib/postgresql/wal_archive
chmod 700 /var/lib/postgresql/wal_archive
# Configure replication access
cat >> /etc/postgresql/14/main/pg_hba.conf << 'EOF'
# Allow replication connections from secondary datacenter
host replication repl_user 10.0.2.0/24 md5
EOF
systemctl restart postgresql
Create Replication User
# Create replication user
sudo -u postgres psql << 'EOF'
CREATE USER repl_user WITH REPLICATION ENCRYPTED PASSWORD 'secure_password';
-- Verify role was created
\du repl_user
EOF
Create Base Backup
# Use pg_basebackup for replica initialization
pg_basebackup \
-h 10.0.1.10 \
-U repl_user \
-D /var/lib/postgresql/14/replication-backup \
-Xstream \
-v
Secondary Server Configuration
# Restore base backup to secondary
sudo -u postgres pg_basebackup \
-h 10.0.1.10 \
-U repl_user \
-D /var/lib/postgresql/14/main \
-Xstream \
-R \
-v
# Create recovery.conf (or postgresql.auto.conf)
cat > /var/lib/postgresql/14/main/recovery.conf << 'EOF'
# Streaming replication
primary_conninfo = 'host=10.0.1.10 port=5432 user=repl_user password=secure_password'
primary_slot_name = 'standby1'
# Recovery settings
recovery_target_timeline = 'latest'
EOF
# Adjust permissions
chown postgres:postgres /var/lib/postgresql/14/main/recovery.conf
chmod 600 /var/lib/postgresql/14/main/recovery.conf
# Start PostgreSQL
systemctl start postgresql
PostgreSQL Replication Monitoring
# Check replication status
check_postgresql_replication() {
sudo -u postgres psql << 'EOF'
-- Check standby connections
SELECT
pid,
usename,
application_name,
client_addr,
state,
sync_state,
flush_lsn,
replay_lsn
FROM pg_stat_replication;
-- Check replication slots
SELECT
slot_name,
slot_type,
active,
restart_lsn,
confirmed_flush_lsn
FROM pg_replication_slots;
-- Check WAL level
SHOW wal_level;
EOF
}
# Monitor replication lag
monitor_postgresql_lag() {
sudo -u postgres psql -t << 'EOF'
SELECT
EXTRACT(EPOCH FROM (NOW() - pg_last_xact_replay_timestamp()))::INT as replication_lag_seconds;
EOF
}
# Continuous monitoring script
cat > /usr/local/bin/pg-replication-monitor.sh << 'EOF'
#!/bin/bash
ALERT_EMAIL="[email protected]"
LAG_THRESHOLD=60
monitor_lag() {
local lag=$(sudo -u postgres psql -t -c \
"SELECT EXTRACT(EPOCH FROM (NOW() - pg_last_xact_replay_timestamp()))::INT;")
if [ -z "$lag" ] || [ "$lag" = "NULL" ]; then
echo "CRITICAL: Cannot determine replication lag" | \
mail -s "PostgreSQL Replication Alert" "$ALERT_EMAIL"
return 1
fi
if [ "$lag" -gt "$LAG_THRESHOLD" ]; then
echo "WARNING: PostgreSQL replication lag is ${lag}s" | \
mail -s "PostgreSQL Replication Alert" "$ALERT_EMAIL"
fi
}
monitor_lag
EOF
chmod +x /usr/local/bin/pg-replication-monitor.sh
Storage Synchronization
For non-database storage, use rsync and DRBD for efficient cross-datacenter synchronization.
Rsync-Based Replication
# Rsync configuration for continuous sync
cat > /usr/local/bin/sync-storage.sh << 'EOF'
#!/bin/bash
SOURCE_DIR="/data/storage"
REMOTE_HOST="secondary-dc.com"
REMOTE_USER="backup"
REMOTE_DIR="/data/storage"
LOG_FILE="/var/log/storage-sync.log"
LOCK_FILE="/var/run/storage-sync.lock"
# Prevent concurrent syncs
if [ -f "$LOCK_FILE" ]; then
echo "Sync already running" >> "$LOG_FILE"
exit 1
fi
touch "$LOCK_FILE"
trap "rm -f $LOCK_FILE" EXIT
echo "[$(date)] Starting storage sync" >> "$LOG_FILE"
# Perform rsync with compression and bandwidth limit
rsync -avz \
--bwlimit=50000 \
--delete \
--exclude='.tmp/*' \
--exclude='*.log' \
"$SOURCE_DIR/" \
"$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR" \
>> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
echo "[$(date)] Sync completed successfully" >> "$LOG_FILE"
else
echo "[$(date)] Sync failed with status $?" >> "$LOG_FILE"
fi
EOF
chmod +x /usr/local/bin/sync-storage.sh
# Schedule continuous replication
(crontab -l 2>/dev/null; echo "*/15 * * * * /usr/local/bin/sync-storage.sh") | crontab -
DRBD Configuration (Block-Level Replication)
# DRBD configuration for synchronous replication
cat > /etc/drbd.d/storage.res << 'EOF'
resource storage {
protocol C; # Synchronous replication (waits for acknowledgment)
on primary-server {
device /dev/drbd0;
disk /dev/sda1;
address 10.0.1.20:7788;
meta-disk internal;
}
on secondary-server {
device /dev/drbd0;
disk /dev/sda1;
address 10.0.2.20:7788;
meta-disk internal;
}
net {
# Performance tuning
max-buffers 8192;
max-epoch-size 20000;
sndbuf-size 512K;
rcvbuf-size 512K;
# Fencing
fencing resource-only;
# Data integrity
verify-alg sha256;
}
}
EOF
# Initialize DRBD
drbdadm create-md storage
systemctl start drbd
# Promote primary
drbdadm primary storage --force
# Create filesystem on DRBD device
mkfs.ext4 /dev/drbd0
# Mount the replicated filesystem
mkdir -p /data/replicated
mount /dev/drbd0 /data/replicated
# Verify replication status
cat /proc/drbd
Network Considerations
Bandwidth Planning
# Calculate required bandwidth for replication
cat > /usr/local/bin/bandwidth-calculator.sh << 'EOF'
#!/bin/bash
# Input parameters
database_size_gb=$1 # Total database size in GB
daily_change_percent=$2 # Percentage of data changing daily (0-100)
replication_window_hours=$3 # Available replication window in hours
if [ -z "$database_size_gb" ] || [ -z "$daily_change_percent" ] || [ -z "$replication_window_hours" ]; then
echo "Usage: $0 <size_gb> <change_percent> <window_hours>"
echo "Example: $0 500 5 4"
exit 1
fi
# Calculate required throughput
daily_change_bytes=$((database_size_gb * $daily_change_percent * 10737418)) # GB to bytes
throughput_bytes=$((daily_change_bytes / ($replication_window_hours * 3600)))
throughput_mbps=$((throughput_bytes * 8 / 1000000))
echo "Replication Bandwidth Calculator"
echo "=================================="
echo "Database Size: ${database_size_gb}GB"
echo "Daily Change: ${daily_change_percent}%"
echo "Replication Window: ${replication_window_hours} hours"
echo ""
echo "Required Throughput: ${throughput_mbps} Mbps"
echo "Recommended Link Speed: $((throughput_mbps * 2)) Mbps (with redundancy)"
EOF
chmod +x /usr/local/bin/bandwidth-calculator.sh
bash /usr/local/bin/bandwidth-calculator.sh 500 5 4
Network Optimization
# Optimize network for replication
configure_network_optimization() {
# Increase buffer sizes for TCP window scaling
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728
sysctl -w net.ipv4.tcp_rmem="4096 87380 67108864"
sysctl -w net.ipv4.tcp_wmem="4096 65536 67108864"
# Enable TCP window scaling
sysctl -w net.ipv4.tcp_window_scaling=1
# Optimize for replication (disable Nagle's algorithm)
sysctl -w net.ipv4.tcp_nodelay=1
# Increase backlog
sysctl -w net.core.netdev_max_backlog=5000
# Persist changes
cat >> /etc/sysctl.conf << 'EOF'
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.ipv4.tcp_rmem=4096 87380 67108864
net.ipv4.tcp_wmem=4096 65536 67108864
net.ipv4.tcp_window_scaling=1
net.ipv4.tcp_nodelay=1
net.core.netdev_max_backlog=5000
EOF
}
configure_network_optimization
Monitoring Replication
# Comprehensive replication monitoring
cat > /usr/local/bin/replication-health.sh << 'EOF'
#!/bin/bash
ALERT_EMAIL="[email protected]"
METRICS_FILE="/var/metrics/replication-health.txt"
check_replication_health() {
echo "=== Replication Health Report ===" > "$METRICS_FILE"
echo "Timestamp: $(date)" >> "$METRICS_FILE"
echo "" >> "$METRICS_FILE"
# Check MySQL replication
echo "MySQL Replication Status:" >> "$METRICS_FILE"
mysql -u root -sNe "SHOW SLAVE STATUS\G" >> "$METRICS_FILE" 2>&1
echo "" >> "$METRICS_FILE"
# Check PostgreSQL replication
echo "PostgreSQL Replication Status:" >> "$METRICS_FILE"
sudo -u postgres psql -c "SELECT * FROM pg_stat_replication;" >> "$METRICS_FILE" 2>&1
echo "" >> "$METRICS_FILE"
# Check DRBD status
if command -v drbdadm &> /dev/null; then
echo "DRBD Status:" >> "$METRICS_FILE"
cat /proc/drbd >> "$METRICS_FILE"
echo "" >> "$METRICS_FILE"
fi
# Check network connectivity
echo "Network Connectivity:" >> "$METRICS_FILE"
ping -c 3 secondary-dc.com >> "$METRICS_FILE" 2>&1
echo "" >> "$METRICS_FILE"
}
check_replication_health
cat "$METRICS_FILE"
EOF
chmod +x /usr/local/bin/replication-health.sh
Failover Strategies
Automatic Failover with Keepalived
# Configure automatic failover with Keepalived
cat > /etc/keepalived/keepalived.conf << 'EOF'
global_defs {
router_id DATABASE_HA
script_user root
enable_script_security
}
vrrp_script check_database {
script "/usr/local/bin/check-db-health.sh"
interval 5
weight -20
fall 3
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass database_failover
}
virtual_ipaddress {
10.0.1.50/24
}
track_script {
check_database
}
notify_master "/usr/local/bin/notify-master.sh"
notify_backup "/usr/local/bin/notify-backup.sh"
notify_fault "/usr/local/bin/notify-fault.sh"
}
EOF
# Health check script
cat > /usr/local/bin/check-db-health.sh << 'EOF'
#!/bin/bash
# Check if database is responding
mysql -u health_check -p'password' -e "SELECT 1" > /dev/null 2>&1
if [ $? -eq 0 ]; then
exit 0 # Health check passed
else
exit 1 # Health check failed
fi
EOF
chmod +x /usr/local/bin/check-db-health.sh
systemctl restart keepalived
Manual Failover Procedure
# Manual failover procedure
failover_to_secondary() {
echo "Starting failover to secondary datacenter"
# 1. Stop replication on secondary
echo "Step 1: Stopping replication on secondary"
mysql -h secondary-dc.com -u root << 'EOF'
STOP SLAVE;
SHOW SLAVE STATUS\G;
EOF
# 2. Promote secondary to primary
echo "Step 2: Promoting secondary to primary"
mysql -h secondary-dc.com -u root << 'EOF'
RESET SLAVE ALL;
SHOW MASTER STATUS\G;
EOF
# 3. Update application connection strings
echo "Step 3: Updating application configuration"
# Update /etc/myapp/database.conf
# Restart application services
# 4. Point DNS to new primary
echo "Step 4: Updating DNS records"
# Update DNS to point database.example.com to secondary-dc.com
# 5. Verify connectivity
echo "Step 5: Verifying connectivity"
mysql -h secondary-dc.com -u app_user -p << 'EOF'
SELECT @@hostname, @@server_id, NOW();
EOF
echo "Failover completed"
}
Troubleshooting
# Common replication issues and solutions
# Issue 1: Replication lag
diagnose_replication_lag() {
echo "Diagnosing replication lag..."
# Check binary log position on primary
echo "Primary Binary Log Status:"
mysql -h primary-dc.com -u root -e "SHOW MASTER STATUS\G"
# Check relay log position on secondary
echo "Secondary Relay Log Status:"
mysql -h secondary-dc.com -u root -e "SHOW SLAVE STATUS\G"
# Check for long-running queries
echo "Long-running queries on primary:"
mysql -h primary-dc.com -u root -e "SHOW PROCESSLIST WHERE Time > 300\G"
}
# Issue 2: Replication error
fix_replication_error() {
local secondary_host=$1
echo "Attempting to recover from replication error..."
# Get error details
mysql -h "$secondary_host" -u root << 'EOF'
SHOW SLAVE STATUS\G | grep -i error;
EOF
# Skip the error (use with caution!)
# mysql -h "$secondary_host" -u root -e "SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1; START SLAVE;"
echo "Consult error details before skipping"
}
# Issue 3: Binary log not found
recover_missing_binlog() {
local primary_host=$1
# Get available binlogs
mysql -h "$primary_host" -u root -e "SHOW BINARY LOGS;"
# If log is missing, may need to resync from scratch
echo "If required binlog is missing, perform full resync:"
echo "1. Take new backup on primary"
echo "2. Restore on secondary"
echo "3. Reinitialize replication"
}
Conclusion
Effective cross-datacenter replication requires:
- Database Replication: Use native replication (MySQL binlog, PostgreSQL WAL)
- Storage Sync: Complement with rsync or DRBD for non-database data
- Network Optimization: Ensure sufficient bandwidth and low latency
- Monitoring: Continuous health checks and lag monitoring
- Testing: Regular failover drills to validate procedures
- Documentation: Keep runbooks updated for your specific setup
Start with asynchronous replication for simplicity, then upgrade to synchronous replication for critical systems where data loss is unacceptable. Remember that replication is not a backup—maintain separate backups alongside replication.


