Sunday, April 27, 2025

# Function to compare DBA_REGISTRY components compare_dba_registry() { local src_conn=$1 local tgt_conn=$2 local html_file=$3 echo "
" >> "$html_file" echo "

Database Component Comparison (DBA_REGISTRY)

" >> "$html_file" # Get components from both databases sqlplus -s /nolog << EOF > /tmp/src_components.txt connect $src_conn set pagesize 0 feedback off linesize 200 SELECT comp_name || '|' || version || '|' || status FROM dba_registry ORDER BY comp_name; EOF sqlplus -s /nolog << EOF > /tmp/tgt_components.txt connect $tgt_conn set pagesize 0 feedback off linesize 200 SELECT comp_name || '|' || version || '|' || status FROM dba_registry ORDER BY comp_name; EOF # Clean SQL*Plus output sed -i '/^Disconnected/d' /tmp/src_components.txt /tmp/tgt_components.txt sed -i '/^$/d' /tmp/src_components.txt /tmp/tgt_components.txt echo "" >> "$html_file" awk -F'|' ' BEGIN { print "" } NR==FNR { src_comp[$1] = $2 "|" $3 delete tgt_comp[$1] next } { comp=$1 if (comp in src_comp) { split(src_comp[comp], src, "|") if ($2 != src[1]) { status = "version-mismatch" msg = "Version mismatch" } else if ($3 != src[2]) { status = "status-mismatch" msg = "Status mismatch" } else { status = "match" msg = "OK" } printf "\n", status, comp, src[1], src[2], $2, $3, msg delete src_comp[comp] } else { tgt_comp[$1] = $0 } } END { # Print components only in source for (comp in src_comp) { split(src_comp[comp], src, "|") printf "\n", comp, src[1], src[2] } # Print components only in target for (comp in tgt_comp) { split(tgt_comp[comp], tgt, "|") printf "\n", comp, tgt[2], tgt[3] } print "" } ' /tmp/src_components.txt /tmp/tgt_components.txt >> "$html_file" echo "
Component Source Version Source Status Target Version Target Status Status
%s%s%s%s%s%s
%s%s%sNot FoundMissing in target
%sNot Found%s%sMissing in source
" >> "$html_file" echo "
" >> "$html_file" rm -f /tmp/src_components.txt /tmp/tgt_components.txt } init_html_report() { local html_file="$1" local migration="$2" cat << EOF > "$html_file" PDB Compatibility Report: $migration

PDB Compatibility Report: $migration

Generated at: $(date)

EOF } #!/bin/bash # Configuration REPORT_FILE="firewall_report.html" EMAIL_FROM="dba@example.com" EMAIL_TO="admin@example.com" EMAIL_SUBJECT="Firewall Policy Validation Report" TNS_FILE="${1}" # Check if tnsnames.ora file is provided if [ -z "${TNS_FILE}" ] || [ ! -f "${TNS_FILE}" ]; then echo "Usage: $0 " echo "Please provide valid tnsnames.ora file path" exit 1 fi # Check for dependencies command -v nc >/dev/null 2>&1 || { echo "netcat (nc) is required but not installed. Aborting."; exit 1; } # Initialize HTML report initialize_report() { cat << EOF > "${REPORT_FILE}" Firewall Policy Validation Report

Firewall Policy Validation Report

Generated at: $(date)

EOF } # Extract dbname and hosts from tnsnames.ora extract_entries() { awk -v RS= '{ dbname = ""; host = "" # Extract dbname from first line if (match($0, /^[^=]+/)) { dbname = substr($0, RSTART, RLENGTH) gsub(/^[ \t]+|[ \t]+$/, "", dbname) } # Extract all HOST entries remaining = $0 while (match(remaining, /HOST\s*=\s*[^ \t\)]+/i)) { host_line = substr(remaining, RSTART, RLENGTH) split(host_line, arr, "=") host = arr[2] gsub(/^[ \t]+|[ \t]+$/, "", host) print dbname "|" host remaining = substr(remaining, RSTART + RLENGTH) } }' "${TNS_FILE}" | sort -u } # Test port function test_port() { host=$1 port=1521 timeout 3 nc -zv "${host}" "${port}" > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "success" else echo "failure" fi } # Complete HTML report finalize_report() { cat << EOF >> "${REPORT_FILE}"
DB Name Hostname Port 1521 Status
EOF } # Email function send_email() { local report_size=$(du -b "${REPORT_FILE}" | awk '{print $1}') if [ "${report_size}" -lt 1048576 ]; then mailx -s "${EMAIL_SUBJECT}" -a "Content-type: text/html" -r "${EMAIL_FROM}" "${EMAIL_TO}" < "${REPORT_FILE}" else echo "Please find attached the firewall validation report" | \ mailx -s "${EMAIL_SUBJECT}" -a "${REPORT_FILE}" -r "${EMAIL_FROM}" "${EMAIL_TO}" fi } # Main execution initialize_report while IFS="|" read -r dbname host; do [ -z "$host" ] && continue # Skip invalid entries status=$(test_port "${host}") if [ "${status}" = "success" ]; then class="success" status_text="Open" else class="failure" status_text="Closed" fi echo "Processing DB: ${dbname} - Host: ${host} - Status: ${status_text}" cat << EOF >> "${REPORT_FILE}" ${dbname} ${host} ${status_text} EOF done < <(extract_entries) finalize_report # Send email send_email echo "Report generated: ${REPORT_FILE}" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do IFS=$'\n' read -rd '' -a lines <<< "${STANDBY_REDO_GROUPS_MAP[$STANDBY_CONN]}" GROUPS_TO_DROP="" for entry in "${lines[@]}"; do [[ -z "$entry" ]] && continue grp="${entry%%,*}" GROUPS_TO_DROP="$GROUPS_TO_DROP $grp" done drop_redo_groups_safely "$STANDBY_CONN" "$GROUPS_TO_DROP" IFS=$'\n' read -rd '' -a lines <<< "${STANDBY_STANDBY_GROUPS_MAP[$STANDBY_CONN]}" GROUPS_TO_DROP="" for entry in "${lines[@]}"; do [[ -z "$entry" ]] && continue grp="${entry%%,*}" GROUPS_TO_DROP="$GROUPS_TO_DROP $grp" done drop_standby_groups_safely "$STANDBY_CONN" "$GROUPS_TO_DROP" done STANDBY_REDO_GROUPS="101,1 102,1 103,2 104,2" # Set IFS to split on newline IFS=$'\n' read -rd '' -a lines <<< "$STANDBY_REDO_GROUPS" GROUPS_TO_DROP="" for entry in "${lines[@]}"; do [[ -z "$entry" ]] && continue grp="${entry%%,*}" # part before comma GROUPS_TO_DROP="$GROUPS_TO_DROP $grp" done echo "Result GROUPS_TO_DROP = '$GROUPS_TO_DROP'" STANDBY_REDO_GROUPS="101,1 102,1 103,2 104,2" # Set IFS to split on newline IFS=$'\n' read -rd '' -a lines <<< "$STANDBY_REDO_GROUPS" GROUPS_TO_DROP="" for entry in "${lines[@]}"; do [[ -z "$entry" ]] && continue grp="${entry%%,*}" # part before comma GROUPS_TO_DROP="$GROUPS_TO_DROP $grp" done echo "Result GROUPS_TO_DROP = '$GROUPS_TO_DROP'" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do GROUPS_TO_DROP="" while IFS= read -r entry; do [[ -z "$entry" ]] && continue grp=$(echo "$entry" | cut -d',' -f1) GROUPS_TO_DROP="$GROUPS_TO_DROP $grp" done < <(echo "${STANDBY_REDO_GROUPS_MAP[$STANDBY_CONN]}") drop_redo_groups_safely "$STANDBY_CONN" "$GROUPS_TO_DROP" GROUPS_TO_DROP="" while IFS= read -r entry; do [[ -z "$entry" ]] && continue grp=$(echo "$entry" | cut -d',' -f1) GROUPS_TO_DROP="$GROUPS_TO_DROP $grp" done < <(echo "${STANDBY_STANDBY_GROUPS_MAP[$STANDBY_CONN]}") drop_standby_groups_safely "$STANDBY_CONN" "$GROUPS_TO_DROP" done #!/bin/bash # Script to resize redo and standby redo logs using sqlplus & dgmgrl connections. # Accepts input from a provided text file. set -euo pipefail INPUT_FILE=$1 LOG_FILE="redo_log_resize_$(date +%Y%m%d%H%M%S).log" NEW_REDO_CREATED=0 NEW_STANDBY_CREATED=0 OLD_REDO_DROPPED=0 OLD_STANDBY_DROPPED=0 source ./functions.sh save_redo_logfile_state() { local CONN=$1 local FILE=$2 exec_sqlplus "$CONN" " SET PAGESIZE 500 SET LINESIZE 300 COL GROUP# FOR 9999 COL THREAD# FOR 99 COL SIZE_MB FOR 99999 COL MEMBER FOR A100 SELECT l.group#, l.thread#, l.bytes/1024/1024 AS size_mb, lf.member FROM v\$log l JOIN v\$logfile lf ON l.group# = lf.group# ORDER BY l.thread#, l.group#; " > "$FILE" log "Saved REDO log file state to $FILE" } save_standby_logfile_state() { local CONN=$1 local FILE=$2 exec_sqlplus "$CONN" " SET PAGESIZE 500 SET LINESIZE 300 COL GROUP# FOR 9999 COL THREAD# FOR 99 COL SIZE_MB FOR 99999 COL MEMBER FOR A100 SELECT sl.group#, sl.thread#, sl.bytes/1024/1024 AS size_mb, lf.member FROM v\$standby_log sl JOIN v\$logfile lf ON sl.group# = lf.group# ORDER BY sl.thread#, sl.group#; " > "$FILE" log "Saved STANDBY log file state to $FILE" } if [ ! -f "$INPUT_FILE" ]; then error_exit "Input file not found!" fi readarray -t PARAMS < "$INPUT_FILE" DB_NAME=${PARAMS[0]:-} SYS_USER=${PARAMS[1]:-} SYS_PASSWORD=${PARAMS[2]:-} NEW_REDO_SIZE_MB=${PARAMS[3]:-} NEW_STANDBY_REDO_SIZE_MB=${PARAMS[4]:-} STANDBY_DB_NAMES=(${PARAMS[@]:5}) PRIMARY_CONN="$SYS_USER/$SYS_PASSWORD@$DB_NAME as sysdba" STANDBY_CONNS=() for standby_db in "${STANDBY_DB_NAMES[@]}"; do STANDBY_CONNS+=("$SYS_USER/$SYS_PASSWORD@$standby_db as sysdba") fi trap 'error_exit "Unexpected error occurred."' ERR save_redo_logfile_state "$PRIMARY_CONN" "/tmp/primary_redo_before.lst" save_standby_logfile_state "$PRIMARY_CONN" "/tmp/primary_standby_before.lst" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do conn_clean=$(echo "$STANDBY_CONN" | sed 's/[^a-zA-Z0-9]/_/g') save_redo_logfile_state "$STANDBY_CONN" "/tmp/${conn_clean}_redo_before.lst" save_standby_logfile_state "$STANDBY_CONN" "/tmp/${conn_clean}_standby_before.lst" done log "Setting standby_file_management to MANUAL." for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do exec_sqlplus "$STANDBY_CONN" "ALTER SYSTEM SET standby_file_management='MANUAL' SCOPE=BOTH;" done log "Capturing current redo and standby log groups." PRIMARY_REDO_GROUPS=$(exec_sqlplus "$PRIMARY_CONN" "SELECT group# FROM v\$log ORDER BY group#;") PRIMARY_STANDBY_GROUPS=$(exec_sqlplus "$PRIMARY_CONN" "SELECT group# FROM v\$standby_log ORDER BY group#;") declare -A STANDBY_REDO_GROUPS_MAP declare -A STANDBY_STANDBY_GROUPS_MAP for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do STANDBY_REDO_GROUPS_MAP["$STANDBY_CONN"]=$(exec_sqlplus "$STANDBY_CONN" "SELECT group#||','||thread# FROM v\$log ORDER BY group#;") STANDBY_STANDBY_GROUPS_MAP["$STANDBY_CONN"]=$(exec_sqlplus "$STANDBY_CONN" "SELECT group#||','||thread# FROM v\$standby_log ORDER BY group#;") done MAX_GROUP=$(exec_sqlplus "$PRIMARY_CONN" " SELECT MAX(max_group#) FROM ( SELECT MAX(group#) AS max_group# FROM v\$log UNION ALL SELECT MAX(group#) FROM v\$standby_log ); ") NEW_GROUP_ID=$((MAX_GROUP + 1)) MAX_THREAD=$(exec_sqlplus "$PRIMARY_CONN" "SELECT GREATEST(MAX(thread#)) FROM (SELECT thread# FROM v\$log UNION SELECT thread# FROM v\$standby_log);") REDO_PER_THREAD=$(exec_sqlplus "$PRIMARY_CONN" "SELECT COUNT(1) FROM v\$log WHERE thread#=1;") STANDBY_LOGS_PER_THREAD=$((REDO_PER_THREAD + 1)) log "Creating new redo log groups." add_redo_log_groups "$PRIMARY_CONN" "$NEW_REDO_SIZE_MB" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do add_redo_log_groups "$STANDBY_CONN" "$NEW_REDO_SIZE_MB" done log "Creating new standby redo log groups." add_standby_redo_log_groups "$PRIMARY_CONN" "$NEW_STANDBY_REDO_SIZE_MB" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do add_standby_redo_log_groups "$STANDBY_CONN" "$NEW_STANDBY_REDO_SIZE_MB" done log "Forcing log switch." exec_sqlplus "$PRIMARY_CONN" "ALTER SYSTEM SWITCH LOGFILE;" sleep 10 log "Dropping old redo and standby redo log groups." drop_redo_groups_safely "$PRIMARY_CONN" "$PRIMARY_REDO_GROUPS" drop_standby_groups_safely "$PRIMARY_CONN" "$PRIMARY_STANDBY_GROUPS" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do GROUPS_TO_DROP="" while IFS= read -r entry; do grp="${entry%%,*}" if [[ -n "$grp" ]]; then GROUPS_TO_DROP="$GROUPS_TO_DROP $grp" fi done <<< "${STANDBY_REDO_GROUPS_MAP[$STANDBY_CONN]}" drop_redo_groups_safely "$STANDBY_CONN" "$GROUPS_TO_DROP" GROUPS_TO_DROP="" while IFS= read -r entry; do grp="${entry%%,*}" if [[ -n "$grp" ]]; then GROUPS_TO_DROP="$GROUPS_TO_DROP $grp" fi done <<< "${STANDBY_STANDBY_GROUPS_MAP[$STANDBY_CONN]}" drop_standby_groups_safely "$STANDBY_CONN" "$GROUPS_TO_DROP" done log "Setting standby_file_management back to AUTO." for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do exec_sqlplus "$STANDBY_CONN" "ALTER SYSTEM SET standby_file_management='AUTO' SCOPE=BOTH;" done for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do STANDBY_DB_ROLE=$(exec_sqlplus "$STANDBY_CONN" "SELECT database_role FROM v\$database;") if [[ "$STANDBY_DB_ROLE" == "PHYSICAL STANDBY" ]]; then log "Restarting managed recovery on standby $STANDBY_CONN." exec_sqlplus "$STANDBY_CONN" "ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;" fi done save_redo_logfile_state "$PRIMARY_CONN" "/tmp/primary_redo_after.lst" save_standby_logfile_state "$PRIMARY_CONN" "/tmp/primary_standby_after.lst" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do conn_clean=$(echo "$STANDBY_CONN" | sed 's/[^a-zA-Z0-9]/_/g') save_redo_logfile_state "$STANDBY_CONN" "/tmp/${conn_clean}_redo_after.lst" save_standby_logfile_state "$STANDBY_CONN" "/tmp/${conn_clean}_standby_after.lst" done log "Validating Data Guard configuration." echo "show configuration;" | dgmgrl / "$SYS_USER/$SYS_PASSWORD@$DB_NAME" | tee -a "$LOG_FILE" log "Redo and Standby redo log resizing complete." log "Summary:" echo " New redo log groups created: $NEW_REDO_CREATED" | tee -a "$LOG_FILE" echo " New standby redo log groups created: $NEW_STANDBY_CREATED" | tee -a "$LOG_FILE" echo " Old redo log groups dropped: $OLD_REDO_DROPPED" | tee -a "$LOG_FILE" echo " Old standby redo log groups dropped: $OLD_STANDBY_DROPPED" | tee -a "$LOG_FILE" log "Summary also saved to $LOG_FILE" #!/bin/bash # Script to resize redo and standby redo logs using sqlplus & dgmgrl connections. # Accepts input from a provided text file. set -euo pipefail INPUT_FILE=$1 LOG_FILE="redo_log_resize_$(date +%Y%m%d%H%M%S).log" NEW_REDO_CREATED=0 NEW_STANDBY_CREATED=0 OLD_REDO_DROPPED=0 OLD_STANDBY_DROPPED=0 source ./functions.sh if [ ! -f "$INPUT_FILE" ]; then error_exit "Input file not found!" fi readarray -t PARAMS < "$INPUT_FILE" DB_NAME=${PARAMS[0]:-} SYS_USER=${PARAMS[1]:-} SYS_PASSWORD=${PARAMS[2]:-} NEW_REDO_SIZE_MB=${PARAMS[3]:-} NEW_STANDBY_REDO_SIZE_MB=${PARAMS[4]:-} STANDBY_DB_NAMES=(${PARAMS[@]:5}) PRIMARY_CONN="$SYS_USER/$SYS_PASSWORD@$DB_NAME as sysdba" STANDBY_CONNS=() for standby_db in "${STANDBY_DB_NAMES[@]}"; do STANDBY_CONNS+=("$SYS_USER/$SYS_PASSWORD@$standby_db as sysdba") fi trap 'error_exit "Unexpected error occurred."' ERR save_redo_logfile_state "$PRIMARY_CONN" "/tmp/primary_redo_before.lst" save_standby_logfile_state "$PRIMARY_CONN" "/tmp/primary_standby_before.lst" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do conn_clean=$(echo "$STANDBY_CONN" | sed 's/[^a-zA-Z0-9]/_/g') save_redo_logfile_state "$STANDBY_CONN" "/tmp/${conn_clean}_redo_before.lst" save_standby_logfile_state "$STANDBY_CONN" "/tmp/${conn_clean}_standby_before.lst" done log "Setting standby_file_management to MANUAL." for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do exec_sqlplus "$STANDBY_CONN" "ALTER SYSTEM SET standby_file_management='MANUAL' SCOPE=BOTH;" done log "Capturing current redo and standby log groups." PRIMARY_REDO_GROUPS=$(exec_sqlplus "$PRIMARY_CONN" "SELECT group#||','||thread# FROM v\$log;") PRIMARY_STANDBY_GROUPS=$(exec_sqlplus "$PRIMARY_CONN" "SELECT group#||','||thread# FROM v\$standby_log;") declare -A STANDBY_REDO_GROUPS_MAP declare -A STANDBY_STANDBY_GROUPS_MAP for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do STANDBY_REDO_GROUPS_MAP["$STANDBY_CONN"]=$(exec_sqlplus "$STANDBY_CONN" "SELECT group#||','||thread# FROM v\$log;") STANDBY_STANDBY_GROUPS_MAP["$STANDBY_CONN"]=$(exec_sqlplus "$STANDBY_CONN" "SELECT group#||','||thread# FROM v\$standby_log;") done MAX_GROUP=$(exec_sqlplus "$PRIMARY_CONN" " SELECT MAX(max_group#) FROM ( SELECT MAX(group#) AS max_group# FROM v\$log UNION ALL SELECT MAX(group#) FROM v\$standby_log ); ") NEW_GROUP_ID=$((MAX_GROUP + 1)) MAX_THREAD=$(exec_sqlplus "$PRIMARY_CONN" "SELECT GREATEST(MAX(thread#)) FROM (SELECT thread# FROM v\$log UNION SELECT thread# FROM v\$standby_log);") REDO_PER_THREAD=$(exec_sqlplus "$PRIMARY_CONN" "SELECT COUNT(1) FROM v\$log WHERE thread#=1;") STANDBY_LOGS_PER_THREAD=$((REDO_PER_THREAD + 1)) log "Creating new redo log groups." add_redo_log_groups "$PRIMARY_CONN" "$NEW_REDO_SIZE_MB" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do add_redo_log_groups "$STANDBY_CONN" "$NEW_REDO_SIZE_MB" done log "Creating new standby redo log groups." add_standby_redo_log_groups "$PRIMARY_CONN" "$NEW_STANDBY_REDO_SIZE_MB" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do add_standby_redo_log_groups "$STANDBY_CONN" "$NEW_STANDBY_REDO_SIZE_MB" done log "Forcing log switch." exec_sqlplus "$PRIMARY_CONN" "ALTER SYSTEM SWITCH LOGFILE;" sleep 10 log "Dropping old redo and standby redo log groups." drop_redo_groups_safely "$PRIMARY_CONN" "$PRIMARY_REDO_GROUPS" drop_standby_groups_safely "$PRIMARY_CONN" "$PRIMARY_STANDBY_GROUPS" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do drop_redo_groups_safely "$STANDBY_CONN" "${STANDBY_REDO_GROUPS_MAP[$STANDBY_CONN]}" drop_standby_groups_safely "$STANDBY_CONN" "${STANDBY_STANDBY_GROUPS_MAP[$STANDBY_CONN]}" done log "Setting standby_file_management back to AUTO." for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do exec_sqlplus "$STANDBY_CONN" "ALTER SYSTEM SET standby_file_management='AUTO' SCOPE=BOTH;" done for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do STANDBY_DB_ROLE=$(exec_sqlplus "$STANDBY_CONN" "SELECT database_role FROM v\$database;") if [[ "$STANDBY_DB_ROLE" == "PHYSICAL STANDBY" ]]; then log "Restarting managed recovery on standby $STANDBY_CONN." exec_sqlplus "$STANDBY_CONN" "ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;" fi done save_redo_logfile_state "$PRIMARY_CONN" "/tmp/primary_redo_after.lst" save_standby_logfile_state "$PRIMARY_CONN" "/tmp/primary_standby_after.lst" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do conn_clean=$(echo "$STANDBY_CONN" | sed 's/[^a-zA-Z0-9]/_/g') save_redo_logfile_state "$STANDBY_CONN" "/tmp/${conn_clean}_redo_after.lst" save_standby_logfile_state "$STANDBY_CONN" "/tmp/${conn_clean}_standby_after.lst" done log "Validating Data Guard configuration." echo "show configuration;" | dgmgrl / "$SYS_USER/$SYS_PASSWORD@$DB_NAME" | tee -a "$LOG_FILE" log "Redo and Standby redo log resizing complete." log "Summary:" echo " New redo log groups created: $NEW_REDO_CREATED" | tee -a "$LOG_FILE" echo " New standby redo log groups created: $NEW_STANDBY_CREATED" | tee -a "$LOG_FILE" echo " Old redo log groups dropped: $OLD_REDO_DROPPED" | tee -a "$LOG_FILE" echo " Old standby redo log groups dropped: $OLD_STANDBY_DROPPED" | tee -a "$LOG_FILE" log "Summary also saved to $LOG_FILE" #!/bin/bash # Script to resize redo and standby redo logs using sqlplus & dgmgrl connections. # Accepts input from a provided text file. set -euo pipefail INPUT_FILE=$1 LOG_FILE="redo_log_resize_$(date +%Y%m%d%H%M%S).log" NEW_REDO_CREATED=0 NEW_STANDBY_CREATED=0 OLD_REDO_DROPPED=0 OLD_STANDBY_DROPPED=0 source ./functions.sh if [ ! -f "$INPUT_FILE" ]; then error_exit "Input file not found!" fi readarray -t PARAMS < "$INPUT_FILE" DB_NAME=${PARAMS[0]:-} SYS_USER=${PARAMS[1]:-} SYS_PASSWORD=${PARAMS[2]:-} NEW_REDO_SIZE_MB=${PARAMS[3]:-} NEW_STANDBY_REDO_SIZE_MB=${PARAMS[4]:-} STANDBY_DB_NAMES=(${PARAMS[@]:5}) PRIMARY_CONN="$SYS_USER/$SYS_PASSWORD@$DB_NAME as sysdba" STANDBY_CONNS=() for standby_db in "${STANDBY_DB_NAMES[@]}"; do STANDBY_CONNS+=("$SYS_USER/$SYS_PASSWORD@$standby_db as sysdba") fi trap 'error_exit "Unexpected error occurred."' ERR log_current_logfile_state "$PRIMARY_CONN" "PRIMARY (Before)" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do log_current_logfile_state "$STANDBY_CONN" "STANDBY (Before)" done log "Setting standby_file_management to MANUAL." for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do exec_sqlplus "$STANDBY_CONN" "ALTER SYSTEM SET standby_file_management='MANUAL' SCOPE=BOTH;" done log "Capturing current redo and standby log groups." PRIMARY_REDO_GROUPS=$(exec_sqlplus "$PRIMARY_CONN" "SELECT group#||','||thread# FROM v\$log;") PRIMARY_STANDBY_GROUPS=$(exec_sqlplus "$PRIMARY_CONN" "SELECT group#||','||thread# FROM v\$standby_log;") declare -A STANDBY_REDO_GROUPS_MAP declare -A STANDBY_STANDBY_GROUPS_MAP for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do STANDBY_REDO_GROUPS_MAP["$STANDBY_CONN"]=$(exec_sqlplus "$STANDBY_CONN" "SELECT group#||','||thread# FROM v\$log;") STANDBY_STANDBY_GROUPS_MAP["$STANDBY_CONN"]=$(exec_sqlplus "$STANDBY_CONN" "SELECT group#||','||thread# FROM v\$standby_log;") done MAX_GROUP=$(exec_sqlplus "$PRIMARY_CONN" " SELECT MAX(max_group#) FROM ( SELECT MAX(group#) AS max_group# FROM v\$log UNION ALL SELECT MAX(group#) FROM v\$standby_log ); ") NEW_GROUP_ID=$((MAX_GROUP + 1)) MAX_THREAD=$(exec_sqlplus "$PRIMARY_CONN" "SELECT GREATEST(MAX(thread#)) FROM (SELECT thread# FROM v\$log UNION SELECT thread# FROM v\$standby_log);") REDO_PER_THREAD=$(exec_sqlplus "$PRIMARY_CONN" "SELECT COUNT(1) FROM v\$log WHERE thread#=1;") STANDBY_LOGS_PER_THREAD=$((REDO_PER_THREAD + 1)) log "Creating new redo log groups." add_redo_log_groups "$PRIMARY_CONN" "$NEW_REDO_SIZE_MB" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do add_redo_log_groups "$STANDBY_CONN" "$NEW_REDO_SIZE_MB" done log "Creating new standby redo log groups." add_standby_redo_log_groups "$PRIMARY_CONN" "$NEW_STANDBY_REDO_SIZE_MB" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do add_standby_redo_log_groups "$STANDBY_CONN" "$NEW_STANDBY_REDO_SIZE_MB" done log "Forcing log switch." exec_sqlplus "$PRIMARY_CONN" "ALTER SYSTEM SWITCH LOGFILE;" sleep 10 log "Dropping old redo and standby redo log groups." drop_redo_groups_safely "$PRIMARY_CONN" "$PRIMARY_REDO_GROUPS" drop_standby_groups_safely "$PRIMARY_CONN" "$PRIMARY_STANDBY_GROUPS" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do drop_redo_groups_safely "$STANDBY_CONN" "${STANDBY_REDO_GROUPS_MAP[$STANDBY_CONN]}" drop_standby_groups_safely "$STANDBY_CONN" "${STANDBY_STANDBY_GROUPS_MAP[$STANDBY_CONN]}" done log "Setting standby_file_management back to AUTO." for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do exec_sqlplus "$STANDBY_CONN" "ALTER SYSTEM SET standby_file_management='AUTO' SCOPE=BOTH;" done for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do STANDBY_DB_ROLE=$(exec_sqlplus "$STANDBY_CONN" "SELECT database_role FROM v\$database;") if [[ "$STANDBY_DB_ROLE" == "PHYSICAL STANDBY" ]]; then log "Restarting managed recovery on standby $STANDBY_CONN." exec_sqlplus "$STANDBY_CONN" "ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;" fi done log_current_logfile_state "$PRIMARY_CONN" "PRIMARY (After)" for STANDBY_CONN in "${STANDBY_CONNS[@]}"; do log_current_logfile_state "$STANDBY_CONN" "STANDBY (After)" done log "Validating Data Guard configuration." echo "show configuration;" | dgmgrl / "$SYS_USER/$SYS_PASSWORD@$DB_NAME" | tee -a "$LOG_FILE" log "Redo and Standby redo log resizing complete." log "Summary:" echo " New redo log groups created: $NEW_REDO_CREATED" | tee -a "$LOG_FILE" echo " New standby redo log groups created: $NEW_STANDBY_CREATED" | tee -a "$LOG_FILE" echo " Old redo log groups dropped: $OLD_REDO_DROPPED" | tee -a "$LOG_FILE" echo " Old standby redo log groups dropped: $OLD_STANDBY_DROPPED" | tee -a "$LOG_FILE" log "Summary also saved to $LOG_FILE" #!/bin/bash # Common functions for redo and standby redo log management log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } error_exit() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$LOG_FILE" >&2 exit 1 } exec_sqlplus() { sqlplus -s /nolog < "$FILE" } capture_existing_groups() { local CONN=$1 local VAR_REDO=$2 local VAR_STANDBY=$3 eval "$VAR_REDO=\"$(exec_sqlplus \"$CONN\" \"SELECT group# FROM v\\$log ORDER BY group#;\")\"" eval "$VAR_STANDBY=\"$(exec_sqlplus \"$CONN\" \"SELECT group# FROM v\\$standby_log ORDER BY group#;\")\"" } add_redo_log_groups() { local CONN=$1 local REDO_SIZE_MB=$2 local DB_ROLE DB_ROLE=$(exec_sqlplus "$CONN" "SELECT database_role FROM v$database;") for thr in $(exec_sqlplus "$CONN" "SELECT DISTINCT thread# FROM v$log;"); do exec_sqlplus "$CONN" "ALTER DATABASE ADD LOGFILE THREAD $thr GROUP $NEW_GROUP_ID SIZE ${REDO_SIZE_MB}M;" log "[$DB_ROLE] Added redo log group $NEW_GROUP_ID for thread $thr on $CONN." NEW_GROUP_ID=$((NEW_GROUP_ID + 1)) NEW_REDO_CREATED=$((NEW_REDO_CREATED + 1)) done } add_standby_redo_log_groups() { local CONN=$1 local STANDBY_REDO_SIZE_MB=$2 local DB_ROLE DB_ROLE=$(exec_sqlplus "$CONN" "SELECT database_role FROM v$database;") for ((thr=1; thr<=MAX_THREAD; thr++)); do for ((i=1; i<=STANDBY_LOGS_PER_THREAD; i++)); do exec_sqlplus "$CONN" "ALTER DATABASE ADD STANDBY LOGFILE THREAD $thr GROUP $NEW_GROUP_ID SIZE ${STANDBY_REDO_SIZE_MB}M;" log "[$DB_ROLE] Added standby redo log group $NEW_GROUP_ID for thread $thr on $CONN." NEW_GROUP_ID=$((NEW_GROUP_ID + 1)) NEW_STANDBY_CREATED=$((NEW_STANDBY_CREATED + 1)) done sleep 2 done } drop_redo_groups_safely() { local CONN=$1 local GROUPS=$2 for grp in $GROUPS; do while true; do exec_sqlplus "$CONN" "ALTER SYSTEM SWITCH LOGFILE;" sleep 5 exec_sqlplus "$CONN" "ALTER DATABASE DROP LOGFILE GROUP $grp;" if [ $? -eq 0 ]; then log "Dropped redo log group $grp." break else log "Retrying to drop redo log group $grp after logfile switch." sleep 5 fi done done } drop_standby_groups_safely() { local CONN=$1 local GROUPS=$2 for grp in $GROUPS; do while true; do exec_sqlplus "$CONN" "ALTER DATABASE DROP STANDBY LOGFILE GROUP $grp;" if [ $? -eq 0 ]; then log "Dropped standby redo log group $grp." break else log "Retrying to drop standby redo group $grp after clear operation." sleep 5 fi done done } text_to_html() { local input_file=$1 local output_file=$2 { echo "Report
"
    cat "$input_file"
    echo "
" } > "$output_file" log "Converted $input_file into HTML format as $output_file." } send_email() { local to_address=$1 local subject=$2 local file_to_send=$3 local file_size_kb file_size_kb=$(du -k "$file_to_send" | cut -f1) if [ "$file_size_kb" -lt 1024 ]; then mailx -a "Content-type: text/html" -s "$subject" "$to_address" < "$file_to_send" log "Sent email to $to_address with embedded HTML content." else echo "Please see attached report." | mailx -s "$subject" -a "$file_to_send" "$to_address" log "Sent email to $to_address with $file_to_send as attachment (file size > 1MB)." fi }
#!/bin/bash set -euo pipefail # Load all functions source ./functions.sh # Example Variables REPORT_DIR="./reports" LOG_DIR="./logs" mkdir -p "$REPORT_DIR" "$LOG_DIR" REPORT_NAME="db_healthcheck_$(date +%Y%m%d%H%M%S)" TEXT_REPORT="$REPORT_DIR/${REPORT_NAME}.txt" HTML_REPORT="$REPORT_DIR/${REPORT_NAME}.html" EMAIL_TO="dba_team@example.com" EMAIL_SUBJECT="Database Health Check Report - $REPORT_NAME" # Step 1: Simulate generating a text report (you would run your SQLPlus spool here) log "Generating sample text report." echo "Database Health Check - Sample Report" > "$TEXT_REPORT" echo "------------------------------------" >> "$TEXT_REPORT" echo "Tablespace Usage: OK" >> "$TEXT_REPORT" echo "Invalid Objects: 0" >> "$TEXT_REPORT" echo "Archive Usage: 75%" >> "$TEXT_REPORT" echo "No Locks Detected." >> "$TEXT_REPORT" # Step 2: Convert text report to HTML log "Converting text report to HTML." text_to_html "$TEXT_REPORT" "$HTML_REPORT" # Step 3: Send email with the HTML report log "Sending email with report." send_email "$EMAIL_TO" "$EMAIL_SUBJECT" "$HTML_REPORT" # Step 4: Done log "Health check report generated and emailed successfully." text_to_html() { local input_file=$1 local output_file=$2 { echo "Report
"
    cat "$input_file"
    echo "
" } > "$output_file" log "Converted $input_file into HTML format as $output_file." } send_email() { local to_address=$1 local subject=$2 local file_to_send=$3 local file_size_kb file_size_kb=$(du -k "$file_to_send" | cut -f1) if [ "$file_size_kb" -lt 1024 ]; then # Embed HTML content inside email mailx -a "Content-type: text/html" -s "$subject" "$to_address" < "$file_to_send" log "Sent email to $to_address with embedded HTML content." else # Attach the file echo "Please see attached report." | mailx -s "$subject" -a "$file_to_send" "$to_address" log "Sent email to $to_address with $file_to_send as attachment (file size > 1MB)." fi } #!/bin/bash # Common functions for redo and standby redo log management log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } error_exit() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$LOG_FILE" >&2 exit 1 } exec_sqlplus() { sqlplus -s /nolog < "$FILE" } capture_existing_groups() { local CONN=$1 local VAR_REDO=$2 local VAR_STANDBY=$3 eval "$VAR_REDO=\"$(exec_sqlplus \"$CONN\" \"SELECT group# FROM v\\$log ORDER BY group#;\")\"" eval "$VAR_STANDBY=\"$(exec_sqlplus \"$CONN\" \"SELECT group# FROM v\\$standby_log ORDER BY group#;\")\"" } add_redo_log_groups() { local CONN=$1 local REDO_SIZE_MB=$2 local DB_ROLE DB_ROLE=$(exec_sqlplus "$CONN" "SELECT database_role FROM v$database;") for thr in $(exec_sqlplus "$CONN" "SELECT DISTINCT thread# FROM v$log;"); do exec_sqlplus "$CONN" "ALTER DATABASE ADD LOGFILE THREAD $thr GROUP $NEW_GROUP_ID SIZE ${REDO_SIZE_MB}M;" log "[$DB_ROLE] Added redo log group $NEW_GROUP_ID for thread $thr on $CONN." NEW_GROUP_ID=$((NEW_GROUP_ID + 1)) NEW_REDO_CREATED=$((NEW_REDO_CREATED + 1)) done } add_standby_redo_log_groups() { local CONN=$1 local STANDBY_REDO_SIZE_MB=$2 local DB_ROLE DB_ROLE=$(exec_sqlplus "$CONN" "SELECT database_role FROM v$database;") for ((thr=1; thr<=MAX_THREAD; thr++)); do for ((i=1; i<=STANDBY_LOGS_PER_THREAD; i++)); do exec_sqlplus "$CONN" "ALTER DATABASE ADD STANDBY LOGFILE THREAD $thr GROUP $NEW_GROUP_ID SIZE ${STANDBY_REDO_SIZE_MB}M;" log "[$DB_ROLE] Added standby redo log group $NEW_GROUP_ID for thread $thr on $CONN." NEW_GROUP_ID=$((NEW_GROUP_ID + 1)) NEW_STANDBY_CREATED=$((NEW_STANDBY_CREATED + 1)) done sleep 2 done } drop_redo_groups_safely() { local CONN=$1 local GROUPS=$2 for grp in $GROUPS; do while true; do exec_sqlplus "$CONN" "ALTER SYSTEM SWITCH LOGFILE;" sleep 5 exec_sqlplus "$CONN" "ALTER DATABASE DROP LOGFILE GROUP $grp;" if [ $? -eq 0 ]; then log "Dropped redo log group $grp." break else log "Retrying to drop redo log group $grp after logfile switch." sleep 5 fi done done } drop_standby_groups_safely() { local CONN=$1 local GROUPS=$2 for grp in $GROUPS; do while true; do exec_sqlplus "$CONN" "ALTER DATABASE DROP STANDBY LOGFILE GROUP $grp;" if [ $? -eq 0 ]; then log "Dropped standby redo log group $grp." break else log "Retrying to drop standby redo group $grp after clear operation." sleep 5 fi done done } text_to_html() { local input_file=$1 local output_file=$2 { echo "Report
"
    cat "$input_file"
    echo "
" } > "$output_file" log "Converted $input_file into HTML format as $output_file." } send_email() { local to_address=$1 local subject=$2 local file_to_send=$3 local file_size_kb file_size_kb=$(du -k "$file_to_send" | cut -f1) if [ "$file_size_kb" -lt 1024 ]; then mailx -a "Content-type: text/html" -s "$subject" "$to_address" < "$file_to_send" log "Sent email to $to_address with embedded HTML content." else echo "Please see attached report." | mailx -s "$subject" -a "$file_to_send" "$to_address" log "Sent email to $to_address with $file_to_send as attachment (file size > 1MB)." fi } #!/bin/bash # Main script to resize redo and standby redo logs set -euo pipefail INPUT_FILE=$1 LOG_FILE="./logs/redo_log_resize_$(date +%Y%m%d%H%M%S).log" source ./functions.sh # Initialization NEW_REDO_CREATED=0 NEW_STANDBY_CREATED=0 if [ ! -f "$INPUT_FILE" ]; then error_exit "Input file not found!" fi readarray -t PARAMS < "$INPUT_FILE" DB_NAME=${PARAMS[0]:-} SYS_USER=${PARAMS[1]:-} SYS_PASSWORD=${PARAMS[2]:-} NEW_REDO_SIZE_MB=${PARAMS[3]:-} NEW_STANDBY_REDO_SIZE_MB=${PARAMS[4]:-} PRIMARY_CONN="$SYS_USER/$SYS_PASSWORD@$DB_NAME as sysdba" STANDBY_CONN="$SYS_USER/$SYS_PASSWORD@${DB_NAME}_STBY as sysdba" trap 'error_exit "Unexpected error occurred."' ERR # Capture initial state log "Capturing current redo/standby logs BEFORE changes..." save_logfile_state "$PRIMARY_CONN" "/tmp/primary_before.lst" save_logfile_state "$STANDBY_CONN" "/tmp/standby_before.lst" capture_existing_groups "$PRIMARY_CONN" PRIMARY_OLD_REDO_GROUPS PRIMARY_OLD_STANDBY_GROUPS capture_existing_groups "$STANDBY_CONN" STANDBY_OLD_REDO_GROUPS STANDBY_OLD_STANDBY_GROUPS log "Setting standby_file_management to MANUAL." exec_sqlplus "$STANDBY_CONN" "ALTER SYSTEM SET standby_file_management='MANUAL' SCOPE=BOTH;" MAX_GROUP=$(exec_sqlplus "$PRIMARY_CONN" "SELECT GREATEST(MAX(group#), NVL((SELECT MAX(group#) FROM v$standby_log), 0)) FROM v$log;") NEW_GROUP_ID=$((MAX_GROUP + 1)) MAX_THREAD=$(exec_sqlplus "$PRIMARY_CONN" "SELECT GREATEST(MAX(thread#)) FROM (SELECT thread# FROM v$log UNION SELECT thread# FROM v$standby_log);") REDO_PER_THREAD=$(exec_sqlplus "$PRIMARY_CONN" "SELECT COUNT(1) FROM v$log WHERE thread#=1;") STANDBY_LOGS_PER_THREAD=$((REDO_PER_THREAD + 1)) # Create redo logs and standby logs log "Adding new redo log groups on PRIMARY." add_redo_log_groups "$PRIMARY_CONN" "$NEW_REDO_SIZE_MB" log "Adding new redo log groups on STANDBY." add_redo_log_groups "$STANDBY_CONN" "$NEW_REDO_SIZE_MB" log "Adding new standby redo log groups on PRIMARY." add_standby_redo_log_groups "$PRIMARY_CONN" "$NEW_STANDBY_REDO_SIZE_MB" log "Adding new standby redo log groups on STANDBY." add_standby_redo_log_groups "$STANDBY_CONN" "$NEW_STANDBY_REDO_SIZE_MB" # Force logfile switch log "Switching logfile to make old redo inactive." exec_sqlplus "$PRIMARY_CONN" "ALTER SYSTEM SWITCH LOGFILE;" sleep 10 # Drop old groups log "Dropping old redo logs on PRIMARY..." drop_redo_groups_safely "$PRIMARY_CONN" "$PRIMARY_OLD_REDO_GROUPS" log "Dropping old standby redo logs on PRIMARY..." drop_standby_groups_safely "$PRIMARY_CONN" "$PRIMARY_OLD_STANDBY_GROUPS" log "Dropping old redo logs on STANDBY..." drop_redo_groups_safely "$STANDBY_CONN" "$STANDBY_OLD_REDO_GROUPS" log "Dropping old standby redo logs on STANDBY..." drop_standby_groups_safely "$STANDBY_CONN" "$STANDBY_OLD_STANDBY_GROUPS" # Reset standby management log "Setting standby_file_management back to AUTO." exec_sqlplus "$STANDBY_CONN" "ALTER SYSTEM SET standby_file_management='AUTO' SCOPE=BOTH;" # Restart managed recovery if needed STANDBY_DB_ROLE=$(exec_sqlplus "$STANDBY_CONN" "SELECT database_role FROM v$database;") if [[ "$STANDBY_DB_ROLE" == "PHYSICAL STANDBY" ]]; then log "Restarting managed recovery on standby." exec_sqlplus "$STANDBY_CONN" "ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;" fi # Capture final state log "Capturing current redo/standby logs AFTER changes..." save_logfile_state "$PRIMARY_CONN" "/tmp/primary_after.lst" save_logfile_state "$STANDBY_CONN" "/tmp/standby_after.lst" log "Diff for PRIMARY database:" diff /tmp/primary_before.lst /tmp/primary_after.lst | tee -a "$LOG_FILE" log "Diff for STANDBY database:" diff /tmp/standby_before.lst /tmp/standby_after.lst | tee -a "$LOG_FILE" rm -f /tmp/primary_before.lst /tmp/primary_after.lst /tmp/standby_before.lst /tmp/standby_after.lst log "Redo and Standby redo log resizing complete." ##################### #!/bin/bash # Common functions for redo and standby redo log management log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } error_exit() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$LOG_FILE" >&2 exit 1 } exec_sqlplus() { sqlplus -s /nolog < "$FILE" } capture_existing_groups() { local CONN=$1 local VAR_REDO=$2 local VAR_STANDBY=$3 eval "$VAR_REDO=\"\$(exec_sqlplus \"$CONN\" \"SELECT group# FROM v\\$log ORDER BY group#;\")\"" eval "$VAR_STANDBY=\"\$(exec_sqlplus \"$CONN\" \"SELECT group# FROM v\\$standby_log ORDER BY group#;\")\"" } add_redo_log_groups() { local CONN=$1 local REDO_SIZE_MB=$2 local DB_ROLE DB_ROLE=$(exec_sqlplus "$CONN" "SELECT database_role FROM v\$database;") for thr in $(exec_sqlplus "$CONN" "SELECT DISTINCT thread# FROM v\$log;"); do exec_sqlplus "$CONN" "ALTER DATABASE ADD LOGFILE THREAD $thr GROUP $NEW_GROUP_ID SIZE ${REDO_SIZE_MB}M;" log "[$DB_ROLE] Added redo log group $NEW_GROUP_ID for thread $thr on $CONN." NEW_GROUP_ID=$((NEW_GROUP_ID + 1)) NEW_REDO_CREATED=$((NEW_REDO_CREATED + 1)) done } add_standby_redo_log_groups() { local CONN=$1 local STANDBY_REDO_SIZE_MB=$2 local DB_ROLE DB_ROLE=$(exec_sqlplus "$CONN" "SELECT database_role FROM v\$database;") for ((thr=1; thr<=MAX_THREAD; thr++)); do for ((i=1; i<=STANDBY_LOGS_PER_THREAD; i++)); do exec_sqlplus "$CONN" "ALTER DATABASE ADD STANDBY LOGFILE THREAD $thr GROUP $NEW_GROUP_ID SIZE ${STANDBY_REDO_SIZE_MB}M;" log "[$DB_ROLE] Added standby redo log group $NEW_GROUP_ID for thread $thr on $CONN." NEW_GROUP_ID=$((NEW_GROUP_ID + 1)) NEW_STANDBY_CREATED=$((NEW_STANDBY_CREATED + 1)) done sleep 2 done } drop_redo_groups_safely() { local CONN=$1 local GROUPS=$2 for grp in $GROUPS; do while true; do exec_sqlplus "$CONN" "ALTER SYSTEM SWITCH LOGFILE;" sleep 5 exec_sqlplus "$CONN" "ALTER DATABASE DROP LOGFILE GROUP $grp;" if [ $? -eq 0 ]; then log "Dropped redo log group $grp." break else log "Retrying to drop redo log group $grp after logfile switch." sleep 5 fi done done } drop_standby_groups_safely() { local CONN=$1 local GROUPS=$2 for grp in $GROUPS; do while true; do exec_sqlplus "$CONN" "ALTER DATABASE DROP STANDBY LOGFILE GROUP $grp;" if [ $? -eq 0 ]; then log "Dropped standby redo log group $grp." break else log "Retrying to drop standby redo group $grp after clear operation." sleep 5 fi done done }
#!/bin/bash # Shell script to resize Oracle database redo log files (primary and standby). # This script adds new redo log groups of a specified size to an Oracle primary database (and corresponding standby redo logs on the standby database), # then drops the old redo log groups and standby redo log groups. It handles Oracle RAC by processing each thread, # and Data Guard by performing actions on both primary and standby. # # Usage: resize_redo_logs.sh # The param_file should be a text file containing five lines with the following values: # 1. Database name (TNS alias or ORACLE_SID of the primary database) # 2. SYS user name (usually 'SYS') # 3. SYS password # 4. New redo log size in MB (for primary online redo logs) # 5. New standby redo log size in MB (for standby database) # # Example param_file: # ORCL # SYS # MySysPassword # 1024 # 1024 # # Note: Ensure the standby database is accessible via a TNS alias derived from the DB name (e.g., ORCL_STBY), # or adjust the script to use the correct connection string for the standby. # The script assumes Oracle Managed Files (OMF) or ASM is configured so that explicit file paths are not required # when adding log files. If OMF is not used, you may need to modify the ALTER DATABASE ADD LOGFILE commands # to include file paths. # # The script will: # - Connect to the primary database and retrieve existing redo log group numbers and threads. # - Connect to the standby database and retrieve existing standby redo log group numbers and threads. # - On the primary: Add new redo log groups for each thread with the specified size. # - Force log switches on the primary to ensure old logs are archived and not active. # - Drop old redo log groups on the primary. # - On the standby: Stop Redo Apply (managed recovery) if active, and set STANDBY_FILE_MANAGEMENT to MANUAL. # - Add new standby redo log groups for each thread with the specified size. # - Drop old standby redo log groups on the standby. # - Resume Redo Apply on the standby and set STANDBY_FILE_MANAGEMENT back to AUTO (if it was originally AUTO). # - Handle errors during drops by retrying or continuing without exiting the script. # # Make sure ORACLE_HOME is set and SQL*Plus is in PATH for the user running this script. # Also ensure the SYS password is correct and the user has SYSDBA privileges on both primary and standby. # # Begin script # Validate arguments if [ $# -ne 1 ]; then echo "Usage: $0 " exit 1 fi PARAM_FILE="$1" if [ ! -f "$PARAM_FILE" ]; then echo "Parameter file '$PARAM_FILE' not found!" exit 1 fi # Read parameters from file (5 lines as specified) DB_NAME="" SYS_USER="" SYS_PASS="" NEW_REDO_MB="" NEW_STANDBY_MB="" { IFS= read -r DB_NAME IFS= read -r SYS_USER IFS= read -r SYS_PASS IFS= read -r NEW_REDO_MB IFS= read -r NEW_STANDBY_MB } < "$PARAM_FILE" # Trim whitespace DB_NAME=$(echo "$DB_NAME" | xargs) SYS_USER=$(echo "$SYS_USER" | xargs) SYS_PASS=$(echo "$SYS_PASS" | xargs) NEW_REDO_MB=$(echo "$NEW_REDO_MB" | xargs) NEW_STANDBY_MB=$(echo "$NEW_STANDBY_MB" | xargs) # Validate parameters if [ -z "$DB_NAME" ] || [ -z "$SYS_USER" ] || [ -z "$SYS_PASS" ] || [ -z "$NEW_REDO_MB" ] || [ -z "$NEW_STANDBY_MB" ]; then echo "Parameter file must contain five values: DB_NAME, SYS_USER, SYS_PASS, NEW_REDO_MB, NEW_STANDBY_MB." exit 1 fi # Ensure sizes are numeric if ! [[ "$NEW_REDO_MB" =~ ^[0-9]+$ ]]; then echo "New redo log size (MB) must be a positive integer." exit 1 fi if ! [[ "$NEW_STANDBY_MB" =~ ^[0-9]+$ ]]; then echo "New standby redo log size (MB) must be a positive integer." exit 1 fi # Connection strings (adjust if needed for standby) PRIMARY_CONN="${SYS_USER}/${SYS_PASS}@${DB_NAME} as sysdba" STANDBY_CONN="${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY as sysdba" echo "Starting redo log resize procedure for database '$DB_NAME'..." # Test connection to primary echo " - Testing connection to primary database..." sqlplus -s /nolog <<-EOSQL WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA EXIT EOSQL if [ $? -ne 0 ]; then echo "ERROR: Unable to connect to primary database '${DB_NAME}' as SYSDBA." exit 1 fi # Test connection to standby echo " - Testing connection to standby database..." sqlplus -s /nolog <<-EOSQL WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA EXIT EOSQL if [ $? -ne 0 ]; then echo "ERROR: Unable to connect to standby database '${DB_NAME}_STBY' as SYSDBA." echo "Ensure the standby TNS alias is correct and the standby database is reachable." exit 1 fi # Retrieve current redo log groups from primary echo " - Retrieving current redo log configuration from primary..." primary_log_info=$(sqlplus -s /nolog <<-EOSQL SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA SELECT group# || ',' || thread# FROM v\\$log ORDER BY thread#, group#; EXIT EOSQL ) if [ -z "$primary_log_info" ]; then echo "ERROR: Failed to fetch redo log information from primary database." exit 1 fi # Retrieve current standby redo log groups from standby echo " - Retrieving current standby redo log configuration from standby..." standby_log_info=$(sqlplus -s /nolog <<-EOSQL SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA SELECT group# || ',' || thread# FROM v\\$standby_log ORDER BY thread#, group#; EXIT EOSQL ) # Parse primary redo log info into arrays OLD_PRIMARY_GROUPS=() OLD_PRIMARY_THREADS=() while IFS=',' read -r grp thr; do if [ -n "$grp" ] && [ -n "$thr" ]; then OLD_PRIMARY_GROUPS+=("$grp") OLD_PRIMARY_THREADS+=("$thr") fi done <<< "$primary_log_info" # Parse standby redo log info into arrays (if any) OLD_STANDBY_GROUPS=() OLD_STANDBY_THREADS=() if [ -n "$standby_log_info" ]; then while IFS=',' read -r grp thr; do if [ -n "$grp" ] && [ -n "$thr" ]; then OLD_STANDBY_GROUPS+=("$grp") OLD_STANDBY_THREADS+=("$thr") fi done <<< "$standby_log_info" fi # Unique thread IDs on primary and standby THREADS_ON_PRIMARY=($(printf "%s\n" "${OLD_PRIMARY_THREADS[@]}" | sort -n | uniq)) THREADS_ON_STANDBY=($(printf "%s\n" "${OLD_STANDBY_THREADS[@]}" | sort -n | uniq)) echo " Found ${#OLD_PRIMARY_GROUPS[@]} redo log groups on primary across ${#THREADS_ON_PRIMARY[@]} thread(s)." if [ ${#OLD_STANDBY_GROUPS[@]} -gt 0 ]; then echo " Found ${#OLD_STANDBY_GROUPS[@]} standby redo log groups on standby across ${#THREADS_ON_STANDBY[@]} thread(s)." else echo " No standby redo logs found on standby (physical standby should have standby logs)." fi # Determine starting group numbers for new logs to avoid conflicts max_primary_group=0 for g in "${OLD_PRIMARY_GROUPS[@]}"; do if [ "$g" -gt "$max_primary_group" ]; then max_primary_group=$g fi done max_standby_group=0 for g in "${OLD_STANDBY_GROUPS[@]}"; do if [ "$g" -gt "$max_standby_group" ]; then max_standby_group=$g fi done new_primary_group=$((max_primary_group + 1)) # For standby, start after the larger of current primary or standby group numbers new_standby_group=$(( (max_standby_group > max_primary_group ? max_standby_group : max_primary_group) + 1 )) # Defer log shipping on primary (to standby) during changes echo " - Deferring log transport to standby on primary..." sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA BEGIN FOR dest IN ( SELECT dest_id FROM v\\$archive_dest WHERE target='STANDBY' AND status='VALID' AND dest_state='ENABLE' ) LOOP EXECUTE IMMEDIATE 'ALTER SYSTEM SET LOG_ARCHIVE_DEST_STATE_' || dest.dest_id || '=DEFER SCOPE=MEMORY'; END LOOP; END; / EXIT EOSQL # Check current STANDBY_FILE_MANAGEMENT mode on standby orig_standby_mgmt=$(sqlplus -s /nolog <<-EOSQL SET HEAD OFF FEEDBACK OFF CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA SELECT value FROM v\\$parameter WHERE name='standby_file_management'; EXIT EOSQL ) orig_standby_mgmt=$(echo "$orig_standby_mgmt" | xargs) if [ -z "$orig_standby_mgmt" ]; then echo "WARNING: Could not determine STANDBY_FILE_MANAGEMENT mode on standby." fi # Cancel managed recovery on standby (if active) and set STANDBY_FILE_MANAGEMENT to MANUAL echo " - Preparing standby for changes (cancelling Redo Apply and setting STANDBY_FILE_MANAGEMENT to MANUAL)..." sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA BEGIN EXECUTE IMMEDIATE 'ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL'; -- ignore error if not active EXCEPTION WHEN OTHERS THEN NULL; END; / ALTER SYSTEM SET standby_file_management='MANUAL' SCOPE=BOTH; EXIT EOSQL # Add new redo log groups on primary echo " - Adding new ${NEW_REDO_MB}MB redo log group(s) on primary..." ADD_REDO_CMDS="" max_old_count=0 for thr in "${THREADS_ON_PRIMARY[@]}"; do count=0 for idx in "${!OLD_PRIMARY_THREADS[@]}"; do if [ "${OLD_PRIMARY_THREADS[$idx]}" = "$thr" ]; then count=$((count+1)) fi done if [ $count -gt $max_old_count ]; then max_old_count=$count fi # Create same number of new log groups for this thread for ((i=1; i<=count; i++)); do ADD_REDO_CMDS+="ALTER DATABASE ADD LOGFILE THREAD $thr GROUP $new_primary_group SIZE ${NEW_REDO_MB}M;\n" new_primary_group=$((new_primary_group + 1)) done done sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA $ADD_REDO_CMDS EXIT EOSQL # Add new standby redo log groups on standby echo " - Adding new ${NEW_STANDBY_MB}MB standby redo log group(s) on standby..." ADD_STANDBY_CMDS="" if [ ${#OLD_STANDBY_GROUPS[@]} -gt 0 ]; then # Use same count of standby logs per thread as existing for thr in "${THREADS_ON_STANDBY[@]}"; do count=0 for idx in "${!OLD_STANDBY_THREADS[@]}"; do if [ "${OLD_STANDBY_THREADS[$idx]}" = "$thr" ]; then count=$((count+1)) fi done for ((i=1; i<=count; i++)); do ADD_STANDBY_CMDS+="ALTER DATABASE ADD STANDBY LOGFILE THREAD $thr GROUP $new_standby_group SIZE ${NEW_STANDBY_MB}M;\n" new_standby_group=$((new_standby_group + 1)) done done else # If no standby logs present, create same number as primary's online logs per thread for thr in "${THREADS_ON_PRIMARY[@]}"; do count=0 for idx in "${!OLD_PRIMARY_THREADS[@]}"; do if [ "${OLD_PRIMARY_THREADS[$idx]}" = "$thr" ]; then count=$((count+1)) fi done # (Optionally add +1 here to have standby logs = primary count + 1) for ((i=1; i<=count; i++)); do ADD_STANDBY_CMDS+="ALTER DATABASE ADD STANDBY LOGFILE THREAD $thr GROUP $new_standby_group SIZE ${NEW_STANDBY_MB}M;\n" new_standby_group=$((new_standby_group + 1)) done done fi sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA $ADD_STANDBY_CMDS EXIT EOSQL # Force log switches on primary to archive old redo logs (archives all threads in RAC)​:contentReference[oaicite:0]{index=0} echo " - Forcing log switch on primary to archive old redo logs..." ARCHIVE_CMDS="" # Perform multiple log switches (for RAC, one ALTER SYSTEM ARCHIVE LOG CURRENT covers all threads) switch_count=$((max_old_count + 1)) for ((i=1; i<=switch_count; i++)); do ARCHIVE_CMDS+="ALTER SYSTEM ARCHIVE LOG CURRENT;\n" done sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA $ARCHIVE_CMDS EXIT EOSQL # Drop old redo log groups on primary echo " - Dropping old redo log groups on primary..." for idx in "${!OLD_PRIMARY_GROUPS[@]}"; do g=${OLD_PRIMARY_GROUPS[$idx]} thr=${OLD_PRIMARY_THREADS[$idx]} # Skip if group number overlaps new ones (shouldn't happen, as new_group starts above max old) if [ "$g" -ge "$new_primary_group" ]; then continue fi # Attempt to drop the redo log group drop_output=$(sqlplus -s /nolog <<-EOSQL WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA ALTER DATABASE DROP LOGFILE GROUP $g; EXIT EOSQL ) if [ $? -ne 0 ]; then # Drop failed if echo "$drop_output" | grep -q -E "ORA-01623|ORA-01624"; then echo " Redo log group $g (thread $thr) is active or current; switching log and retrying drop..." sqlplus -s /nolog <<-EOSQL2 CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA ALTER SYSTEM ARCHIVE LOG CURRENT; EXIT EOSQL2 drop_output2=$(sqlplus -s /nolog <<-EOSQL3 WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA ALTER DATABASE DROP LOGFILE GROUP $g; EXIT EOSQL3 ) if [ $? -ne 0 ]; then echo "WARNING: Could not drop redo log group $g on primary after retry. Please remove it manually if needed." else echo " Dropped redo log group $g on primary (after retry)." fi else echo "WARNING: Failed to drop redo log group $g on primary. Error: $(echo "$drop_output" | tr -d '\n')" fi else echo " Dropped redo log group $g on primary." fi done # Drop old standby redo log groups on standby echo " - Dropping old standby redo log groups on standby..." for idx in "${!OLD_STANDBY_GROUPS[@]}"; do g=${OLD_STANDBY_GROUPS[$idx]} thr=${OLD_STANDBY_THREADS[$idx]} if [ "$g" -ge "$new_standby_group" ]; then continue fi drop_output=$(sqlplus -s /nolog <<-EOSQL WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA ALTER DATABASE DROP STANDBY LOGFILE GROUP $g; EXIT EOSQL ) if [ $? -ne 0 ]; then # Drop failed if echo "$drop_output" | grep -q "ORA-00261"; then echo " Standby redo log group $g is being archived; clearing and retrying..." clear_out=$(sqlplus -s /nolog <<-EOSQL2 WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA ALTER DATABASE CLEAR LOGFILE GROUP $g; EXIT EOSQL2 ) if [ $? -ne 0 ]; then echo "WARNING: Could not clear standby redo log group $g. Please check it manually." else drop_output2=$(sqlplus -s /nolog <<-EOSQL3 WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA ALTER DATABASE DROP STANDBY LOGFILE GROUP $g; EXIT EOSQL3 ) if [ $? -ne 0 ]; then echo "WARNING: Could not drop standby redo log group $g on standby after clearing. Please remove it manually if needed." else echo " Dropped standby redo log group $g on standby (after clearing)." end of conversation 37] ## Oracle Redo Log Resizing Shell Script ```bash #!/bin/bash # Shell script to resize Oracle database redo log files (primary and standby). # This script adds new redo log groups of a specified size to an Oracle primary database (and corresponding standby redo logs on the standby database), # then drops the old redo log groups and standby redo log groups. It handles Oracle RAC by processing each thread, # and Data Guard by performing actions on both primary and standby. # # Usage: resize_redo_logs.sh # The param_file should be a text file containing five lines with the following values: # 1. Database name (TNS alias or ORACLE_SID of the primary database) # 2. SYS user name (usually 'SYS') # 3. SYS password # 4. New redo log size in MB (for primary online redo logs) # 5. New standby redo log size in MB (for standby database) # # Example param_file: # ORCL # SYS # MySysPassword # 1024 # 1024 # # Note: Ensure the standby database is accessible via a TNS alias derived from the DB name (e.g., ORCL_STBY), # or adjust the script to use the correct connection string for the standby. # The script assumes Oracle Managed Files (OMF) or ASM is configured so that explicit file paths are not required # when adding log files. If OMF is not used, you may need to modify the ALTER DATABASE ADD LOGFILE commands # to include file paths. # # The script will: # - Connect to the primary database and retrieve existing redo log group numbers and threads. # - Connect to the standby database and retrieve existing standby redo log group numbers and threads. # - On the primary: Add new redo log groups for each thread with the specified size. # - Force log switches on the primary to ensure old logs are archived and not active. # - Drop old redo log groups on the primary. # - On the standby: Stop Redo Apply (managed recovery) if active, and set STANDBY_FILE_MANAGEMENT to MANUAL. # - Add new standby redo log groups for each thread with the specified size. # - Drop old standby redo log groups on the standby. # - Resume Redo Apply on the standby and set STANDBY_FILE_MANAGEMENT back to AUTO (if it was originally AUTO). # - Handle errors during drops by retrying or continuing without exiting the script. # # Make sure ORACLE_HOME is set and SQL*Plus is in PATH for the user running this script. # Also ensure the SYS password is correct and the user has SYSDBA privileges on both primary and standby. # # Begin script # Validate arguments if [ $# -ne 1 ]; then echo "Usage: $0 " exit 1 fi PARAM_FILE="$1" if [ ! -f "$PARAM_FILE" ]; then echo "Parameter file '$PARAM_FILE' not found!" exit 1 fi # Read parameters from file (5 lines as specified) DB_NAME="" SYS_USER="" SYS_PASS="" NEW_REDO_MB="" NEW_STANDBY_MB="" { IFS= read -r DB_NAME IFS= read -r SYS_USER IFS= read -r SYS_PASS IFS= read -r NEW_REDO_MB IFS= read -r NEW_STANDBY_MB } < "$PARAM_FILE" # Trim whitespace DB_NAME=$(echo "$DB_NAME" | xargs) SYS_USER=$(echo "$SYS_USER" | xargs) SYS_PASS=$(echo "$SYS_PASS" | xargs) NEW_REDO_MB=$(echo "$NEW_REDO_MB" | xargs) NEW_STANDBY_MB=$(echo "$NEW_STANDBY_MB" | xargs) # Validate parameters if [ -z "$DB_NAME" ] || [ -z "$SYS_USER" ] || [ -z "$SYS_PASS" ] || [ -z "$NEW_REDO_MB" ] || [ -z "$NEW_STANDBY_MB" ]; then echo "Parameter file must contain five values: DB_NAME, SYS_USER, SYS_PASS, NEW_REDO_MB, NEW_STANDBY_MB." exit 1 fi # Ensure sizes are numeric if ! [[ "$NEW_REDO_MB" =~ ^[0-9]+$ ]]; then echo "New redo log size (MB) must be a positive integer." exit 1 fi if ! [[ "$NEW_STANDBY_MB" =~ ^[0-9]+$ ]]; then echo "New standby redo log size (MB) must be a positive integer." exit 1 fi # Connection strings (adjust if needed for standby) PRIMARY_CONN="${SYS_USER}/${SYS_PASS}@${DB_NAME} as sysdba" STANDBY_CONN="${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY as sysdba" echo "Starting redo log resize procedure for database '$DB_NAME'..." # Test connection to primary echo " - Testing connection to primary database..." sqlplus -s /nolog <<-EOSQL WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA EXIT EOSQL if [ $? -ne 0 ]; then echo "ERROR: Unable to connect to primary database '${DB_NAME}' as SYSDBA." exit 1 fi # Test connection to standby echo " - Testing connection to standby database..." sqlplus -s /nolog <<-EOSQL WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA EXIT EOSQL if [ $? -ne 0 ]; then echo "ERROR: Unable to connect to standby database '${DB_NAME}_STBY' as SYSDBA." echo "Ensure the standby TNS alias is correct and the standby database is reachable." exit 1 fi # Retrieve current redo log groups from primary echo " - Retrieving current redo log configuration from primary..." primary_log_info=$(sqlplus -s /nolog <<-EOSQL SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA SELECT group# || ',' || thread# FROM v\\$log ORDER BY thread#, group#; EXIT EOSQL ) if [ -z "$primary_log_info" ]; then echo "ERROR: Failed to fetch redo log information from primary database." exit 1 fi # Retrieve current standby redo log groups from standby echo " - Retrieving current standby redo log configuration from standby..." standby_log_info=$(sqlplus -s /nolog <<-EOSQL SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA SELECT group# || ',' || thread# FROM v\\$standby_log ORDER BY thread#, group#; EXIT EOSQL ) # Parse primary redo log info into arrays OLD_PRIMARY_GROUPS=() OLD_PRIMARY_THREADS=() while IFS=',' read -r grp thr; do if [ -n "$grp" ] && [ -n "$thr" ]; then OLD_PRIMARY_GROUPS+=("$grp") OLD_PRIMARY_THREADS+=("$thr") fi done <<< "$primary_log_info" # Parse standby redo log info into arrays (if any) OLD_STANDBY_GROUPS=() OLD_STANDBY_THREADS=() if [ -n "$standby_log_info" ]; then while IFS=',' read -r grp thr; do if [ -n "$grp" ] && [ -n "$thr" ]; then OLD_STANDBY_GROUPS+=("$grp") OLD_STANDBY_THREADS+=("$thr") fi done <<< "$standby_log_info" fi # Unique thread IDs on primary and standby THREADS_ON_PRIMARY=($(printf "%s\n" "${OLD_PRIMARY_THREADS[@]}" | sort -n | uniq)) THREADS_ON_STANDBY=($(printf "%s\n" "${OLD_STANDBY_THREADS[@]}" | sort -n | uniq)) echo " Found ${#OLD_PRIMARY_GROUPS[@]} redo log groups on primary across ${#THREADS_ON_PRIMARY[@]} thread(s)." if [ ${#OLD_STANDBY_GROUPS[@]} -gt 0 ]; then echo " Found ${#OLD_STANDBY_GROUPS[@]} standby redo log groups on standby across ${#THREADS_ON_STANDBY[@]} thread(s)." else echo " No standby redo logs found on standby (physical standby should have standby logs)." fi # Determine starting group numbers for new logs to avoid conflicts max_primary_group=0 for g in "${OLD_PRIMARY_GROUPS[@]}"; do if [ "$g" -gt "$max_primary_group" ]; then max_primary_group=$g fi done max_standby_group=0 for g in "${OLD_STANDBY_GROUPS[@]}"; do if [ "$g" -gt "$max_standby_group" ]; then max_standby_group=$g fi done new_primary_group=$((max_primary_group + 1)) # For standby, start after the larger of current primary or standby group numbers new_standby_group=$(( (max_standby_group > max_primary_group ? max_standby_group : max_primary_group) + 1 )) # Defer log shipping on primary (to standby) during changes echo " - Deferring log transport to standby on primary..." sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA BEGIN FOR dest IN ( SELECT dest_id FROM v\\$archive_dest WHERE target='STANDBY' AND status='VALID' AND dest_state='ENABLE' ) LOOP EXECUTE IMMEDIATE 'ALTER SYSTEM SET LOG_ARCHIVE_DEST_STATE_' || dest.dest_id || '=DEFER SCOPE=MEMORY'; END LOOP; END; / EXIT EOSQL # Check current STANDBY_FILE_MANAGEMENT mode on standby orig_standby_mgmt=$(sqlplus -s /nolog <<-EOSQL SET HEAD OFF FEEDBACK OFF CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA SELECT value FROM v\\$parameter WHERE name='standby_file_management'; EXIT EOSQL ) orig_standby_mgmt=$(echo "$orig_standby_mgmt" | xargs) if [ -z "$orig_standby_mgmt" ]; then echo "WARNING: Could not determine STANDBY_FILE_MANAGEMENT mode on standby." fi # Cancel managed recovery on standby (if active) and set STANDBY_FILE_MANAGEMENT to MANUAL echo " - Preparing standby for changes (cancelling Redo Apply and setting STANDBY_FILE_MANAGEMENT to MANUAL)..." sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA BEGIN EXECUTE IMMEDIATE 'ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL'; -- ignore error if not active EXCEPTION WHEN OTHERS THEN NULL; END; / ALTER SYSTEM SET standby_file_management='MANUAL' SCOPE=BOTH; EXIT EOSQL # Add new redo log groups on primary echo " - Adding new ${NEW_REDO_MB}MB redo log group(s) on primary..." ADD_REDO_CMDS="" max_old_count=0 for thr in "${THREADS_ON_PRIMARY[@]}"; do count=0 for idx in "${!OLD_PRIMARY_THREADS[@]}"; do if [ "${OLD_PRIMARY_THREADS[$idx]}" = "$thr" ]; then count=$((count+1)) fi done if [ $count -gt $max_old_count ]; then max_old_count=$count fi # Create same number of new log groups for this thread for ((i=1; i<=count; i++)); do ADD_REDO_CMDS+="ALTER DATABASE ADD LOGFILE THREAD $thr GROUP $new_primary_group SIZE ${NEW_REDO_MB}M;\n" new_primary_group=$((new_primary_group + 1)) done done sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA $ADD_REDO_CMDS EXIT EOSQL # Add new standby redo log groups on standby echo " - Adding new ${NEW_STANDBY_MB}MB standby redo log group(s) on standby..." ADD_STANDBY_CMDS="" if [ ${#OLD_STANDBY_GROUPS[@]} -gt 0 ]; then # Use same count of standby logs per thread as existing for thr in "${THREADS_ON_STANDBY[@]}"; do count=0 for idx in "${!OLD_STANDBY_THREADS[@]}"; do if [ "${OLD_STANDBY_THREADS[$idx]}" = "$thr" ]; then count=$((count+1)) fi done for ((i=1; i<=count; i++)); do ADD_STANDBY_CMDS+="ALTER DATABASE ADD STANDBY LOGFILE THREAD $thr GROUP $new_standby_group SIZE ${NEW_STANDBY_MB}M;\n" new_standby_group=$((new_standby_group + 1)) done done else # If no standby logs present, create same number as primary's online logs per thread for thr in "${THREADS_ON_PRIMARY[@]}"; do count=0 for idx in "${!OLD_PRIMARY_THREADS[@]}"; do if [ "${OLD_PRIMARY_THREADS[$idx]}" = "$thr" ]; then count=$((count+1)) fi done # (Optionally add +1 here to have standby logs = primary count + 1) for ((i=1; i<=count; i++)); do ADD_STANDBY_CMDS+="ALTER DATABASE ADD STANDBY LOGFILE THREAD $thr GROUP $new_standby_group SIZE ${NEW_STANDBY_MB}M;\n" new_standby_group=$((new_standby_group + 1)) done done fi sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA $ADD_STANDBY_CMDS EXIT EOSQL # Force log switches on primary to archive old redo logs (archives all threads in RAC​:contentReference[oaicite:1]{index=1}】 echo " - Forcing log switch on primary to archive old redo logs..." ARCHIVE_CMDS="" # Perform multiple log switches (for RAC, one ALTER SYSTEM ARCHIVE LOG CURRENT covers all threads) switch_count=$((max_old_count + 1)) for ((i=1; i<=switch_count; i++)); do ARCHIVE_CMDS+="ALTER SYSTEM ARCHIVE LOG CURRENT;\n" done sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA $ARCHIVE_CMDS EXIT EOSQL # Drop old redo log groups on primary echo " - Dropping old redo log groups on primary..." for idx in "${!OLD_PRIMARY_GROUPS[@]}"; do g=${OLD_PRIMARY_GROUPS[$idx]} thr=${OLD_PRIMARY_THREADS[$idx]} # Skip if group number overlaps new ones (shouldn't happen, as new_group starts above max old) if [ "$g" -ge "$new_primary_group" ]; then continue fi # Attempt to drop the redo log group drop_output=$(sqlplus -s /nolog <<-EOSQL WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA ALTER DATABASE DROP LOGFILE GROUP $g; EXIT EOSQL ) if [ $? -ne 0 ]; then # Drop failed if echo "$drop_output" | grep -q -E "ORA-01623|ORA-01624"; then echo " Redo log group $g (thread $thr) is active or current; switching log and retrying drop..." sqlplus -s /nolog <<-EOSQL2 CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA ALTER SYSTEM ARCHIVE LOG CURRENT; EXIT EOSQL2 drop_output2=$(sqlplus -s /nolog <<-EOSQL3 WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA ALTER DATABASE DROP LOGFILE GROUP $g; EXIT EOSQL3 ) if [ $? -ne 0 ]; then echo "WARNING: Could not drop redo log group $g on primary after retry. Please remove it manually if needed." else echo " Dropped redo log group $g on primary (after retry)." fi else echo "WARNING: Failed to drop redo log group $g on primary. Error: $(echo "$drop_output" | tr -d '\n')" fi else echo " Dropped redo log group $g on primary." fi done # Drop old standby redo log groups on standby echo " - Dropping old standby redo log groups on standby..." for idx in "${!OLD_STANDBY_GROUPS[@]}"; do g=${OLD_STANDBY_GROUPS[$idx]} thr=${OLD_STANDBY_THREADS[$idx]} if [ "$g" -ge "$new_standby_group" ]; then continue fi drop_output=$(sqlplus -s /nolog <<-EOSQL WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA ALTER DATABASE DROP STANDBY LOGFILE GROUP $g; EXIT EOSQL ) if [ $? -ne 0 ]; then # Drop failed if echo "$drop_output" | grep -q "ORA-00261"; then echo " Standby redo log group $g is being archived; clearing and retrying..." clear_out=$(sqlplus -s /nolog <<-EOSQL2 WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA ALTER DATABASE CLEAR LOGFILE GROUP $g; EXIT EOSQL2 ) if [ $? -ne 0 ]; then echo "WARNING: Could not clear standby redo log group $g. Please check it manually." else drop_output2=$(sqlplus -s /nolog <<-EOSQL3 WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA ALTER DATABASE DROP STANDBY LOGFILE GROUP $g; EXIT EOSQL3 ) if [ $? -ne 0 ]; then echo "WARNING: Could not drop standby redo log group $g on standby after clearing. Please remove it manually if needed." else echo " Dropped standby redo log group $g on standby (after clearing)." fi fi elif echo "$drop_output" | grep -q -E "ORA-01623|ORA-01624"; then echo " Standby redo log group $g is active or current; retrying drop after an extra log switch on primary..." sqlplus -s /nolog <<-EOSQL4 CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA ALTER SYSTEM ARCHIVE LOG CURRENT; EXIT EOSQL4 drop_output3=$(sqlplus -s /nolog <<-EOSQL5 WHENEVER SQLERROR EXIT FAILURE CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA ALTER DATABASE DROP STANDBY LOGFILE GROUP $g; EXIT EOSQL5 ) if [ $? -ne 0 ]; then echo "WARNING: Could not drop standby redo log group $g on standby after retry. Please remove it manually if needed." else echo " Dropped standby redo log group $g on standby (after retry)." fi else echo "WARNING: Failed to drop standby redo log group $g on standby. Error: $(echo "$drop_output" | tr -d '\n')" fi else echo " Dropped standby redo log group $g on standby." fi done # Resume managed recovery on standby and restore STANDBY_FILE_MANAGEMENT setting echo " - Resuming Redo Apply on standby and restoring STANDBY_FILE_MANAGEMENT mode..." sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME}_STBY AS SYSDBA $( [ "$orig_standby_mgmt" = "AUTO" ] && printf "%s" "ALTER SYSTEM SET standby_file_management='AUTO' SCOPE=BOTH;" ) ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION; EXIT EOSQL # Re-enable log shipping from primary to standby echo " - Re-enabling log transport to standby on primary..." sqlplus -s /nolog <<-EOSQL CONNECT ${SYS_USER}/${SYS_PASS}@${DB_NAME} AS SYSDBA BEGIN FOR dest IN ( SELECT dest_id FROM v\\$archive_dest WHERE target='STANDBY' AND dest_state='DEFER' ) LOOP EXECUTE IMMEDIATE 'ALTER SYSTEM SET LOG_ARCHIVE_DEST_STATE_' || dest.dest_id || '=ENABLE SCOPE=MEMORY'; END LOOP; END; / EXIT EOSQL echo "Redo log resize procedure completed." #!/bin/bash # Script to resize redo and standby redo logs using sqlplus & dgmgrl connections. # Accepts input from a provided text file. # Input file format: # DB_NAME # SYS_USER # SYS_PASSWORD # NEW_REDO_SIZE_MB # NEW_STANDBY_REDO_SIZE_MB INPUT_FILE=$1 if [ ! -f "$INPUT_FILE" ]; then echo "Input file not found!" exit 1 fi readarray -t PARAMS < "$INPUT_FILE" DB_NAME=${PARAMS[0]} SYS_USER=${PARAMS[1]} SYS_PASSWORD=${PARAMS[2]} NEW_REDO_SIZE_MB=${PARAMS[3]} NEW_STANDBY_REDO_SIZE_MB=${PARAMS[4]} PRIMARY_CONN="$SYS_USER/$SYS_PASSWORD@$DB_NAME as sysdba" STANDBY_CONN="$SYS_USER/$SYS_PASSWORD@${DB_NAME}_STBY as sysdba" function exec_sqlplus { sqlplus -s /nolog < # Initialize environment INPUT_FILE=$1 LOG_FILE="redo_recreation_$(date +%Y%m%d%H%M%S).log" TMP_DIR="/tmp/redo_workdir_$(date +%s)" mkdir -p $TMP_DIR # Load configuration source $INPUT_FILE || { echo "Error loading input file"; exit 1; } # Database connections PRIMARY_CONN="${SYS_USER}/${SYS_PASSWORD}@${DB_NAME} as sysdba" STANDBY_DB=$(dgmgrl -silent ${PRIMARY_CONN} < $TMP_DIR/primary_groups.lst log "Capturing standby database configuration" capture_groups "$STANDBY_CONN" STANDBY > $TMP_DIR/standby_groups.lst # Create new groups on both databases for db_type in PRIMARY STANDBY; do conn=$([ "$db_type" = "PRIMARY" ] && echo "$PRIMARY_CONN" || echo "$STANDBY_CONN") input_file=$TMP_DIR/${db_type,,}_groups.lst # Create primary redo logs log "Creating new PRIMARY redo logs on $db_type" awk -F: '/:PRIMARY:/{print $2}' $input_file | sort -nu | while read thread; do count=$(grep ":PRIMARY:$db_type" $input_file | grep ":$thread:" | wc -l) create_new_groups "$conn" "PRIMARY" "$NEW_REDO_SIZE" "$thread" "$count" done # Create standby redo logs log "Creating new STANDBY redo logs on $db_type" awk -F: '/:STANDBY:/{print $2}' $input_file | sort -nu | while read thread; do count=$(grep ":STANDBY:$db_type" $input_file | grep ":$thread:" | wc -l) create_new_groups "$conn" "STANDBY" "$NEW_STANDBY_REDO_SIZE" "$thread" "$count" done done # Force log switches to age out old groups log "Initiating log switches to inactive old groups" for i in {1..10}; do sqlplus -S /nolog <

Friday, April 25, 2025

#!/bin/bash # Script: recreate_redologs.sh # Usage: ./recreate_redologs.sh # Initialize variables INPUT_FILE=$1 LOG_FILE="redo_recreation_$(date +%Y%m%d%H%M%S).log" TMP_DIR="/tmp/redo_workdir" mkdir -p $TMP_DIR # Load input parameters source $INPUT_FILE || { echo "Error loading input file"; exit 1; } # Database connection strings PRIMARY_CONN="${SYS_USER}/${SYS_PASSWORD}@${DB_NAME}" # Get standby DB name using dgmgrl get_standby_db() { dgmgrl -silent ${PRIMARY_CONN} < $TMP_DIR/primary_groups.lst log "Capturing existing groups on standby" capture_groups $STANDBY_CONN STANDBY > $TMP_DIR/standby_groups.lst # Create new groups on primary log "Creating new primary redo logs" awk -F, '/PRIMARY/{print $2}' $TMP_DIR/primary_groups.lst | sort -u | while read thread; do count=$(grep ",$thread,PRIMARY" $TMP_DIR/primary_groups.lst | wc -l) create_new_groups $PRIMARY_CONN PRIMARY $NEW_REDO_SIZE $thread $count done # Create new standby logs on standby log "Creating new standby redo logs" awk -F, '/STANDBY/{print $2}' $TMP_DIR/standby_groups.lst | sort -u | while read thread; do count=$(grep ",$thread,STANDBY" $TMP_DIR/standby_groups.lst | wc -l) create_new_groups $STANDBY_CONN STANDBY $NEW_STANDBY_REDO_SIZE $thread $count done # Switch logs and wait for old groups to become inactive log "Forcing log switches on primary" for i in {1..10}; do sqlplus -S $PRIMARY_CONN <