Sunday, April 27, 2025
#!/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 <