Enterprise Oracle RMAN Backup Automation to Oracle Cloud Infrastructure
Database administrators face a critical challenge: ensuring reliable, cost-effective backup strategies that protect against both local hardware failures and provide efficient disaster recovery capabilities. This comprehensive guide presents an enterprise-grade solution that automates Oracle RMAN backups with intelligent optimization and seamless integration with Oracle Cloud Infrastructure (OCI) Object Storage.
Key Benefits of This Solution:
- Automated full, incremental, and archive log backup strategies with parallel processing
- Comprehensive error handling and validation for production reliability
- Seamless cloud integration with automatic upload and verification
- Built-in cleanup and maintenance procedures
The script addresses a common problem: traditional backup scripts often backup the same archive logs repeatedly, wasting storage space and network bandwidth. Our optimized solution ensures each archive log is backed up exactly once, dramatically improving efficiency.
Understanding the Backup Architecture
The Three-Tier Strategy
This solution implements a sophisticated backup approach that balances recovery objectives with operational efficiency:
Full Database Backups (Weekly Foundation)
Full backups create Level 0 incremental backups that serve as the foundation for all subsequent incremental backups. Using four parallel RMAN channels, these backups can reduce backup windows significantly. For example, a 500GB database that might take 4 hours to backup serially could complete in approximately 1.5 hours with parallel processing.
Incremental Backups (Daily Efficiency)
Level 1 incremental backups capture only changed data blocks since the last backup. In typical production environments where daily changes represent 5-10% of total database size, this approach can reduce backup storage by 90% compared to daily full backups.
Archive Log Backups (Continuous Protection with Smart Deduplication)
The revolutionary feature of this script is its intelligent archive log handling. Traditional approaches backup all available archive logs every execution, creating massive redundancy. Our solution uses RMAN's tag-based tracking to backup each archive log exactly once.
Execution Flow (Very Important)
START
↓
main()
↓
validate_environment
↓
perform_rman_backup
├── fail → exit
↓
upload_to_oci
├── fail → exit (backup kept locally)
↓
cleanup_local
↓
SUCCESS MESSAGE
↓
END
↓
main()
↓
validate_environment
↓
perform_rman_backup
├── fail → exit
↓
upload_to_oci
├── fail → exit (backup kept locally)
↓
cleanup_local
↓
SUCCESS MESSAGE
↓
END
The Complete Production Script
Here's the enterprise-ready script with dummy data for security:
#!/bin/bash ################################################################################ # Enterprise RMAN Backup to OCI - With Atomic Locking and 10-Hour Retention # Location: /home/oracle/scripts/rman_oci_backup.sh # Usage: ./rman_oci_backup.sh <FULL|INCREMENTAL|ARCHIVELOG> # Database: ACMEPROD | Maintained by: Database Operations Team ################################################################################ # Source Oracle Environment FIRST (critical for consistent ORACLE_SID) if [[ -f /home/oracle/.bash_profile ]]; then . /home/oracle/.bash_profile else echo "ERROR: Cannot source Oracle environment from /home/oracle/.bash_profile" exit 1 fi ################################################################################ # MANDATORY PARAMETER VALIDATION ################################################################################ if [[ -z "$1" ]]; then echo "==========================================================" echo "ERROR: Backup type parameter is required!" echo "==========================================================" echo "Usage: $0 <FULL|INCREMENTAL|ARCHIVELOG>" echo "" echo "Examples:" echo " $0 FULL # Full database backup (Level 0)" echo " $0 INCREMENTAL # Incremental backup (Level 1)" echo " $0 ARCHIVELOG # Archive log backup only" echo "" echo "Database: ${ORACLE_SID:-NOT SET}" echo "==========================================================" exit 1 fi BACKUP_TYPE=$(echo "$1" | tr '[:lower:]' '[:upper:]') # Validate backup type case "$BACKUP_TYPE" in FULL|INCREMENTAL|ARCHIVELOG) # Valid backup type ;; *) echo "ERROR: Invalid backup type '$BACKUP_TYPE'" echo "Allowed types: FULL, INCREMENTAL, ARCHIVELOG" exit 1 ;; esac ################################################################################ # CONFIGURATION SECTION - UPDATE FOR YOUR ENVIRONMENT ################################################################################ # Directory Configuration BACKUP_BASE="/u02/rman_staging" LOG_DIR="/home/oracle/logs/backup_operations" # OCI Configuration - UPDATE THESE VALUES OCI_CONFIG_FILE="/home/oracle/.oci/config" OCI_BUCKET="acme-prod-backups" OCI_PREFIX="ACMEPROD_RMAN" # Retention Configuration - UPDATED TO 10 HOURS LOCAL_RETENTION_HOURS=10 LOG_RETENTION_DAYS=7 # System Variables DATE_STAMP=$(date +%Y%m%d) TIME_STAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="${BACKUP_BASE}/${DATE_STAMP}" LOG_FILE="${LOG_DIR}/backup_${BACKUP_TYPE}_${TIME_STAMP}.log" # Create required directories mkdir -p "$LOG_DIR" "$BACKUP_DIR" ################################################################################ # ATOMIC LOCKING SYSTEM ################################################################################ # Validate ORACLE_SID before creating lock if [[ -z "$ORACLE_SID" ]]; then echo "CRITICAL ERROR: ORACLE_SID is not set!" echo "This prevents proper lock file creation and could allow concurrent backups." echo "Please check your Oracle environment setup." exit 1 fi # Lock file configuration - SINGLE GLOBAL LOCK PER DATABASE LOCK_DIR="/var/lock/rman_oci" LOCK_FILE="${LOCK_DIR}/rman_backup_${ORACLE_SID}_GLOBAL.lock" # Create lock directory if it doesn't exist if ! mkdir -p "$LOCK_DIR" 2>/dev/null; then # Fallback to tmp directory LOCK_DIR="/tmp/rman_oci_locks" LOCK_FILE="${LOCK_DIR}/rman_backup_${ORACLE_SID}_GLOBAL.lock" mkdir -p "$LOCK_DIR" fi # Open file descriptor 200 for locking exec 200>"$LOCK_FILE" ################################################################################ # LOGGING FUNCTIONS ################################################################################ log_msg() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } log_err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$LOG_FILE" >&2 } log_ok() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" | tee -a "$LOG_FILE" } ################################################################################ # COMPREHENSIVE LOCK MANAGEMENT ################################################################################ acquire_lock() { log_msg "==========================================================" log_msg "INITIALIZING ATOMIC BACKUP LOCK SYSTEM" log_msg "==========================================================" log_msg "Requested Backup Type: $BACKUP_TYPE" log_msg "Database SID: $ORACLE_SID" log_msg "Process ID: $$" log_msg "Lock File: $LOCK_FILE" log_msg "==========================================================" # Attempt to acquire exclusive lock (non-blocking) if ! flock -n 200; then log_err "==========================================================" log_err "BACKUP CONFLICT DETECTED - CANNOT PROCEED" log_err "==========================================================" # Try to get information about the conflicting process local lock_info="" if [[ -f "$LOCK_FILE" ]]; then lock_info=$(cat "$LOCK_FILE" 2>/dev/null) fi if [[ -n "$lock_info" ]]; then log_err "Another RMAN backup is currently running on database: $ORACLE_SID" log_err "Lock file contents:" echo "$lock_info" | while IFS= read -r line; do log_err " $line" done else log_err "Another RMAN backup process has locked this database: $ORACLE_SID" fi # Show any running RMAN processes for additional context local rman_processes rman_processes=$(ps -ef | grep -E "(rman target|rman_oci_backup)" | grep -v grep | grep -v "$$") if [[ -n "$rman_processes" ]]; then log_err "" log_err "Currently running RMAN-related processes:" echo "$rman_processes" | while IFS= read -r line; do log_err " $line" done fi log_err "" log_err "RESOLUTION OPTIONS:" log_err " 1. WAIT: Allow current backup to complete" log_err " 2. MONITOR: Check running processes with 'ps -ef | grep rman'" log_err " 3. EMERGENCY: Force unlock with 'rm -f $LOCK_FILE' (CAUTION!)" log_err "==========================================================" return 1 fi # Lock acquired successfully - write process information cat > "$LOCK_FILE" <<LOCKINFO PID: $$ BACKUP_TYPE: $BACKUP_TYPE START_TIME: $(date '+%Y-%m-%d %H:%M:%S') START_TIMESTAMP: $(date +%s) HOSTNAME: $(hostname) USER: $(whoami) ORACLE_SID: $ORACLE_SID SCRIPT_PATH: $0 LOCKINFO log_ok "==========================================================" log_ok "EXCLUSIVE BACKUP LOCK ACQUIRED SUCCESSFULLY" log_ok "==========================================================" log_ok "This process now has exclusive backup rights for: $ORACLE_SID" log_ok "No other backup operations can start until this completes" log_ok "==========================================================" return 0 } release_lock() { # Lock is automatically released when file descriptor 200 closes # But we can also clean up the lock file for tidiness if [[ -f "$LOCK_FILE" ]]; then local lock_pid lock_pid=$(grep "^PID:" "$LOCK_FILE" 2>/dev/null | cut -d' ' -f2) # Only clean up if this process owns the lock if [[ "$lock_pid" == "$$" ]]; then rm -f "$LOCK_FILE" 2>/dev/null log_msg "Lock file cleaned up successfully" fi fi log_msg "==========================================================" log_ok "BACKUP LOCK RELEASED" log_msg "Other backup operations can now proceed" log_msg "==========================================================" } # Ensure lock is released on any exit condition trap 'release_lock' EXIT ################################################################################ # ENVIRONMENT VALIDATION ################################################################################ validate_environment() { log_msg "Starting comprehensive environment validation..." # Check Oracle Environment Variables if [[ -z "$ORACLE_SID" || -z "$ORACLE_HOME" ]]; then log_err "Oracle environment not properly configured" log_err "ORACLE_SID: ${ORACLE_SID:-NOT SET}" log_err "ORACLE_HOME: ${ORACLE_HOME:-NOT SET}" log_err "Verify /home/oracle/.bash_profile contains correct settings" exit 1 fi log_ok "Oracle environment validated: SID=$ORACLE_SID, HOME=$ORACLE_HOME" # Verify Database Status local db_status db_status=$(sqlplus -s / as sysdba <<EOF SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF SELECT status FROM v\$instance; EXIT; EOF ) db_status=$(echo "$db_status" | tr -d '[:space:]') if [[ "$db_status" != "OPEN" ]]; then log_err "Database not in OPEN state. Current status: $db_status" exit 1 fi log_ok "Database status verified: $db_status" # Verify ARCHIVELOG Mode local log_mode log_mode=$(sqlplus -s / as sysdba <<EOF SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF SELECT log_mode FROM v\$database; EXIT; EOF ) log_mode=$(echo "$log_mode" | tr -d '[:space:]') if [[ "$log_mode" != "ARCHIVELOG" ]]; then log_err "Database not in ARCHIVELOG mode. Current: $log_mode" exit 1 fi log_ok "Database ARCHIVELOG mode confirmed" # Check Disk Space (minimum 50GB required for 10-hour retention) local avail_space avail_space=$(df -BG "$BACKUP_BASE" | tail -1 | awk '{print $4}' | sed 's/G//') if (( avail_space < 50 )); then log_err "Insufficient disk space: ${avail_space}GB (minimum 50GB required for 10-hour retention)" log_err "Required space: Database Size + (2 × Daily Archive Logs) + 20% buffer" exit 1 fi log_ok "Disk space validated: ${avail_space}GB available" # Verify OCI CLI and Connectivity if ! command -v oci >/dev/null; then log_err "OCI CLI not found in PATH" exit 1 fi if ! oci os ns get --config-file "$OCI_CONFIG_FILE" >/dev/null 2>&1; then log_err "OCI authentication failed. Check config: $OCI_CONFIG_FILE" exit 1 fi if ! oci os bucket get --bucket-name "$OCI_BUCKET" --config-file "$OCI_CONFIG_FILE" >/dev/null 2>&1; then log_err "Cannot access OCI bucket: $OCI_BUCKET" exit 1 fi log_ok "OCI connectivity and bucket access verified" } ################################################################################ # DISK SPACE MONITORING ################################################################################ check_disk_space() { local avail_space avail_space=$(df -BG "$BACKUP_BASE" | tail -1 | awk '{print $4}' | sed 's/G//') local used_percent used_percent=$(df -h "$BACKUP_BASE" | tail -1 | awk '{print $5}' | sed 's/%//') log_msg "Disk space status: ${avail_space}GB available (${used_percent}% used)" if (( used_percent > 85 )); then log_err "WARNING: Disk usage exceeds 85% threshold" log_err "Consider reducing LOCAL_RETENTION_HOURS or expanding storage" fi } ################################################################################ # RMAN BACKUP EXECUTION WITH OPTIMIZATION ################################################################################ perform_rman_backup() { log_msg "Starting RMAN $BACKUP_TYPE backup operation" log_msg "Target database: $ORACLE_SID | Staging: $BACKUP_DIR" local rman_commands="" case "$BACKUP_TYPE" in FULL) local backup_tag="ACME_FULL_${DATE_STAMP}" log_msg "Executing FULL backup with 4 parallel channels" rman_commands=" RUN { ALLOCATE CHANNEL ch1 DEVICE TYPE DISK FORMAT '$BACKUP_DIR/FULL_%d_%T_%U.bkp'; ALLOCATE CHANNEL ch2 DEVICE TYPE DISK FORMAT '$BACKUP_DIR/FULL_%d_%T_%U.bkp'; ALLOCATE CHANNEL ch3 DEVICE TYPE DISK FORMAT '$BACKUP_DIR/FULL_%d_%T_%U.bkp'; ALLOCATE CHANNEL ch4 DEVICE TYPE DISK FORMAT '$BACKUP_DIR/FULL_%d_%T_%U.bkp'; BACKUP AS COMPRESSED BACKUPSET INCREMENTAL LEVEL 0 DATABASE TAG '$backup_tag' PLUS ARCHIVELOG TAG '${backup_tag}_ARCH'; BACKUP CURRENT CONTROLFILE FORMAT '$BACKUP_DIR/CTL_%d_%T_%U.ctl' TAG '$backup_tag'; BACKUP SPFILE FORMAT '$BACKUP_DIR/SPF_%d_%T_%U.ora' TAG '$backup_tag'; RELEASE CHANNEL ch1; RELEASE CHANNEL ch2; RELEASE CHANNEL ch3; RELEASE CHANNEL ch4; }" ;; INCREMENTAL) local backup_tag="ACME_INCR_${DATE_STAMP}" log_msg "Executing INCREMENTAL backup with 3 parallel channels" rman_commands=" RUN { ALLOCATE CHANNEL ch1 DEVICE TYPE DISK FORMAT '$BACKUP_DIR/INCR_%d_%T_%U.bkp'; ALLOCATE CHANNEL ch2 DEVICE TYPE DISK FORMAT '$BACKUP_DIR/INCR_%d_%T_%U.bkp'; ALLOCATE CHANNEL ch3 DEVICE TYPE DISK FORMAT '$BACKUP_DIR/INCR_%d_%T_%U.bkp'; BACKUP AS COMPRESSED BACKUPSET INCREMENTAL LEVEL 1 DATABASE TAG '$backup_tag' PLUS ARCHIVELOG TAG '${backup_tag}_ARCH'; BACKUP CURRENT CONTROLFILE FORMAT '$BACKUP_DIR/CTL_%d_%T_%U.ctl' TAG '$backup_tag'; RELEASE CHANNEL ch1; RELEASE CHANNEL ch2; RELEASE CHANNEL ch3; }" ;; ARCHIVELOG) log_msg "Executing OPTIMIZED archive log backup" log_msg "Strategy: Backup only NEW archive logs using intelligent deduplication" rman_commands=" RUN { ALLOCATE CHANNEL ch1 DEVICE TYPE DISK FORMAT '$BACKUP_DIR/ARCH_%d_%T_%U.bkp'; BACKUP AS COMPRESSED BACKUPSET ARCHIVELOG ALL NOT BACKED UP 1 TIMES TAG 'ACME_ARCH_SECONDARY'; BACKUP CURRENT CONTROLFILE FORMAT '$BACKUP_DIR/CTL_%d_%T_%U.ctl' TAG 'ACME_ARCH_CTL'; RELEASE CHANNEL ch1; }" ;; esac # Execute RMAN rman target / << RMANEOF >> "$LOG_FILE" 2>&1 $rman_commands LIST BACKUP SUMMARY; EXIT; RMANEOF # Check RMAN Status if [[ $? -ne 0 ]] || grep -qi "RMAN-[0-9]*.*ERROR" "$LOG_FILE"; then log_err "RMAN backup failed. Check $LOG_FILE for details" return 1 fi # Verify Files Created local file_count file_count=$(find "$BACKUP_DIR" -type f 2>/dev/null | wc -l) if [[ $file_count -eq 0 ]]; then log_msg "No new files to backup (optimization prevented unnecessary backup)" return 0 fi local total_size total_size=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1) log_ok "RMAN backup completed: $file_count files, $total_size" return 0 } ################################################################################ # OCI UPLOAD WITH VERIFICATION AND RETRY LOGIC ################################################################################ upload_to_oci() { log_msg "Starting OCI upload to $OCI_BUCKET/$OCI_PREFIX/$DATE_STAMP/" local file_count file_count=$(find "$BACKUP_DIR" -type f 2>/dev/null | wc -l) if [[ $file_count -eq 0 ]]; then log_msg "No files to upload (no new backups created)" return 0 fi local total_size total_size=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1) log_msg "Uploading $file_count files ($total_size) to OCI Object Storage" # Execute bulk upload to OCI Object Storage if ! oci os object bulk-upload \ --config-file "$OCI_CONFIG_FILE" \ --bucket-name "$OCI_BUCKET" \ --src-dir "$BACKUP_DIR" \ --prefix "$OCI_PREFIX/$DATE_STAMP/" \ --overwrite >> "$LOG_FILE" 2>&1; then log_err "OCI upload failed" return 1 fi # Verify Upload with retry logic local retry_count=0 local max_retries=3 local uploaded_count=0 while [[ $retry_count -lt $max_retries ]]; do sleep 5 uploaded_count=$(oci os object list \ --config-file "$OCI_CONFIG_FILE" \ --bucket-name "$OCI_BUCKET" \ --prefix "$OCI_PREFIX/$DATE_STAMP/" \ --all 2>/dev/null | grep -c '"name"' || echo "0") if (( uploaded_count >= file_count )); then log_ok "Upload verified: $uploaded_count files in OCI" # Create upload verification marker with timestamp touch "${BACKUP_DIR}/.uploaded_$(date +%s)" log_msg "Upload marker created for retention management" return 0 fi ((retry_count++)) log_msg "Verification attempt $retry_count: Found $uploaded_count of $file_count files" done log_err "Upload verification failed after $max_retries attempts" log_err "Expected $file_count files, found $uploaded_count" return 1 } ################################################################################ # BACKUP INVENTORY DISPLAY ################################################################################ display_backup_inventory() { local current_dirs current_dirs=$(find "$BACKUP_BASE" -maxdepth 1 -type d -name "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]" 2>/dev/null | wc -l) if [[ $current_dirs -gt 0 ]]; then local total_local_size total_local_size=$(du -sh "$BACKUP_BASE" 2>/dev/null | cut -f1) log_msg "==========================================" log_msg "Local Backup Inventory Summary" log_msg "==========================================" log_msg "Total directories: $current_dirs | Total size: $total_local_size" log_msg "" find "$BACKUP_BASE" -maxdepth 1 -type d -name "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]" 2>/dev/null | sort -r | while read -r dir_path; do local dir dir=$(basename "$dir_path") local dir_size dir_size=$(du -sh "$dir_path" 2>/dev/null | cut -f1) local dir_age_hours dir_age_hours=$(( ($(date +%s) - $(stat -c %Y "$dir_path" 2>/dev/null || echo "0")) / 3600 )) # SAFE glob handling for upload status local upload_status="Not uploaded" shopt -s nullglob local markers=("$dir_path"/.uploaded_*) shopt -u nullglob if (( ${#markers[@]} > 0 )); then upload_status="Uploaded to OCI" log_ok " $dir: $dir_size, ${dir_age_hours}h old, $upload_status" else log_msg " $dir: $dir_size, ${dir_age_hours}h old, $upload_status" fi done log_msg "==========================================" else log_msg "No local backup directories found" fi } ################################################################################ # ENHANCED CLEANUP WITH 10-HOUR RETENTION (SHELL-COMPATIBLE) ################################################################################ cleanup_local() { log_msg "Starting cleanup with ${LOCAL_RETENTION_HOURS}-hour local retention policy..." log_msg "Current backup preserved: $BACKUP_DIR (retained for ${LOCAL_RETENTION_HOURS}h)" # Calculate retention threshold in minutes for precise control local retention_minutes=$((LOCAL_RETENTION_HOURS * 60)) log_msg "Retention threshold: ${LOCAL_RETENTION_HOURS} hours (${retention_minutes} minutes)" # Find and process directories older than retention period local cleaned_count=0 local cleaned_size_mb=0 local preserved_count=0 # Create secure temporary file for directory list (FIXES SYNTAX ERROR) local temp_dirs temp_dirs=$(mktemp) || { log_err "Failed to create temporary file for cleanup" return 1 } # Find directories older than retention period find "$BACKUP_BASE" -maxdepth 1 -type d \ -name "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]" \ -mmin +${retention_minutes} 2>/dev/null > "$temp_dirs" # Process each old directory while IFS= read -r old_dir; do [[ -z "$old_dir" ]] && continue local dir_name dir_name=$(basename "$old_dir") # Verify it's a date-formatted directory (8 digits) if [[ ! "$dir_name" =~ ^[0-9]{8}$ ]]; then log_msg "Skipping non-date directory: $dir_name" continue fi # Skip current backup directory explicitly if [[ "$old_dir" == "$BACKUP_DIR" ]]; then log_msg "Preserving current backup: $dir_name" continue fi # SAFE glob handling for upload markers shopt -s nullglob local markers=("$old_dir"/.uploaded_*) shopt -u nullglob if (( ${#markers[@]} == 0 )); then log_msg "Preserving unverified backup: $dir_name (upload may have failed or in progress)" ((preserved_count++)) continue fi # DOUBLE-CHECK: Verify backup actually exists in OCI local oci_object_count oci_object_count=$(oci os object list \ --config-file "$OCI_CONFIG_FILE" \ --bucket-name "$OCI_BUCKET" \ --prefix "$OCI_PREFIX/$dir_name/" \ --all 2>/dev/null | grep -c '"name"' || echo "0") if (( oci_object_count == 0 )); then log_msg "Preserving backup $dir_name: upload marker exists but no objects found in OCI" ((preserved_count++)) continue fi # Safe to delete: old enough + marked + verified in OCI local dir_size_mb dir_size_mb=$(du -sm "$old_dir" 2>/dev/null | cut -f1) log_msg "Removing verified old backup: $dir_name (${dir_size_mb}MB, confirmed in OCI: $oci_object_count files)" if rm -rf "$old_dir" 2>/dev/null; then ((cleaned_count++)) ((cleaned_size_mb+=dir_size_mb)) else log_err "Failed to remove directory: $old_dir" fi done < "$temp_dirs" # Clean up temporary file rm -f "$temp_dirs" # Report cleanup results if [[ $cleaned_count -gt 0 ]]; then local cleaned_size_gb cleaned_size_gb=$(echo "scale=2; $cleaned_size_mb / 1024" | bc 2>/dev/null || echo "0") log_ok "Cleaned $cleaned_count old backup directories (${cleaned_size_gb}GB freed)" else log_msg "No directories older than ${LOCAL_RETENTION_HOURS} hours were eligible for cleanup" fi if [[ $preserved_count -gt 0 ]]; then log_msg "Preserved $preserved_count backup directories (within retention or unverified)" fi # Display current local backup inventory display_backup_inventory # Clean old log files local old_logs old_logs=$(find "$LOG_DIR" -name "*.log" -mtime +${LOG_RETENTION_DAYS} 2>/dev/null | wc -l) if [[ $old_logs -gt 0 ]]; then find "$LOG_DIR" -name "*.log" -mtime +${LOG_RETENTION_DAYS} -delete 2>/dev/null log_ok "Cleaned $old_logs old log files (>${LOG_RETENTION_DAYS} days)" fi log_ok "Cleanup completed" } ################################################################################ # MAIN EXECUTION ################################################################################ main() { # CRITICAL: Acquire atomic lock FIRST if ! acquire_lock; then exit 1 fi log_msg "==========================================================" log_msg "ENTERPRISE RMAN BACKUP TO OCI - ACME CORPORATION" log_msg "==========================================================" log_msg "Backup Type: $BACKUP_TYPE" log_msg "Database: $ORACLE_SID" log_msg "Local Retention: ${LOCAL_RETENTION_HOURS} hours" log_msg "OCI Bucket: $OCI_BUCKET" log_msg "==========================================================" # Pre-backup disk space check check_disk_space validate_environment if ! perform_rman_backup; then log_err "Backup failed. Process aborted." exit 1 fi if ! upload_to_oci; then log_err "Upload failed. Local backup preserved at: $BACKUP_DIR" log_err "Will retry upload on next backup cycle" # Still run cleanup to manage old backups cleanup_local exit 1 fi # Always run cleanup to remove old directories cleanup_local # Post-backup disk space check check_disk_space log_msg "==========================================================" log_ok "BACKUP COMPLETED SUCCESSFULLY" log_msg "==========================================================" log_msg "Local Copy: $BACKUP_DIR" log_msg "Retention: ${LOCAL_RETENTION_HOURS} hours from upload completion" log_msg "OCI Location: $OCI_BUCKET/$OCI_PREFIX/$DATE_STAMP/" log_msg "==========================================================" } main "$@"
The Archive Log Optimization Breakthrough
The Problem: Redundant Backups
Traditional archive log backup scripts suffer from a critical inefficiency. Consider this scenario without optimization:
- 2:00 PM: Script backs up archive logs 1-100 (all available)
- 2:30 PM: Script backs up archive logs 1-125 (includes duplicates 1-100)
- 3:00 PM: Script backs up archive logs 1-150 (includes duplicates 1-125)
Over 24 hours with 48 executions, each archive log gets backed up an average of 24 times, wasting storage and bandwidth.
The Solution: Tag-Based Intelligence
Our script uses RMAN's sophisticated tracking mechanism:
BACKUP ARCHIVELOG ALL NOT BACKED UP 1 TIMES TAG 'ACME_ARCH_SECONDARY';
How It Works:
- 2:00 PM: Backs up logs 1-100, tags them with 'ACME_ARCH_SECONDARY'
- 2:30 PM: RMAN checks all logs, finds 1-100 already have the tag, backs up only 101-125
- 3:00 PM: Only backs up new logs 126-150
Result: Each archive log is backed up exactly once, reducing storage consumption by up to 90%.
Why This Doesn't Interfere with Other Backups
The tag-based system operates independently of your primary backup strategy. Your automatic backup system uses different tags (or no tags), so this secondary system won't interfere with your existing processes.
Deployment Guide
Step 1: Environment Preparation
Create the necessary directory structure:
# Create backup staging area
sudo mkdir -p /u02/rman_staging
sudo chown oracle:oinstall /u02/rman_staging
sudo chmod 755 /u02/rman_staging
# Create log directory
mkdir -p /home/oracle/logs/backup_operations
chmod 755 /home/oracle/logs/backup_operations
Step 2: OCI Configuration
Set up OCI CLI configuration:
mkdir -p /home/oracle/.oci
chmod 700 /home/oracle/.oci
# Create configuration file
vi /home/oracle/.oci/config
Add your configuration (replace with actual values):
[DEFAULT]
user=ocid1.user.oc1..aaaaaaaexampleuserocid
fingerprint=aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99
key_file=/home/oracle/.oci/oci_api_key.pem
tenancy=ocid1.tenancy.oc1..aaaaaaaexampletenancyocid
region=us-phoenix-1
Test connectivity:
oci os ns get --config-file /home/oracle/.oci/config
Step 3: Script Installation
Install the script:
vi /home/oracle/scripts/rman_oci_backup.sh
# Paste the script and update the CONFIGURATION section
chmod +x /home/oracle/scripts/rman_oci_backup.sh
# Verify syntax
bash -n /home/oracle/scripts/rman_oci_backup.sh
echo "Exit code: $?"
Step 4: Testing and Validation
Test each backup type:
# Test archive log backup (demonstrates optimization)
/home/oracle/scripts/rman_oci_backup.sh ARCHIVELOG
# Run again immediately to see optimization in action
/home/oracle/scripts/rman_oci_backup.sh ARCHIVELOG
# Test incremental backup
/home/oracle/scripts/rman_oci_backup.sh INCREMENTAL
# Verify OCI upload
TODAY=$(date +%Y%m%d)
oci os object list --bucket-name acme-prod-backups --prefix "ACMEPROD_RMAN/$TODAY/" --output table
Production Scheduling
Configure automated scheduling using cron:
crontab -e
Add this production schedule:
# ACME Corporation Database Backup Schedule
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""
# Full Backup: Saturday at 2:00 AM (creates Level 0 baseline)
0 2 * * 6 /home/oracle/scripts/rman_oci_backup.sh FULL >> /home/oracle/logs/backup_operations/cron_full_$(date +\%Y\%m\%d).log 2>&1
# Incremental Backup: Daily (Sunday-Friday) at 2:00 AM
0 2 * * 0-5 /home/oracle/scripts/rman_oci_backup.sh INCREMENTAL >> /home/oracle/logs/backup_operations/cron_incr_$(date +\%Y\%m\%d).log 2>&1
# Archive Log Backup: Every 30 minutes (optimized for efficiency)
*/30 * * * * /home/oracle/scripts/rman_oci_backup.sh ARCHIVELOG >> /home/oracle/logs/backup_operations/cron_arch_$(date +\%Y\%m\%d).log 2>&1
Monitoring and Maintenance
Daily Monitoring Script
Create a monitoring script to verify backup success:
#!/bin/bash
# Save as /home/oracle/scripts/check_backups.sh
TODAY=$(date +%Y%m%d)
echo "=== Daily Backup Status Report ==="
echo "Generated: $(date)"
# Check today's backups in OCI
oci os object list \
--bucket-name acme-prod-backups \
--prefix "ACMEPROD_RMAN/$TODAY/" \
--config-file /home/oracle/.oci/config \
--output table
# Check for recent errors
echo -e "\nRecent Errors:"
grep -i error /home/oracle/logs/backup_operations/*.log | tail -10
Space Requirements Calculation
Calculate required staging space using this formula:
For example, a 500GB database generating 50GB of daily archive logs:
Cost Optimization Strategies
Lifecycle Policies
Configure OCI lifecycle policies to manage costs:
# Create lifecycle policy to archive old backups
cat > /tmp/lifecycle-policy.json << 'EOF'
{
"items": [
{
"name": "archive-monthly-backups",
"action": "ARCHIVE",
"timeAmount": 30,
"timeUnit": "DAYS",
"isEnabled": true,
"target": "objects"
},
{
"name": "delete-old-backups",
"action": "DELETE",
"timeAmount": 180,
"timeUnit": "DAYS",
"isEnabled": true,
"target": "objects"
}
]
}
EOF
oci os object-lifecycle-policy put \
--bucket-name acme-prod-backups \
--lifecycle-policy file:///tmp/lifecycle-policy.json \
--config-file /home/oracle/.oci/config
This policy automatically moves backups older than 30 days to Archive Storage (90% cost reduction) and deletes backups older than 180 days.
Troubleshooting Common Issues
Issue: Insufficient Disk Space
Solution: Verify space requirements and expand staging area:
df -h /u02
# If insufficient, either expand filesystem or reduce parallel channels
Issue: OCI Authentication Failures
Solution: Verify OCI configuration:
# Test authentication
oci os ns get --config-file /home/oracle/.oci/config
# Verify key permissions
ls -l /home/oracle/.oci/oci_api_key.pem # Should be 600
Issue: Archive Log Backups Taking Too Long
This usually indicates the optimization isn't working. Check RMAN backup history:
rman target / << EOF
LIST BACKUP OF ARCHIVELOG ALL;
EXIT;
EOF
Look for consistent use of the 'ACME_ARCH_SECONDARY' tag.
Security Considerations
Access Control
Implement restrictive permissions:
chmod 700 /u02/rman_staging
chmod 700 /home/oracle/logs/backup_operations
chmod 600 /home/oracle/.oci/oci_api_key.pem
Encryption
OCI Object Storage provides encryption at rest by default. For additional security, configure customer-managed encryption keys through OCI Vault.
Key Benefits Achieved:
- Cost Efficiency: Eliminates redundant archive log backups
- Reliability: Comprehensive error handling and validation
- Scalability: Parallel processing reduces backup windows
- Security: Encrypted cloud storage with access controls
- Automation: Complete hands-off operation with monitoring
Regular testing, monitoring, and maintenance of this backup system will provide the confidence that your critical database systems are protected against any disaster scenario while maintaining cost-effective cloud storage utilization.

Post a Comment
Post a Comment