Configure comprehensive TimescaleDB backup solutions with continuous WAL archiving, automated point-in-time recovery procedures, and monitoring systems to ensure data protection and business continuity.
Prerequisites
- Root or sudo access
- PostgreSQL 16+ installed
- At least 20GB free disk space
- Basic PostgreSQL administration knowledge
What this solves
TimescaleDB requires specialized backup strategies that account for its time-series data patterns and hypertable architecture. This tutorial implements production-grade continuous backup with Write-Ahead Log (WAL) archiving, automated point-in-time recovery (PITR) capabilities, and comprehensive monitoring to prevent data loss and minimize recovery time objectives.
Step-by-step configuration
Install TimescaleDB and backup dependencies
Install TimescaleDB extension and PostgreSQL backup utilities for comprehensive backup management.
curl -s https://packagecloud.io/install/repositories/timescale/timescaledb/script.deb.sh | sudo bash
sudo apt update
sudo apt install -y timescaledb-2-postgresql-16 postgresql-client-common pigz pv
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf install -y timescaledb_16 postgresql16-server pigz pv
Configure PostgreSQL for WAL archiving
Enable continuous WAL archiving to support point-in-time recovery and streaming replication.
# WAL configuration for continuous archiving
wal_level = replica
max_wal_senders = 3
max_replication_slots = 3
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/wal_archive/%f && cp %p /var/lib/postgresql/wal_archive/%f'
archive_timeout = 300
# Checkpoint and WAL settings
checkpoint_timeout = 5min
max_wal_size = 2GB
min_wal_size = 80MB
checkpoint_completion_target = 0.9
# Logging for backup monitoring
log_destination = 'csvlog'
logging_collector = on
log_directory = '/var/log/postgresql'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 1d
log_rotation_size = 100MB
Create WAL archive directory structure
Set up directories for WAL archives and backups with proper permissions for the postgres user.
sudo mkdir -p /var/lib/postgresql/wal_archive
sudo mkdir -p /var/lib/postgresql/backups
sudo mkdir -p /var/lib/postgresql/pitr_recovery
sudo chown -R postgres:postgres /var/lib/postgresql/wal_archive
sudo chown -R postgres:postgres /var/lib/postgresql/backups
sudo chown -R postgres:postgres /var/lib/postgresql/pitr_recovery
sudo chmod 750 /var/lib/postgresql/wal_archive
sudo chmod 750 /var/lib/postgresql/backups
Configure TimescaleDB backup script
Create a comprehensive backup script that handles base backups with compression and metadata logging.
#!/bin/bash
# TimescaleDB Backup Script with Compression and Validation
set -euo pipefail
# Configuration
BACKUP_DIR="/var/lib/postgresql/backups"
WAL_ARCHIVE_DIR="/var/lib/postgresql/wal_archive"
DB_NAME="${1:-timescaledb}"
RETENTION_DAYS=7
COMPRESSION_LEVEL=6
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="${DB_NAME}_backup_${TIMESTAMP}"
LOG_FILE="/var/log/postgresql/backup_${TIMESTAMP}.log"
# Logging function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Error handling
trap 'log "ERROR: Backup failed at line $LINENO"' ERR
log "Starting TimescaleDB backup: $BACKUP_NAME"
# Create backup directory
mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}"
# Perform base backup with compression
log "Creating base backup with pg_basebackup"
pg_basebackup -D "${BACKUP_DIR}/${BACKUP_NAME}" \
-Ft -z -Z$COMPRESSION_LEVEL \
-P -v -W \
-X stream \
--label="TimescaleDB_backup_${TIMESTAMP}" 2>&1 | tee -a "$LOG_FILE"
# Create backup metadata
log "Creating backup metadata"
cat > "${BACKUP_DIR}/${BACKUP_NAME}/backup_info.txt" << EOF
Backup Name: $BACKUP_NAME
Database: $DB_NAME
Timestamp: $TIMESTAMP
Backup Type: Full Base Backup
Compression: Level $COMPRESSION_LEVEL
WAL Archive Location: $WAL_ARCHIVE_DIR
Backup Size: $(du -sh "${BACKUP_DIR}/${BACKUP_NAME}" | cut -f1)
PostgreSQL Version: $(psql -t -c "SELECT version();" | head -1)
TimescaleDB Version: $(psql -d $DB_NAME -t -c "SELECT extversion FROM pg_extension WHERE extname='timescaledb';" | head -1)
EOF
# Validate backup integrity
log "Validating backup integrity"
if [ -f "${BACKUP_DIR}/${BACKUP_NAME}/base.tar.gz" ]; then
tar -tzf "${BACKUP_DIR}/${BACKUP_NAME}/base.tar.gz" >/dev/null 2>&1
log "Base backup validation: PASSED"
else
log "ERROR: Base backup file not found"
exit 1
fi
# Cleanup old backups
log "Cleaning up backups older than $RETENTION_DAYS days"
find "$BACKUP_DIR" -name "${DB_NAME}_backup_*" -type d -mtime +$RETENTION_DAYS -exec rm -rf {} +
# Cleanup old WAL archives (keep 2x retention period)
find "$WAL_ARCHIVE_DIR" -name "*.gz" -type f -mtime +$((RETENTION_DAYS * 2)) -delete
log "Backup completed successfully: $BACKUP_NAME"
log "Total backup size: $(du -sh "${BACKUP_DIR}/${BACKUP_NAME}" | cut -f1)"
exit 0
sudo chmod 755 /usr/local/bin/timescale-backup.sh
sudo chown postgres:postgres /usr/local/bin/timescale-backup.sh
Implement point-in-time recovery script
Create an automated PITR script that can restore to any point in time within the WAL archive retention period.
#!/bin/bash
# TimescaleDB Point-in-Time Recovery Script
set -euo pipefail
# Configuration
BACKUP_DIR="/var/lib/postgresql/backups"
WAL_ARCHIVE_DIR="/var/lib/postgresql/wal_archive"
RECOVERY_DIR="/var/lib/postgresql/pitr_recovery"
POSTGRES_DATA_DIR="/var/lib/postgresql/16/main"
TARGET_TIME="${1:-}"
BACKUP_NAME="${2:-}"
RECOVERY_TARGET_ACTION="promote"
# Validation
if [[ -z "$TARGET_TIME" ]]; then
echo "Usage: $0 'YYYY-MM-DD HH:MM:SS' [backup_name]"
echo "Example: $0 '2024-01-15 14:30:00'"
exit 1
fi
# Get latest backup if not specified
if [[ -z "$BACKUP_NAME" ]]; then
BACKUP_NAME=$(find "$BACKUP_DIR" -name "*_backup_*" -type d | sort | tail -1 | basename)
if [[ -z "$BACKUP_NAME" ]]; then
echo "ERROR: No backups found in $BACKUP_DIR"
exit 1
fi
fi
echo "Starting Point-in-Time Recovery"
echo "Target Time: $TARGET_TIME"
echo "Using Backup: $BACKUP_NAME"
echo "Recovery Directory: $RECOVERY_DIR"
# Stop PostgreSQL service
echo "Stopping PostgreSQL service"
sudo systemctl stop postgresql
# Clean recovery directory
echo "Preparing recovery directory"
rm -rf "$RECOVERY_DIR"/*
mkdir -p "$RECOVERY_DIR"
# Extract base backup
echo "Extracting base backup"
cd "$RECOVERY_DIR"
tar -xzf "${BACKUP_DIR}/${BACKUP_NAME}/base.tar.gz"
if [ -f "${BACKUP_DIR}/${BACKUP_NAME}/pg_wal.tar.gz" ]; then
mkdir -p pg_wal
tar -xzf "${BACKUP_DIR}/${BACKUP_NAME}/pg_wal.tar.gz" -C pg_wal
fi
# Create recovery configuration
echo "Creating recovery configuration"
cat > "$RECOVERY_DIR/postgresql.auto.conf" << EOF
# Point-in-Time Recovery Configuration
restore_command = 'cp $WAL_ARCHIVE_DIR/%f %p'
recovery_target_time = '$TARGET_TIME'
recovery_target_action = '$RECOVERY_TARGET_ACTION'
EOF
# Create recovery signal file
touch "$RECOVERY_DIR/recovery.signal"
# Set proper permissions
chown -R postgres:postgres "$RECOVERY_DIR"
chmod 700 "$RECOVERY_DIR"
echo "Recovery setup completed"
echo "To complete recovery:"
echo "1. Backup current data directory: sudo mv $POSTGRES_DATA_DIR ${POSTGRES_DATA_DIR}.backup"
echo "2. Move recovery directory: sudo mv $RECOVERY_DIR $POSTGRES_DATA_DIR"
echo "3. Start PostgreSQL: sudo systemctl start postgresql"
echo "4. Monitor recovery in PostgreSQL logs"
exit 0
sudo chmod 755 /usr/local/bin/timescale-pitr.sh
sudo chown postgres:postgres /usr/local/bin/timescale-pitr.sh
Configure automated backup scheduling
Set up systemd service and timer for automated daily backups with proper logging and error handling.
[Unit]
Description=TimescaleDB Automated Backup Service
After=postgresql.service
Requires=postgresql.service
[Service]
Type=oneshot
User=postgres
Group=postgres
ExecStart=/usr/local/bin/timescale-backup.sh timescaledb
Environment=PGUSER=postgres
Environment=PGDATABASE=timescaledb
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
[Unit]
Description=TimescaleDB Backup Timer
Requires=timescale-backup.service
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable timescale-backup.timer
sudo systemctl start timescale-backup.timer
Implement backup monitoring and alerting
Create monitoring script to validate backup integrity and send alerts on failures.
#!/bin/bash
# TimescaleDB Backup Monitoring Script
set -euo pipefail
BACKUP_DIR="/var/lib/postgresql/backups"
WAL_ARCHIVE_DIR="/var/lib/postgresql/wal_archive"
MAX_BACKUP_AGE_HOURS=26 # Allow up to 26 hours for daily backups
MAX_WAL_AGE_MINUTES=10 # WAL should be archived within 10 minutes
ALERT_EMAIL="admin@example.com"
SMTP_SERVER="localhost"
# Check latest backup age
check_backup_age() {
local latest_backup
latest_backup=$(find "$BACKUP_DIR" -name "*_backup_*" -type d -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)
if [[ -z "$latest_backup" ]]; then
echo "CRITICAL: No backups found in $BACKUP_DIR"
return 1
fi
local backup_time
backup_time=$(stat -c %Y "$latest_backup")
local current_time
current_time=$(date +%s)
local age_hours
age_hours=$(( (current_time - backup_time) / 3600 ))
if [[ $age_hours -gt $MAX_BACKUP_AGE_HOURS ]]; then
echo "CRITICAL: Latest backup is $age_hours hours old (max: $MAX_BACKUP_AGE_HOURS)"
return 1
fi
echo "OK: Latest backup is $age_hours hours old"
return 0
}
# Check WAL archiving
check_wal_archiving() {
local latest_wal
latest_wal=$(find "$WAL_ARCHIVE_DIR" -name "*.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)
if [[ -z "$latest_wal" ]]; then
echo "WARNING: No WAL archives found"
return 1
fi
local wal_time
wal_time=$(stat -c %Y "$latest_wal")
local current_time
current_time=$(date +%s)
local age_minutes
age_minutes=$(( (current_time - wal_time) / 60 ))
if [[ $age_minutes -gt $MAX_WAL_AGE_MINUTES ]]; then
echo "WARNING: Latest WAL archive is $age_minutes minutes old (max: $MAX_WAL_AGE_MINUTES)"
return 1
fi
echo "OK: WAL archiving is current ($age_minutes minutes ago)"
return 0
}
# Send alert email
send_alert() {
local subject="$1"
local message="$2"
if command -v mail >/dev/null 2>&1; then
echo "$message" | mail -s "$subject" "$ALERT_EMAIL"
elif command -v sendmail >/dev/null 2>&1; then
{
echo "Subject: $subject"
echo "To: $ALERT_EMAIL"
echo ""
echo "$message"
} | sendmail "$ALERT_EMAIL"
else
echo "No mail command available for alerts"
fi
}
# Main monitoring logic
main() {
local backup_status=0
local wal_status=0
local backup_result
local wal_result
backup_result=$(check_backup_age) || backup_status=1
wal_result=$(check_wal_archiving) || wal_status=1
echo "Backup Status: $backup_result"
echo "WAL Status: $wal_result"
if [[ $backup_status -ne 0 ]] || [[ $wal_status -ne 0 ]]; then
local alert_message
alert_message="TimescaleDB Backup Alert\n\nBackup Status: $backup_result\nWAL Status: $wal_result\n\nTime: $(date)\nHost: $(hostname)"
send_alert "TimescaleDB Backup Alert - $(hostname)" "$alert_message"
exit 1
fi
echo "All backup systems operating normally"
exit 0
}
main "$@"
sudo chmod 755 /usr/local/bin/timescale-backup-monitor.sh
sudo chown postgres:postgres /usr/local/bin/timescale-backup-monitor.sh
Configure monitoring systemd service
Set up automated monitoring checks every hour to ensure backup systems are functioning properly.
[Unit]
Description=TimescaleDB Backup Monitoring Service
After=postgresql.service
[Service]
Type=oneshot
User=postgres
Group=postgres
ExecStart=/usr/local/bin/timescale-backup-monitor.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=TimescaleDB Backup Monitoring Timer
Requires=timescale-backup-monitor.service
[Timer]
OnBootSec=15min
OnUnitActiveSec=1h
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable timescale-backup-monitor.timer
sudo systemctl start timescale-backup-monitor.timer
Restart PostgreSQL with new configuration
Apply the WAL archiving configuration by restarting the PostgreSQL service.
sudo systemctl restart postgresql
sudo systemctl status postgresql
Initialize TimescaleDB in test database
Create a test database with TimescaleDB extension to verify backup functionality.
sudo -u postgres createdb timescaledb
sudo -u postgres psql -d timescaledb -c "CREATE EXTENSION IF NOT EXISTS timescaledb;"
sudo -u postgres psql -d timescaledb -c "SELECT timescaledb_pre_restore();"
sudo -u postgres psql -d timescaledb -c "SELECT timescaledb_post_restore();"
Test backup and recovery procedures
Perform initial backup test
Execute the backup script manually to verify functionality and generate baseline backup.
sudo -u postgres /usr/local/bin/timescale-backup.sh timescaledb
Verify WAL archiving
Check that WAL files are being archived automatically by PostgreSQL.
sudo -u postgres psql -c "SELECT pg_switch_wal();"
sleep 30
ls -la /var/lib/postgresql/wal_archive/
Test point-in-time recovery setup
Prepare PITR configuration without actually performing recovery to validate the process.
sudo -u postgres /usr/local/bin/timescale-pitr.sh "$(date '+%Y-%m-%d %H:%M:%S')" --dry-run
Verify your setup
# Check backup timer status
sudo systemctl status timescale-backup.timer
# Check monitoring timer status
sudo systemctl status timescale-backup-monitor.timer
# Verify WAL archiving is enabled
sudo -u postgres psql -c "SHOW archive_mode;"
sudo -u postgres psql -c "SHOW archive_command;"
# Check recent backups
ls -la /var/lib/postgresql/backups/
# Verify WAL archives
ls -la /var/lib/postgresql/wal_archive/
# Test backup monitoring
sudo -u postgres /usr/local/bin/timescale-backup-monitor.sh
# View backup logs
sudo journalctl -u timescale-backup.service --no-pager
Configure retention and cleanup policies
Implement advanced WAL cleanup
Create intelligent WAL archive cleanup that respects backup retention periods and recovery requirements.
#!/bin/bash
# TimescaleDB WAL Archive Cleanup Script
set -euo pipefail
WAL_ARCHIVE_DIR="/var/lib/postgresql/wal_archive"
BACKUP_DIR="/var/lib/postgresql/backups"
RETENTION_DAYS=14
DRY_RUN=${1:-false}
# Find oldest backup to determine safe cleanup point
oldest_backup=$(find "$BACKUP_DIR" -name "*_backup_*" -type d -printf '%T@ %p\n' | sort -n | head -1 | cut -d' ' -f2-)
if [[ -n "$oldest_backup" ]]; then
# Get backup start WAL from backup_label
backup_start_wal=$(find "$oldest_backup" -name "backup_label*" -exec cat {} \; | grep "START WAL LOCATION" | awk '{print $4}' | head -1)
if [[ -n "$backup_start_wal" ]]; then
echo "Oldest backup start WAL: $backup_start_wal"
# Remove WAL files older than oldest backup (safely)
find "$WAL_ARCHIVE_DIR" -name "*.gz" -type f -mtime +$RETENTION_DAYS | while read -r wal_file; do
if [[ "$DRY_RUN" == "true" ]]; then
echo "Would remove: $wal_file"
else
echo "Removing old WAL: $wal_file"
rm -f "$wal_file"
fi
done
fi
fi
echo "WAL cleanup completed"
sudo chmod 755 /usr/local/bin/timescale-wal-cleanup.sh
sudo chown postgres:postgres /usr/local/bin/timescale-wal-cleanup.sh
Schedule WAL cleanup automation
Configure daily WAL cleanup to maintain optimal archive sizes while preserving recovery capabilities.
[Unit]
Description=TimescaleDB WAL Archive Cleanup Service
After=postgresql.service
[Service]
Type=oneshot
User=postgres
Group=postgres
ExecStart=/usr/local/bin/timescale-wal-cleanup.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=TimescaleDB WAL Cleanup Timer
Requires=timescale-wal-cleanup.service
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=600
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# TimescaleDB Backup Strategy & PITR Setup Script
# Usage: ./install_timescaledb_backup.sh [database_name]
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
DB_NAME="${1:-timescaledb}"
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
exit 1
}
cleanup() {
if [ $? -ne 0 ]; then
error "Installation failed. Check logs for details."
fi
}
trap cleanup ERR
# Check prerequisites
if [ $EUID -ne 0 ]; then
error "This script must be run as root"
fi
echo "[1/8] Detecting distribution and package manager..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
PG_VERSION="16"
PG_CONFIG_DIR="/etc/postgresql/16/main"
PG_DATA_DIR="/var/lib/postgresql/16/main"
PG_SERVICE="postgresql"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
PG_VERSION="16"
PG_CONFIG_DIR="/var/lib/pgsql/16/data"
PG_DATA_DIR="/var/lib/pgsql/16/data"
PG_SERVICE="postgresql-16"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
PG_VERSION="16"
PG_CONFIG_DIR="/var/lib/pgsql/data"
PG_DATA_DIR="/var/lib/pgsql/data"
PG_SERVICE="postgresql"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
PG_VERSION="16"
PG_CONFIG_DIR="/var/lib/pgsql/16/data"
PG_DATA_DIR="/var/lib/pgsql/16/data"
PG_SERVICE="postgresql-16"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
log "Detected: $PRETTY_NAME using $PKG_MGR"
else
error "Cannot detect distribution. /etc/os-release not found."
fi
echo "[2/8] Installing TimescaleDB and dependencies..."
case "$ID" in
ubuntu|debian)
curl -s https://packagecloud.io/install/repositories/timescale/timescaledb/script.deb.sh | bash
$PKG_UPDATE
$PKG_INSTALL timescaledb-2-postgresql-16 postgresql-client-common pigz pv postgresql-16
;;
almalinux|rocky|centos|rhel|ol|amzn)
$PKG_INSTALL https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm || true
curl -s https://packagecloud.io/install/repositories/timescale/timescaledb/script.rpm.sh | bash
$PKG_INSTALL timescaledb_16 postgresql16-server postgresql16 pigz pv
;;
fedora)
$PKG_INSTALL postgresql-server postgresql timescaledb pigz pv
;;
esac
echo "[3/8] Initializing PostgreSQL if needed..."
if [ ! -d "$PG_DATA_DIR" ] || [ -z "$(ls -A $PG_DATA_DIR)" ]; then
case "$ID" in
ubuntu|debian)
systemctl start postgresql
;;
*)
postgresql-setup --initdb || /usr/pgsql-16/bin/postgresql-16-setup initdb || true
;;
esac
fi
echo "[4/8] Creating backup directories..."
mkdir -p /var/lib/postgresql/wal_archive
mkdir -p /var/lib/postgresql/backups
mkdir -p /var/lib/postgresql/pitr_recovery
mkdir -p /var/log/postgresql
chown -R postgres:postgres /var/lib/postgresql/wal_archive
chown -R postgres:postgres /var/lib/postgresql/backups
chown -R postgres:postgres /var/lib/postgresql/pitr_recovery
chmod 750 /var/lib/postgresql/wal_archive
chmod 750 /var/lib/postgresql/backups
chmod 750 /var/lib/postgresql/pitr_recovery
echo "[5/8] Configuring PostgreSQL for WAL archiving..."
PG_CONF="$PG_CONFIG_DIR/postgresql.conf"
if [ -f "$PG_CONF" ]; then
cp "$PG_CONF" "$PG_CONF.backup"
cat >> "$PG_CONF" << 'EOF'
# TimescaleDB Backup Configuration
wal_level = replica
max_wal_senders = 3
max_replication_slots = 3
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/wal_archive/%f && cp %p /var/lib/postgresql/wal_archive/%f'
archive_timeout = 300
# Checkpoint and WAL settings
checkpoint_timeout = 5min
max_wal_size = 2GB
min_wal_size = 80MB
checkpoint_completion_target = 0.9
# Logging for backup monitoring
log_destination = 'csvlog'
logging_collector = on
log_directory = '/var/log/postgresql'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 1d
log_rotation_size = 100MB
EOF
log "PostgreSQL configuration updated"
else
error "PostgreSQL configuration file not found at $PG_CONF"
fi
echo "[6/8] Creating backup script..."
cat > /usr/local/bin/timescaledb_backup.sh << 'EOF'
#!/bin/bash
set -euo pipefail
# Configuration
BACKUP_DIR="/var/lib/postgresql/backups"
WAL_ARCHIVE_DIR="/var/lib/postgresql/wal_archive"
DB_NAME="${1:-timescaledb}"
RETENTION_DAYS=7
COMPRESSION_LEVEL=6
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="${DB_NAME}_backup_${TIMESTAMP}"
LOG_FILE="/var/log/postgresql/backup_${TIMESTAMP}.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
trap 'log "ERROR: Backup failed at line $LINENO"' ERR
log "Starting TimescaleDB backup: $BACKUP_NAME"
mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}"
log "Creating base backup with pg_basebackup"
sudo -u postgres pg_basebackup -D "${BACKUP_DIR}/${BACKUP_NAME}" \
-Ft -z -Z$COMPRESSION_LEVEL \
-P -v \
-X stream \
--label="TimescaleDB_backup_${TIMESTAMP}"
log "Creating backup metadata"
cat > "${BACKUP_DIR}/${BACKUP_NAME}/backup_info.txt" << METADATA
Backup Name: $BACKUP_NAME
Database: $DB_NAME
Timestamp: $TIMESTAMP
Backup Type: Full base backup with WAL
Compression: Level $COMPRESSION_LEVEL
METADATA
if [ -f "${BACKUP_DIR}/${BACKUP_NAME}/base.tar.gz" ]; then
log "Base backup validation: PASSED"
else
log "ERROR: Base backup file not found"
exit 1
fi
log "Cleaning up backups older than $RETENTION_DAYS days"
find "$BACKUP_DIR" -name "${DB_NAME}_backup_*" -type d -mtime +$RETENTION_DAYS -exec rm -rf {} + || true
find "$WAL_ARCHIVE_DIR" -name "*.gz" -type f -mtime +$((RETENTION_DAYS * 2)) -delete || true
log "Backup completed successfully: $BACKUP_NAME"
EOF
chmod 755 /usr/local/bin/timescaledb_backup.sh
chown root:root /usr/local/bin/timescaledb_backup.sh
echo "[7/8] Creating recovery script..."
cat > /usr/local/bin/timescaledb_recovery.sh << 'EOF'
#!/bin/bash
set -euo pipefail
BACKUP_NAME="$1"
RECOVERY_TARGET_TIME="${2:-}"
BACKUP_DIR="/var/lib/postgresql/backups"
RECOVERY_DIR="/var/lib/postgresql/pitr_recovery"
if [ -z "$BACKUP_NAME" ]; then
echo "Usage: $0 <backup_name> [recovery_target_time]"
echo "Available backups:"
ls -la "$BACKUP_DIR" | grep backup_
exit 1
fi
echo "Starting point-in-time recovery from backup: $BACKUP_NAME"
systemctl stop postgresql
rm -rf "$RECOVERY_DIR"/*
tar -xzf "$BACKUP_DIR/$BACKUP_NAME/base.tar.gz" -C "$RECOVERY_DIR"
if [ -n "$RECOVERY_TARGET_TIME" ]; then
echo "restore_command = 'cp /var/lib/postgresql/wal_archive/%f %p'" > "$RECOVERY_DIR/recovery.conf"
echo "recovery_target_time = '$RECOVERY_TARGET_TIME'" >> "$RECOVERY_DIR/recovery.conf"
echo "recovery_target_action = 'promote'" >> "$RECOVERY_DIR/recovery.conf"
fi
chown -R postgres:postgres "$RECOVERY_DIR"
echo "Recovery prepared. Manual intervention required to complete."
EOF
chmod 755 /usr/local/bin/timescaledb_recovery.sh
chown root:root /usr/local/bin/timescaledb_recovery.sh
echo "[8/8] Starting and enabling services..."
systemctl enable $PG_SERVICE
systemctl restart $PG_SERVICE
sleep 5
echo "Setting up cron job for daily backups..."
(crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/timescaledb_backup.sh $DB_NAME") | crontab -
log "Verifying installation..."
if systemctl is-active --quiet $PG_SERVICE; then
log "PostgreSQL service is running"
else
error "PostgreSQL service failed to start"
fi
if [ -d "/var/lib/postgresql/wal_archive" ]; then
log "WAL archive directory created successfully"
fi
if [ -x "/usr/local/bin/timescaledb_backup.sh" ]; then
log "Backup script installed successfully"
fi
log "TimescaleDB backup strategy installation completed!"
log "- Backup script: /usr/local/bin/timescaledb_backup.sh"
log "- Recovery script: /usr/local/bin/timescaledb_recovery.sh"
log "- Daily backups scheduled at 2 AM"
log "- WAL archives: /var/lib/postgresql/wal_archive"
log "- Backups stored: /var/lib/postgresql/backups"
Review the script before running. Execute with: bash install.sh