Thursday, April 17, 2025

#!/bin/bash # Configuration Variables ############################################ ONLINE_REDO_SIZE="512M" # New size for online redo logs STANDBY_REDO_SIZE="512M" # New size for standby redo logs ONLINE_GROUPS_PER_THREAD=4 # Online log groups per RAC thread STANDBY_GROUPS_PER_THREAD=5 # Standby log groups per RAC thread MAX_RETRIES=5 # Max attempts to switch logs RETRY_INTERVAL=5 # Seconds between retries ############################################ # Usage: ./safe_redolog_resize.sh # Input file format: # primary_scan primary_db # standby_scan standby_db # Check input parameters if [ $# -ne 1 ]; then echo "Usage: $0 " exit 1 fi INPUT_FILE="$1" PRIMARY_HOST=$(awk 'NR==1 {print $1}' "$INPUT_FILE") PRIMARY_DB=$(awk 'NR==1 {print $2}' "$INPUT_FILE") STANDBY_HOST=$(awk 'NR==2 {print $1}' "$INPUT_FILE") STANDBY_DB=$(awk 'NR==2 {print $2}' "$INPUT_FILE") DG_USER="sys" LOG_FILE="redolog_resize_$(date +%Y%m%d_%H%M).log" # Validate input if [[ -z "$PRIMARY_HOST" || -z "$PRIMARY_DB" || -z "$STANDBY_HOST" || -z "$STANDBY_DB" ]]; then echo "Invalid input file format. Expected:" echo "primary_scan primary_db" echo "standby_scan standby_db" exit 1 fi # Get SYS password read -s -p "Enter SYS password for ${PRIMARY_HOST}/${PRIMARY_DB}: " SYS_PASSWORD echo # Function to log messages log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" } # Verify database role verify_role() { local host=$1 local db=$2 local expected_role=$3 role=$(sqlplus -S "${DG_USER}/${SYS_PASSWORD}@//${host}:1521/${db} as sysdba" << EOF SET HEADING OFF FEEDBACK OFF PAGESIZE 0 SELECT database_role FROM v\$database; EXIT; EOF ) if [[ "$role" != "$expected_role" ]]; then log "Error: ${host}/${db} is not a ${expected_role} (Role: ${role})" exit 1 fi log "${expected_role} database confirmed" } # Add new redo logs (common function) add_new_logs() { local host=$1 local db=$2 local log_type=$3 local groups_per_thread=$4 local size=$5 log "Adding ${groups_per_thread} new ${log_type} log groups/thread (${size}) to ${db}" sqlplus -S "${DG_USER}/${SYS_PASSWORD}@//${host}:1521/${db} as sysdba" << EOF >> "$LOG_FILE" 2>&1 SET SERVEROUTPUT ON DECLARE TYPE thread_t IS TABLE OF NUMBER; threads thread_t; BEGIN -- Get active threads SELECT thread# BULK COLLECT INTO threads FROM v\$thread; -- Add new groups FOR i IN 1..threads.COUNT LOOP FOR j IN 1..${groups_per_thread} LOOP BEGIN EXECUTE IMMEDIATE 'ALTER DATABASE ADD ${log_type} LOGFILE THREAD ' || threads(i) || ' SIZE ${size}'; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error adding group: ' || SQLERRM); END; END LOOP; END LOOP; END; / EXIT; EOF } # Drop old redo logs (role-specific) drop_old_logs() { local host=$1 local db=$2 local log_type=$3 log "Dropping old ${log_type} logs from ${db}" sqlplus -S "${DG_USER}/${SYS_PASSWORD}@//${host}:1521/${db} as sysdba" << EOF >> "$LOG_FILE" 2>&1 SET SERVEROUTPUT ON DECLARE TYPE log_info IS RECORD (group# NUMBER, thread# NUMBER, status VARCHAR2(15)); TYPE log_list IS TABLE OF log_info; logs log_list; db_role VARCHAR2(16); BEGIN SELECT database_role INTO db_role FROM v\$database; -- Get existing logs that don't match new size SELECT group#, thread#, status BULK COLLECT INTO logs FROM v\$${log_type}_log WHERE bytes != (SELECT bytes FROM v\$${log_type}_log WHERE ROWNUM = 1); FOR log_rec IN 1..logs.COUNT LOOP BEGIN IF db_role = 'PRIMARY' AND log_type = 'LOG' THEN -- Handle active logs on primary IF logs(log_rec).status IN ('CURRENT', 'ACTIVE') THEN FOR i IN 1..$MAX_RETRIES LOOP BEGIN EXECUTE IMMEDIATE 'ALTER SYSTEM SWITCH LOGFILE THREAD ' || logs(log_rec).thread#; DBMS_LOCK.SLEEP($RETRY_INTERVAL); EXIT WHEN logs(log_rec).status = 'INACTIVE'; EXCEPTION WHEN OTHERS THEN NULL; END; END LOOP; IF logs(log_rec).status != 'INACTIVE' THEN EXECUTE IMMEDIATE 'ALTER DATABASE CLEAR LOGFILE GROUP ' || logs(log_rec).group#; END IF; END IF; ELSE -- Force clear on standby EXECUTE IMMEDIATE 'ALTER DATABASE CLEAR LOGFILE GROUP ' || logs(log_rec).group# || ' UNARCHIVED'; END IF; EXECUTE IMMEDIATE 'ALTER DATABASE DROP ${log_type} LOGFILE GROUP ' || logs(log_rec).group#; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error dropping group ' || logs(log_rec).group# || ': ' || SQLERRM); END; END LOOP; END; / EXIT; EOF } # DG Broker MRP control manage_mrp() { local action=$1 log "${action} MRP on ${STANDBY_DB}..." echo -e "connect ${DG_USER}/${SYS_PASSWORD}@${PRIMARY_DB}\n"\ "edit database ${STANDBY_DB} set state='APPLY-${action}';" | dgmgrl / >> "$LOG_FILE" 2>&1 if [ $? -ne 0 ]; then log "Failed to ${action} MRP on ${STANDBY_DB}" exit 1 fi log "MRP ${action} successfully on ${STANDBY_DB}" } # Main execution { log "Starting Safe Redo Log Resize Operation" verify_role "$PRIMARY_HOST" "$PRIMARY_DB" "PRIMARY" verify_role "$STANDBY_HOST" "$STANDBY_DB" "PHYSICAL STANDBY" # Process standby first manage_mrp "OFF" # Add new logs on standby add_new_logs "$STANDBY_HOST" "$STANDBY_DB" "LOG" "$ONLINE_GROUPS_PER_THREAD" "$ONLINE_REDO_SIZE" add_new_logs "$STANDBY_HOST" "$STANDBY_DB" "STANDBY" "$STANDBY_GROUPS_PER_THREAD" "$STANDBY_REDO_SIZE" # Drop old logs on standby drop_old_logs "$STANDBY_HOST" "$STANDBY_DB" "LOG" drop_old_logs "$STANDBY_HOST" "$STANDBY_DB" "STANDBY" manage_mrp "ON" # Process primary add_new_logs "$PRIMARY_HOST" "$PRIMARY_DB" "LOG" "$ONLINE_GROUPS_PER_THREAD" "$ONLINE_REDO_SIZE" add_new_logs "$PRIMARY_HOST" "$PRIMARY_DB" "STANDBY" "$STANDBY_GROUPS_PER_THREAD" "$STANDBY_REDO_SIZE" drop_old_logs "$PRIMARY_HOST" "$PRIMARY_DB" "LOG" drop_old_logs "$PRIMARY_HOST" "$PRIMARY_DB" "STANDBY" # Verification log "Final Configuration - Primary DB:" sqlplus -S "${DG_USER}/${SYS_PASSWORD}@//${PRIMARY_HOST}:1521/${PRIMARY_DB} as sysdba" << EOF | tee -a "$LOG_FILE" SET LINES 200 PAGES 1000 COL group# FOR 999 COL thread# FOR 999 COL bytes FOR a15 SELECT 'Online' type, group#, thread#, bytes/1024/1024||'M' bytes, status FROM v\$log UNION ALL SELECT 'Standby', group#, thread#, bytes/1024/1024||'M', status FROM v\$standby_log ORDER BY 1,2; EXIT; EOF log "Final Configuration - Standby DB:" sqlplus -S "${DG_USER}/${SYS_PASSWORD}@//${STANDBY_HOST}:1521/${STANDBY_DB} as sysdba" << EOF | tee -a "$LOG_FILE" SET LINES 200 PAGES 1000 COL group# FOR 999 COL thread# FOR 999 COL bytes FOR a15 SELECT 'Online' type, group#, thread#, bytes/1024/1024||'M' bytes, status FROM v\$log UNION ALL SELECT 'Standby', group#, thread#, bytes/1024/1024||'M', status FROM v\$standby_log ORDER BY 1,2; EXIT; EOF log "Operation completed successfully" } | tee -a "$LOG_FILE" echo "Detailed log saved to: ${LOG_FILE}" ##### Tim Zhang Mon, Apr 14, 10:00 PM (3 days ago) to me #!/bin/bash # === CONFIGURATION === export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1 export PATH=$ORACLE_HOME/bin:$PATH export ORACLE_SID=PROD1 # Change for each node LOG_BASE_DIR="/u01/app/oracle/oradata/PROD" # Or use ASM e.g. +DATA THREAD_COUNT=2 NEW_GROUPS_PER_THREAD=4 REDO_LOG_SIZE_MB=500 SQL_SCRIPT="/tmp/refresh_redo.sql" LOGFILE="/tmp/refresh_redo_$(date +%Y%m%d_%H%M%S).log" # === Get DB Role and Max Group Number === get_role_and_max_group() { read -r DB_ROLE MAX_GROUP <<<$(sqlplus -s / as sysdba < "$SQL_SCRIPT" <> "$SQL_SCRIPT" else echo "ALTER DATABASE ADD STANDBY LOGFILE THREAD $thread ('$log_file') SIZE ${REDO_LOG_SIZE_MB}M;" >> "$SQL_SCRIPT" fi group_id=$((group_id + 1)) done done cat >> "$SQL_SCRIPT" <<'EOF' PROMPT === Begin switch/drop logic === DECLARE v_role VARCHAR2(30); retry NUMBER := 0; max_retries CONSTANT NUMBER := 20; all_inactive BOOLEAN := FALSE; FUNCTION are_all_inactive RETURN BOOLEAN IS cnt NUMBER; BEGIN SELECT COUNT(*) INTO cnt FROM v$log WHERE group# < &start_group AND status != 'INACTIVE'; RETURN cnt = 0; END; PROCEDURE force_log_switch IS BEGIN FOR t IN (SELECT DISTINCT thread# FROM v$log) LOOP BEGIN EXECUTE IMMEDIATE 'ALTER SYSTEM SWITCH LOGFILE'; EXCEPTION WHEN OTHERS THEN NULL; END; END LOOP; END; BEGIN SELECT database_role INTO v_role FROM v$database; IF v_role = 'PRIMARY' THEN LOOP EXIT WHEN are_all_inactive OR retry >= max_retries; force_log_switch; DBMS_LOCK.SLEEP(2); retry := retry + 1; END LOOP; IF NOT are_all_inactive THEN RAISE_APPLICATION_ERROR(-20001, 'Some old redo logs are still ACTIVE or CURRENT.'); END IF; FOR r IN (SELECT group# FROM v$log WHERE group# < &start_group AND status = 'INACTIVE') LOOP BEGIN EXECUTE IMMEDIATE 'ALTER DATABASE DROP LOGFILE GROUP ' || r.group#; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Could not drop group ' || r.group# || ': ' || SQLERRM); END; END LOOP; ELSIF v_role = 'PHYSICAL STANDBY' THEN BEGIN EXECUTE IMMEDIATE 'ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL'; EXCEPTION WHEN OTHERS THEN NULL; END; DBMS_LOCK.SLEEP(3); FOR r IN (SELECT group# FROM v$standby_log WHERE group# < &start_group) LOOP BEGIN EXECUTE IMMEDIATE 'ALTER DATABASE DROP STANDBY LOGFILE GROUP ' || r.group#; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Could not drop standby group ' || r.group# || ': ' || SQLERRM); END; END LOOP; BEGIN EXECUTE IMMEDIATE 'ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT'; EXCEPTION WHEN OTHERS THEN NULL; END; END IF; END; / EXIT; EOF # Replace &start_group manually since sqlplus won't accept bind here sed -i "s/&start_group/$start_group/g" "$SQL_SCRIPT" } # === MAIN === get_role_and_max_group generate_sql echo "Executing SQL script..." sqlplus -s / as sysdba @"$SQL_SCRIPT" | tee "$LOGFILE" echo "Done. Log written to $LOGFILE" ################## # Data Guard Synchronization Check rl_check_dg_sync() { local primary_db="$1" local standby_db="$2" local sys_password="$3" echo "Checking Data Guard Synchronization Status..." dgmgrl -silent / <&1 | tee -a "${RL_CONF[LOG_FILE]}" connect sys/${sys_password}@${primary_db} show database ${standby_db} exit DGCMDS # Additional SQL check for comprehensive status sqlplus -S "sys/${sys_password}@//${primary_db} as sysdba" << EOF | tee -a "${RL_CONF[LOG_FILE]}" SET LINES 200 PAGES 1000 COLUMN name FORMAT A30 COLUMN value FORMAT A50 COLUMN time_computed FORMAT A30 SELECT name, value, TO_CHAR(time_computed, 'YYYY-MM-DD HH24:MI:SS') AS time_computed FROM v\$dataguard_stats WHERE name IN ('apply lag', 'transport lag', 'apply finish time', 'transport status'); SELECT protection_mode, protection_level, remote_archive, dataguard_broker, guard_status FROM v\$database; EXIT; EOF } # Add this function to redolog_lib.sh rl_count_logs() { local host="$1" local db="$2" local sys_password="$3" echo "Current Log Group Configuration for ${db}:" # Count online redo logs sqlplus -S "sys/${sys_password}@//${host}:1521/${db} as sysdba" << EOF SET LINES 200 PAGES 1000 COLUMN "Thread#" FORMAT 999 COLUMN "Online Groups" FORMAT 999 COLUMN "Standby Groups" FORMAT 999 PROMPT Online Redo Log Groups per Thread: SELECT thread# "Thread#", COUNT(*) "Online Groups" FROM v\$log GROUP BY thread# ORDER BY thread#; PROMPT Standby Redo Log Groups per Thread: SELECT thread# "Thread#", COUNT(*) "Standby Groups" FROM v\$standby_log GROUP BY thread# ORDER BY thread#; EXIT; EOF } #!/bin/bash # Oracle RAC Redo Log Management Library # Requires: sqlplus, dgmgrl declare -A RL_CONF rl_load_config() { source redolog.conf || return 1 } rl_init_logging() { local LOG_FILE="$1" exec > >(tee -a "$LOG_FILE") 2>&1 } rl_verify_role() { local host="$1" local db="$2" local expected_role="$3" local sys_password="$4" local role=$(sqlplus -S "sys/${sys_password}@//${host}:1521/${db} as sysdba" << EOF SET HEADING OFF FEEDBACK OFF PAGESIZE 0 SELECT database_role FROM v\$database; EXIT; EOF ) [[ "$role" == "$expected_role" ]] || return 1 } rl_add_logs() { local host="$1" local db="$2" local log_type="$3" local sys_password="$4" sqlplus -S "sys/${sys_password}@//${host}:1521/${db} as sysdba" << EOF >> ${RL_CONF[LOG_FILE]} 2>&1 SET SERVEROUTPUT ON DECLARE TYPE thread_t IS TABLE OF NUMBER; threads thread_t; BEGIN SELECT thread# BULK COLLECT INTO threads FROM v\$thread; FOR i IN 1..threads.COUNT LOOP FOR j IN 1..${RL_CONF[${log_type}_GROUPS_PER_THREAD]} LOOP EXECUTE IMMEDIATE 'ALTER DATABASE ADD ${log_type} LOGFILE THREAD ' || threads(i) || ' SIZE ${RL_CONF[${log_type}_REDO_SIZE]}'; END LOOP; END LOOP; END; / EXIT; EOF } rl_drop_old_logs() { local host="$1" local db="$2" local log_type="$3" local sys_password="$4" sqlplus -S "sys/${sys_password}@//${host}:1521/${db} as sysdba" << EOF >> ${RL_CONF[LOG_FILE]} 2>&1 SET SERVEROUTPUT ON DECLARE CURSOR c_logs IS SELECT group#, thread#, status FROM v\$${log_type}_log; db_role VARCHAR2(16); BEGIN SELECT database_role INTO db_role FROM v\$database; FOR rec IN c_logs LOOP BEGIN IF db_role = 'PRIMARY' AND rec.status IN ('CURRENT','ACTIVE') THEN FOR i IN 1..${RL_CONF[MAX_RETRIES]} LOOP BEGIN EXECUTE IMMEDIATE 'ALTER SYSTEM SWITCH LOGFILE THREAD ' || rec.thread#; DBMS_LOCK.SLEEP(${RL_CONF[RETRY_INTERVAL]}); EXIT WHEN rec.status = 'INACTIVE'; EXCEPTION WHEN OTHERS THEN NULL; END; END LOOP; END IF; EXECUTE IMMEDIATE 'ALTER DATABASE DROP ${log_type} LOGFILE GROUP ' || rec.group#; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error dropping group ' || rec.group# || ': ' || SQLERRM); END; END LOOP; END; / EXIT; EOF } rl_manage_mrp() { local primary_db="$1" local standby_db="$2" local action="$3" local sys_password="$4" dgmgrl -silent / <> ${RL_CONF[LOG_FILE]} 2>&1 connect sys/${sys_password}@${primary_db} edit database ${standby_db} set state='APPLY-${action^^}'; show database ${standby_db} exit DGCMDS return $? } # Oracle Redo Log Configuration declare -A RL_CONF=( [LOG_FILE]="redolog_operation.log" # Main log file [ONLINE_REDO_SIZE]="512M" # New online redo log size [STANDBY_REDO_SIZE]="512M" # New standby redo log size [ONLINE_GROUPS_PER_THREAD]=4 # Online log groups per thread [STANDBY_GROUPS_PER_THREAD]=5 # Standby log groups per thread [MAX_RETRIES]=5 # Max log switch attempts [RETRY_INTERVAL]=5 # Seconds between retries ) #!/bin/bash # Oracle RAC Redo Log Management Script # Usage: ./main_redolog.sh # Load configuration and library source redolog.conf || exit 1 source redolog_lib.sh || exit 1 # Initialize environment INPUT_FILE="$1" [[ -f "$INPUT_FILE" ]] || { echo "Input file missing"; exit 1; } PRIMARY_HOST=$(awk 'NR==1 {print $1}' "$INPUT_FILE") PRIMARY_DB=$(awk 'NR==1 {print $2}' "$INPUT_FILE") STANDBY_HOST=$(awk 'NR==2 {print $1}' "$INPUT_FILE") STANDBY_DB=$(awk 'NR==2 {print $2}' "$INPUT_FILE") rl_load_config || exit 1 rl_init_logging "${RL_CONF[LOG_FILE]}" # Get SYS password read -sp "Enter SYS password: " SYS_PASSWORD echo # Verify database roles if ! rl_verify_role "$PRIMARY_HOST" "$PRIMARY_DB" "PRIMARY" "$SYS_PASSWORD"; then echo "Primary database verification failed" | tee -a "${RL_CONF[LOG_FILE]}" exit 1 fi if ! rl_verify_role "$STANDBY_HOST" "$STANDBY_DB" "PHYSICAL STANDBY" "$SYS_PASSWORD"; then echo "Standby database verification failed" | tee -a "${RL_CONF[LOG_FILE]}" exit 1 fi # Main operation sequence { echo "=== Starting Redo Log Management Operation ===" date # Process standby database echo "Stopping MRP on standby..." rl_manage_mrp "$PRIMARY_DB" "$STANDBY_DB" "OFF" "$SYS_PASSWORD" || exit 1 echo "Adding new logs to standby..." rl_add_logs "$STANDBY_HOST" "$STANDBY_DB" "ONLINE" "$SYS_PASSWORD" rl_add_logs "$STANDBY_HOST" "$STANDBY_DB" "STANDBY" "$SYS_PASSWORD" echo "Removing old logs from standby..." rl_drop_old_logs "$STANDBY_HOST" "$STANDBY_DB" "LOG" "$SYS_PASSWORD" rl_drop_old_logs "$STANDBY_HOST" "$STANDBY_DB" "STANDBY" "$SYS_PASSWORD" echo "Restarting MRP on standby..." rl_manage_mrp "$PRIMARY_DB" "$STANDBY_DB" "ON" "$SYS_PASSWORD" || exit 1 # Process primary database echo "Updating primary database logs..." rl_add_logs "$PRIMARY_HOST" "$PRIMARY_DB" "ONLINE" "$SYS_PASSWORD" rl_add_logs "$PRIMARY_HOST" "$PRIMARY_DB" "STANDBY" "$SYS_PASSWORD" echo "Removing old logs from primary..." rl_drop_old_logs "$PRIMARY_HOST" "$PRIMARY_DB" "LOG" "$SYS_PASSWORD" rl_drop_old_logs "$PRIMARY_HOST" "$PRIMARY_DB" "STANDBY" "$SYS_PASSWORD" echo "=== Operation Completed Successfully ===" date } | tee -a "${RL_CONF[LOG_FILE]}" echo "Detailed log saved to: ${RL_CONF[LOG_FILE]}"