Instruction

Migrate Oracle 19c Standalone to Exadata 2-Node RAC (Data Guard Switchover Method)

Migrate a standalone single-instance Oracle 19c CDB (datafiles on a flat filesystem) to a 2-node Exadata RAC with ASM by building a physical Data Guard standby on the destination with RMAN active duplication, switching over, converting the new open primary to RAC, then consolidating to BIGFILE tablespaces. The entire operation is driven from the destination. The source database stays open and serving during the multi-day seed; the only outage is the switchover, measured in seconds to minutes.

Length note: This is a 10-phase end-to-end migration documented as a single transactional how-to (the [3P]/[SYS]/[DEST] audience handoffs and placeholder continuity span all phases). It exceeds the usual instruction length intentionally and with prior approval, so the reader does not have to reassemble context across files.

Audience legend

Every command block carries one of three audience tags on the bold line directly above it. The tag tells you who runs the command and from where.

Tag Who runs it Where
[DEST] The experienced Exadata DBA driving the migration On the Exadata destination (OS shell, SQL*Plus, RMAN, dgmgrl, asmcmd)
[SYS] The same operator, but connected remotely as SYS over SQL*Net From the destination, into the source database; no source OS access
[3P] An unskilled third party with shell access to the source On the source OS; commands are copy-paste ready, no judgment required

You (the [DEST] operator) have no source OS access and no authority to act on the source OS directly. Source database changes are [SYS] (remote SQL); source OS changes are [3P] (handed off as copy-paste). Hand [3P] blocks to the third party exactly as written after substituting placeholders.

Licensing scope note

Licensed on the destination: Enterprise Edition + Real Application Clusters + Diagnostics and Tuning Pack — and nothing else. This procedure introduces no chargeable option. Specifically:

  • RMAN compression is BASIC only. LOW, MEDIUM, and HIGH require the Advanced Compression Option (ACO) and must not be used.
  • No SQLNET.COMPRESSION (an ACO feature).
  • No OLTP / Advanced Row / Hybrid Columnar (HCC) table compression. Existing segment compression attributes are preserved exactly as-is on move; no compression is introduced.
  • No Partitioning operations. (Partitioned objects existing at all is a Phase 0 compliance blocker for this license set.)
  • No Advanced Security / TDE.
  • No GoldenGate.

Diagnostics and Tuning is licensed: AWR, ASH, and SQL Tuning use is permitted (CONTROL_MANAGEMENT_PACK_ACCESS may remain at DIAGNOSTIC+TUNING).

Placeholder legend

Placeholders use <UPPER_SNAKE_CASE> angle brackets. Substitute every placeholder before running a command. The set below is fixed and used consistently throughout the document.

Placeholder Meaning / example
<SOURCE_DB_NAME> Source DB_NAME (e.g., PRODCDB)
<SOURCE_DB_UNIQUE_NAME> Source DB_UNIQUE_NAME (e.g., prodcdb)
<SOURCE_SID> Source ORACLE_SID on the source host
<SOURCE_SCAN_HOST> Source listener/SCAN hostname or VIP
<SOURCE_DB_SERVICE> Source database service registered with the source listener
<SOURCE_DB_BLOCK_SIZE> Source DB_BLOCK_SIZE (e.g., 8192) — destination auxiliary must match
<SOURCE_DATAFILE_PATH> Source datafile directory, for the Phase 4 optional explicit datafile mapping (e.g., /u02/oradata/PRODCDB/); add one convert pair per source mount point
<SOURCE_REDO_LOG_PATH> Source online redo log directory, for the Phase 4 optional explicit log mapping (e.g., /u02/oradata/PRODCDB/)
<DEST_DB_UNIQUE_NAME> Destination DB_UNIQUE_NAME (e.g., prodexa)
<DEST_SCAN_HOST> Exadata SCAN name
<AUX_INSTANCE> Auxiliary instance ORACLE_SID on the destination during the build
<AUX_AUDIT_DIR> Auxiliary audit_file_dest, created on disk before STARTUP NOMOUNT (e.g., /u01/app/oracle/admin/<AUX_INSTANCE>/adump)
<AUX_STATIC_SERVICE> Static service name registered for the auxiliary (e.g., prodexa_DGMGRL or a dedicated <DEST_DB_UNIQUE_NAME>_aux)
<LISTENER_PORT> Listener port, typically 1521
<DEST_ORACLE_HOME> Destination 19c RDBMS home (e.g., /u01/app/oracle/product/19.0.0/dbhome_1)
<NODE1> / <NODE2> Exadata RAC node hostnames
<INST1> / <INST2> RAC instance names (e.g., prodexa1, prodexa2)
<DATA_DG> Data disk group with leading + (e.g., +DATA)
<RECO_DG> Recovery disk group with leading + (e.g., +RECO)
<DATA_DG_NAME> / <RECO_DG_NAME> Disk group names without the leading + (e.g., DATA, RECO) for srvctl -diskgroup
<DG_NAME> ASM disk group hosting the ACFS volume (e.g., DATA)
<VOLUME_NAME> ADVM volume name (e.g., XFER)
<VOLUME_DEVICE> ADVM volume device path from volinfo (e.g., /dev/asm/xfer-123)
<VOLUME_SIZE> ACFS volume size (small — e.g., 10G; staging holds only small files)
<ACFS_MOUNT> ACFS mount point (= /acfs/transfer)
<TRANSFER_USER> / <TRANSFER_GROUP> Locked-down SFTP transfer account / group
<CHROOT_HOME> Writable drop directory inside the chroot (<ACFS_MOUNT>/<TRANSFER_USER>)
<SYS_PASSWORD> The SYS password (identical on source and destination auxiliary)
<DB_ACCOUNT> The privileged DB account used for the duplicate connection (e.g., SYS)
<PROFILE_NAME> The profile assigned to <DB_ACCOUNT>
<NEW_PASSWORD> The re-set password value (same value, to bump the expiry clock)
<ORIGINAL_VALUE> The profile's original PASSWORD_LIFE_TIME to restore on revert (e.g., the default 180)
<REDO_SIZE> Standby redo log size — ≥ the largest online redo log
<SGA_TARGET> SGA_TARGET value sized for the destination instance (e.g., 48G)
<PGA_TARGET> PGA_AGGREGATE_TARGET value for the destination instance (e.g., 16G)
<RECO_SIZE> DB_RECOVERY_FILE_DEST_SIZE value for the destination FRA (e.g., 2T)
<OLD_TS> Existing smallfile user tablespace being consolidated
<NEW_TS> New BIGFILE tablespace target
<PDB_NAME> A pluggable database name
<SCHEMA> / <TABLE> / <INDEX> Object owner / table / index being relocated

Assumptions

This instruction assumes:

  • The source is a standalone single-instance Oracle 19c CDB (multitenant) with one or more PDBs, on Linux x86-64, datafiles on a flat filesystem, hundreds of datafiles, 200 GB–15 TB, no encryption/TDE.
  • The destination is an Exadata 2-node RAC with Grid Infrastructure running, ASM disk groups <DATA_DG> and <RECO_DG> present, and a 19c RDBMS home installed at the same Release Update as the source.
  • Source and destination are the same endianness (Linux x86-64 both); no cross-platform conversion.
  • DB_BLOCK_SIZE on the destination auxiliary will be set to match the source.
  • The operator is an experienced Exadata/RAC/Data Guard DBA working on the destination, with SYS-over-SQL*Net access to the source on <LISTENER_PORT>, but no source OS access.
  • Oracle Net connectivity exists from the destination to the source SCAN/listener, and back from the source to the destination (for redo transport and FAL after the duplicate).
  • The source has spare archived-redo capacity (or the third party can add it) to retain redo for the entire multi-day seed window plus margin.
  • The source database uses an SPFILE (server parameter file) — mandatory for active database duplication, since DUPLICATE … FROM ACTIVE DATABASE … SPFILE copies it. Confirm on the source with SHOW PARAMETER spfile (the value must be non-empty); if the source runs on a text pfile, create an SPFILE first (CREATE SPFILE FROM PFILE; then restart) — a [3P]/[SYS] action.
  • A third party with shell access to the source is available to run copy-paste [3P] commands.
  • Licensing is Enterprise Edition + RAC + Diagnostics and Tuning only.

Prerequisites

Automatic setup

No automatic or RPM package configures this migration. It is a manual DBA procedure end to end. The Exadata image already ships ASM, ACFS/ADVM, and OpenSSH; no package installation is required, only configuration.

Manual setup

The manual environment preparation is performed inside the phases below, because the setup is staged across three machines and three audiences and depends on values that surface during discovery:

  • Source ([SYS]): FORCE LOGGING, ARCHIVELOG confirmation, standby redo logs, REMOTE_LOGIN_PASSWORDFILE, migration-account non-expiry — Phase 2.
  • Source ([3P]): real password file (orapwd format=12), archived-redo retention sizing, OS account aging, destination name resolution in tnsnames.ora — Phase 2.
  • Destination auxiliary ([DEST]): minimal pfile (matching block size, ENABLE_PLUGGABLE_DATABASE=TRUE), password file (same SYS password), static listener registration, STARTUP NOMOUNT — Phase 3.
  • Destination staging ([DEST]): temporary ACFS volume for small files plus a locked-down SFTP chroot account on both RAC nodes — Phase 1.

Additional setup

  • Enterprise Edition + RAC + Diagnostics and Tuning licenses on the destination.
  • Sufficient <DATA_DG> / <RECO_DG> capacity for the full database plus the Fast Recovery Area.
  • Set destination environment variables before each [DEST] block:

[DEST]

export ORACLE_HOME=<DEST_ORACLE_HOME>
export ORACLE_SID=<AUX_INSTANCE>
export PATH=$ORACLE_HOME/bin:$PATH

Phase 0 — Discovery and compliance go/no-go

Independently confirm, before touching anything, that the source uses no chargeable option and no encryption. This phase is container-aware: run option/pack and encryption checks across CDB$ROOT and every PDB, using CDB_* views for container-wide coverage. Any usage of Partitioning, non-BASIC compression, Advanced Security, In-Memory, Spatial, OLAP, Label Security, or Database Vault is a hard compliance blocker — stop and escalate. AWR/ASH/SQL Tuning usage is fine (Diagnostics and Tuning is licensed).

Step 1: Run the option/pack usage script (MOS 1317265.1)

Obtain options_packs_usage_statistics.sql from My Oracle Support Doc ID 1317265.1 and run its contents in a local SQLPlus session connected as SYS to the source. The script's SPOOL writes its report locally on the destination where you run SQLPlus.

[SYS]

sqlplus sys/<SYS_PASSWORD>@//<SOURCE_SCAN_HOST>:<LISTENER_PORT>/<SOURCE_DB_SERVICE> as sysdba

[SYS]

@options_packs_usage_statistics.sql

Review the generated report. Any line flagging Partitioning, Advanced Compression (non-BASIC), Advanced Security, In-Memory, Spatial, OLAP, Label Security, or Database Vault is a blocker.

Step 2: Check feature usage container-wide

[SYS]

SELECT name, version, detected_usages, currently_used, first_usage_date, last_usage_date
FROM   cdb_feature_usage_statistics
WHERE  detected_usages > 0
ORDER  BY currently_used DESC;

Expected output:

NAME                            VERSION    DETECTED_USAGES CURRENTLY_USED ...
Automatic SQL Tuning Advisor    19.0.0.0.0           1234 TRUE           ...
Real Application Clusters (RAC) 19.0.0.0.0              0 FALSE          ...

No row for Partitioning, Advanced Compression, Advanced Security, In-Memory, Spatial/Spatial and Graph, OLAP, Label Security, or Database Vault with CURRENTLY_USED = TRUE.

Step 3: Check linked-in options

[SYS]

SELECT parameter, value
FROM   v$option
WHERE  parameter IN ('Partitioning','Advanced Compression','Real Application Clusters',
                     'Advanced Security','Oracle Database Vault','Oracle Label Security',
                     'In-Memory Column Store','Spatial','OLAP');

Real Application Clusters may read FALSE on a standalone source (expected). Partitioning, Advanced Compression, Advanced Security, Oracle Database Vault, Oracle Label Security, In-Memory Column Store, Spatial, and OLAP being TRUE only means the option is linked into the binary, not that it is used; combine this with the usage checks. Actual usage of any of those is a blocker.

Step 4: Detect non-BASIC table compression (CDB-wide)

[SYS]

SELECT compression, compress_for, COUNT(*)
FROM   cdb_tables
WHERE  compression = 'ENABLED'
GROUP  BY compression, compress_for;

Expected output:

COMPRESSION COMPRESS_FOR   COUNT(*)
----------- -------------- --------
ENABLED     BASIC                42

Any COMPRESS_FOR other than BASIC (for example ADVANCED, QUERY LOW, QUERY HIGH, ARCHIVE LOW, ARCHIVE HIGH) is an Advanced Compression / HCC usage and is a blocker.

Step 5: Detect partitioned objects (CDB-wide)

[SYS]

SELECT con_id, COUNT(*) AS partitioned_tables
FROM   cdb_part_tables
GROUP  BY con_id;

Expected output:

no rows selected

Any partitioned tables are a Partitioning-option blocker for this license set. If found, stop and escalate.

Step 6: Verify encryption absence in every container

Run the encryption checks in CDB$ROOT and in each PDB (or use CON_ID from the V$ views as below). All must show no encryption.

[SYS]

SELECT con_id, wrl_type, status, wallet_type FROM v$encryption_wallet;
SELECT * FROM v$encryption_keys;
SELECT con_id, masterkey_activated FROM v$database_key_info;
SELECT * FROM v$encrypted_tablespaces;
SELECT tablespace_name, encrypted FROM cdb_tablespaces WHERE encrypted = 'YES';
SELECT * FROM cdb_encrypted_columns;

Expected output:

-- v$encryption_wallet: STATUS = NOT_AVAILABLE for each container
-- v$encryption_keys: no rows selected
-- v$database_key_info: MASTERKEY_ACTIVATED = NO
-- v$encrypted_tablespaces: no rows selected
-- cdb_tablespaces WHERE encrypted='YES': no rows selected
-- cdb_encrypted_columns: no rows selected

Note: TABLESPACE_ENCRYPTION and similar parameters/views vary by Release Update. Rely on whichever encryption views exist in the installed RU; treat the absence of a newer view gracefully rather than as a failure.

Step 7: Confirm no network encryption or SQLNET compression on the source

Hand this to the third party to inspect the source sqlnet.ora.

[3P]

grep -Ei 'SQLNET\.ENCRYPTION_|SQLNET\.CRYPTO_CHECKSUM_|SQLNET\.COMPRESSION' \
  $ORACLE_HOME/network/admin/sqlnet.ora

Expected output:

(no matching lines — the file has none of these parameters set)

Any SQLNET.ENCRYPTION_*, SQLNET.CRYPTO_CHECKSUM_*, or SQLNET.COMPRESSION present must be evaluated: SQLNET.COMPRESSION is an Advanced Compression feature and must remain off for this migration.

Phase 1 — Destination temporary ACFS staging and SFTP chroot account (both nodes)

Create a small ACFS volume on the destination to receive small files (password file, tnsnames.ora, pfile/spfile) from the source, plus a locked-down SFTP-based file-transfer chroot account. Staging is for small files only — a 15 TB backup over a ~1 Gbps link is infeasible to ship this way; the bulk data moves over SQL*Net via RMAN active duplication in Phase 4. General-purpose ACFS staging is supported on Exadata.

Step 1: Create and mount the ACFS volume

[DEST]

asmcmd volcreate -G <DG_NAME> -s <VOLUME_SIZE> <VOLUME_NAME>
asmcmd volinfo -G <DG_NAME> <VOLUME_NAME>

Expected output:

Diskgroup Name: DATA
         Volume Name: XFER
         Volume Device: /dev/asm/xfer-123
         State: ENABLED
         ...

Record the Volume Device as <VOLUME_DEVICE>. Then, as root:

[DEST]

/sbin/mkfs -t acfs <VOLUME_DEVICE>
mkdir -p <ACFS_MOUNT>
/bin/mount -t acfs <VOLUME_DEVICE> <ACFS_MOUNT>
/sbin/acfsutil registry -a <VOLUME_DEVICE> <ACFS_MOUNT>

Registering with acfsutil registry (or srvctl add filesystem) makes the mount cluster-managed across both nodes.

Expected output:

acfsutil registry: mount point <ACFS_MOUNT> successfully added to Oracle Registry

Step 2: Create the chroot directory tree with correct ownership

Warning: OpenSSH ChrootDirectory refuses to operate if the chroot directory or any of its parents is owned by a non-root user or is group/world-writable. A login will fail with bad ownership or modes for chroot directory. The chroot path itself must be root:root and chmod 755; only a subdirectory inside it may be writable by the transfer user.

As root on both nodes:

[DEST]

chown root:root <ACFS_MOUNT>
chmod 755 <ACFS_MOUNT>
groupadd <TRANSFER_GROUP>
useradd -g <TRANSFER_GROUP> -d <CHROOT_HOME> -s /sbin/nologin <TRANSFER_USER>
mkdir -p <CHROOT_HOME>
chown <TRANSFER_USER>:<TRANSFER_GROUP> <CHROOT_HOME>
chmod 750 <CHROOT_HOME>

<CHROOT_HOME> is <ACFS_MOUNT>/<TRANSFER_USER> — the only writable drop directory, inside the root-owned chroot.

Step 3: Configure the SFTP-based chroot in sshd

As root on both nodes, append a Match block to /etc/ssh/sshd_config:

[DEST]

Match User <TRANSFER_USER>
    ChrootDirectory <ACFS_MOUNT>
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no

Validate and reload on both nodes:

[DEST]

sshd -t
systemctl reload sshd

sshd -t produces no output on success. Modern scp uses the SFTP subsystem and therefore works against this account; legacy scp -O (the old SCP protocol) is blocked.

Step 4: Stage small files from the source

After the source password file and pfile exist (Phase 2/3), the third party copies the small files into the chroot drop directory.

[3P]

scp ./tnsnames.ora ./spfile<SOURCE_SID>.ora ./orapw<SOURCE_SID> \
  <TRANSFER_USER>@<DEST_SCAN_HOST>:/

Expected output:

tnsnames.ora          100%  ...
spfilePRODCDB.ora      100%  ...
orapwPRODCDB          100%  ...

The / destination resolves to the writable drop directory inside the chroot.

Phase 2 — Source-side prerequisites

Prepare the source for redo transport and remote duplication. Database changes are [SYS]; OS-level actions and any restart are [3P]. Set the migration-account non-expiry now, before the multi-day seed, so the password cannot expire mid-build.

Step 1: Enable FORCE LOGGING and confirm ARCHIVELOG mode

[SYS]

ALTER DATABASE FORCE LOGGING;
SELECT log_mode, force_logging FROM v$database;

Expected output:

LOG_MODE     FORCE_LOGGING
------------ -------------
ARCHIVELOG   YES

If LOG_MODE is NOARCHIVELOG, hand the third party Step 6 to place the source in ARCHIVELOG mode first.

Step 2: Add standby redo logs on the source

Standby redo logs are required on the source so it can act as a standby after switchover. Count per thread = (online log groups per thread) + 1; size ≥ the largest online log. Repeat the statement for each required group.

[SYS]

ALTER DATABASE ADD STANDBY LOGFILE THREAD 1 SIZE <REDO_SIZE>;

Confirm:

[SYS]

SELECT group#, thread#, bytes/1024/1024 AS mb, status FROM v$standby_log;

Expected output:

    GROUP#    THREAD#         MB STATUS
---------- ---------- ---------- ----------
        11          1       1024 UNASSIGNED
        12          1       1024 UNASSIGNED
        ...

Step 3: Make the migration account non-expiring

Do not assume SYS is exempt from ORA-28001. Set the profile to unlimited password life and re-set the password (same value) to reset the expiry clock.

[SYS]

SELECT username, profile, account_status, expiry_date
FROM   dba_users WHERE username = '<DB_ACCOUNT>';

[SYS]

ALTER PROFILE <PROFILE_NAME> LIMIT PASSWORD_LIFE_TIME UNLIMITED;
ALTER USER <DB_ACCOUNT> IDENTIFIED BY <NEW_PASSWORD>;

Expected output:

Profile altered.
User altered.

Step 4: Create a real password file and set REMOTE_LOGIN_PASSWORDFILE

Hand this to the third party on the source host. A real (orapwd) password file is required for the remote SYS connection used by RMAN.

[3P]

orapwd file=$ORACLE_HOME/dbs/orapw$ORACLE_SID password=<SYS_PASSWORD> format=12 force=y

[SYS]

SHOW PARAMETER remote_login_passwordfile

If not already EXCLUSIVE:

[SYS]

ALTER SYSTEM SET REMOTE_LOGIN_PASSWORDFILE='EXCLUSIVE' SCOPE=SPFILE;

A change here requires a restart (Step 6).

Step 5: Remove OS password aging on the source oracle account

This step always runs, regardless of whether a restart is pending. If the source enforces OS account password aging, the oracle account password could expire mid-seed. Remove aging for the oracle account only while the migration runs.

[3P]

chage -M -1 oracle

Step 6: Place the source in ARCHIVELOG / set REMOTE_LOGIN_PASSWORDFILE and size redo retention (if needed)

Warning: This restarts the source instance. Schedule a maintenance window. Skip this step entirely if Step 1 already confirmed ARCHIVELOG mode and no REMOTE_LOGIN_PASSWORDFILE change is pending.

[3P]

sqlplus / as sysdba <<'EOF'
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;
EOF

Size the Fast Recovery Area / archive destination to retain redo for the entire multi-day seed window plus margin.

Step 7: Stage destination name resolution on the source for redo transport

After switchover the source becomes a standby and must ship redo to the destination via LOG_ARCHIVE_DEST_2='SERVICE=<DEST_DB_UNIQUE_NAME> ...' (Phase 5 Step 3). The SERVICE name must be resolvable on the source through an Oracle Net naming method to a connect descriptor that matches the destination listener. Hand the third party a tnsnames.ora stanza on the source that resolves <DEST_DB_UNIQUE_NAME> to the Exadata SCAN.

[3P]

# Append to $ORACLE_HOME/network/admin/tnsnames.ora on the source
<DEST_DB_UNIQUE_NAME> =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = <DEST_SCAN_HOST>)(PORT = <LISTENER_PORT>))
    (CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = <DEST_DB_UNIQUE_NAME>))
  )

The destination must likewise resolve the source service for FAL (FAL_SERVER=<SOURCE_DB_UNIQUE_NAME>, Phase 5 Step 2). Ensure a matching <SOURCE_DB_UNIQUE_NAME> stanza exists in the destination tnsnames.ora pointing at <SOURCE_SCAN_HOST>:<LISTENER_PORT>/<SOURCE_DB_SERVICE>.

Phase 3 — Destination auxiliary instance preparation

Build a minimal NOMOUNT auxiliary on the destination to receive the duplicate. A NOMOUNT instance does not self-register with the listener, so a static listener entry is mandatory before DUPLICATE.

Step 1: Create a minimal auxiliary pfile

The DB_BLOCK_SIZE MUST equal <SOURCE_DB_BLOCK_SIZE>. Because the source is a CDB, set ENABLE_PLUGGABLE_DATABASE=TRUE.

[DEST]

*.db_name='<SOURCE_DB_NAME>'
*.db_unique_name='<DEST_DB_UNIQUE_NAME>'
*.db_block_size=<SOURCE_DB_BLOCK_SIZE>
*.enable_pluggable_database=TRUE
*.audit_file_dest='<AUX_AUDIT_DIR>'
*.db_create_file_dest='<DATA_DG>'
*.db_recovery_file_dest='<RECO_DG>'
*.db_recovery_file_dest_size=<RECO_SIZE>
*.compatible='19.0.0'
*.sga_target=<SGA_TARGET>
*.pga_aggregate_target=<PGA_TARGET>

Step 2: Create the auxiliary password file with the same SYS password

[DEST]

orapwd file=$ORACLE_HOME/dbs/orapw<AUX_INSTANCE> password=<SYS_PASSWORD> format=12 force=y

Step 3: Add a static listener entry for the auxiliary service

Add a SID_LIST entry to the destination listener for <AUX_STATIC_SERVICE>, then reload the listener.

[DEST]

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = <AUX_STATIC_SERVICE>)
      (ORACLE_HOME = <DEST_ORACLE_HOME>)
      (SID_NAME = <AUX_INSTANCE>)
    )
  )

[DEST]

lsnrctl reload

Step 4: Create the audit directory, then start the auxiliary in NOMOUNT

The audit_file_dest directory must exist on disk before startup, or STARTUP NOMOUNT fails with ORA-09925: Unable to create audit trail file. RMAN cannot create it for you.

[DEST]

mkdir -p <AUX_AUDIT_DIR>
sqlplus / as sysdba <<EOF
STARTUP NOMOUNT PFILE='$ORACLE_HOME/dbs/init<AUX_INSTANCE>.ora';
EOF

Expected output:

ORACLE instance started.
Total System Global Area ...

Step 5: Verify the static service is registered before duplicating

Do not issue DUPLICATE until the static service shows in lsnrctl status. A NOMOUNT instance only appears via the static entry.

[DEST]

lsnrctl status

Expected output:

Service "<AUX_STATIC_SERVICE>" has 1 instance(s).
  Instance "<AUX_INSTANCE>", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully

status UNKNOWN is normal for a statically registered NOMOUNT instance.

Phase 4 — RMAN active duplication for standby

Build the standby directly from the running source over SQL*Net. With the target connected by a net service name, RMAN uses the bandwidth-efficient backup-set (pull) method when the command includes USING [COMPRESSED] BACKUPSET or SECTION SIZE, or when the number of auxiliary channels is greater than or equal to the number of target channels — these are independent triggers, and this command satisfies both. The whole CDB (all PDBs) is duplicated. Because this is a standby duplication, RMAN automatically copies the source password file to the standby (overwriting the temporary one created in Phase 3) — no PASSWORD FILE clause is needed. The result is a mounted standby; redo apply is not yet started.

Step 1: Connect RMAN to target and auxiliary

[DEST]

rman

[DEST]

RMAN> CONNECT TARGET sys/<SYS_PASSWORD>@<SOURCE_SCAN_HOST>:<LISTENER_PORT>/<SOURCE_DB_SERVICE>;
RMAN> CONNECT AUXILIARY sys/<SYS_PASSWORD>@<DEST_SCAN_HOST>:<LISTENER_PORT>/<AUX_STATIC_SERVICE>;

Expected output:

connected to target database: PRODCDB (DBID=...)
connected to auxiliary database: PRODCDB (not mounted)

Step 2: Set BASIC compression

Use BASIC only. LOW, MEDIUM, and HIGH require the Advanced Compression Option and are out of license scope.

[DEST]

RMAN> CONFIGURE COMPRESSION ALGORITHM 'BASIC';

Step 3: Run the duplicate

NOFILENAMECHECK is required because the source and destination are different hosts. CLUSTER_DATABASE='FALSE' is correct here and stays FALSE through switchover — the database remains single-instance until the RAC conversion in Phase 8, which runs on the open primary. Use USING COMPRESSED BACKUPSET (BASIC) by default; use USING BACKUPSET (no compression) instead if source CPU is constrained.

Relocating the flat-file datafiles into ASM. Because the source is on a non-OMF filesystem, always set DB_CREATE_FILE_DEST, DB_CREATE_ONLINE_LOG_DEST_1, and DB_RECOVERY_FILE_DEST (as in the command below). They place the control file, the online/standby redo logs, and every datafile into ASM as Oracle-Managed Files and auto-name them — no per-path mapping — which is the recommended default for hundreds of files. Oracle's guidance is explicit: "Set DB_CREATE_FILE_DEST explicitly… Do not rely solely on DB_FILE_NAME_CONVERT."

If you additionally need to map specific source datafile/redo paths explicitly, add DB_FILE_NAME_CONVERT / LOG_FILE_NAME_CONVERT on top (see the optional variant after the command). They take precedence for the files they match, while DB_CREATE_FILE_DEST still places the control file and anything unmatched — the *_CONVERT parameters do not relocate the control file. Combining them is valid here because the source is non-OMF: the "do not set DB_FILE_NAME_CONVERT if you set DB_CREATE_FILE_DEST" caution in the reference applies only to OMF sources, and Oracle's own ASM duplication example sets both together.

[DEST] — the duplicate command:

RMAN> RUN {
  ALLOCATE CHANNEL t1 TYPE DISK;
  ALLOCATE CHANNEL t2 TYPE DISK;
  ALLOCATE CHANNEL t3 TYPE DISK;
  ALLOCATE CHANNEL t4 TYPE DISK;
  ALLOCATE AUXILIARY CHANNEL a1 TYPE DISK;
  ALLOCATE AUXILIARY CHANNEL a2 TYPE DISK;
  ALLOCATE AUXILIARY CHANNEL a3 TYPE DISK;
  ALLOCATE AUXILIARY CHANNEL a4 TYPE DISK;
  DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE
    USING COMPRESSED BACKUPSET
    SECTION SIZE 8G
    SPFILE
      SET DB_UNIQUE_NAME='<DEST_DB_UNIQUE_NAME>'
      SET DB_CREATE_FILE_DEST='<DATA_DG>'
      SET DB_CREATE_ONLINE_LOG_DEST_1='<DATA_DG>'
      SET DB_RECOVERY_FILE_DEST='<RECO_DG>'
      SET CLUSTER_DATABASE='FALSE'
      SET STANDBY_FILE_MANAGEMENT='AUTO'
    NOFILENAMECHECK
    DORECOVER;
}

[DEST]optional explicit path mapping: to map specific source datafile/redo paths instead of letting OMF auto-name them, add these two lines to the SPFILE SET block above (keep DB_CREATE_FILE_DEST/DB_RECOVERY_FILE_DEST — they place the control file and any unmatched files):

      SET DB_FILE_NAME_CONVERT='<SOURCE_DATAFILE_PATH>','<DATA_DG>'
      SET LOG_FILE_NAME_CONVERT='<SOURCE_REDO_LOG_PATH>','<DATA_DG>'

If datafiles span multiple source mount points, add one 'source','+DATA' pair per path, e.g. SET DB_FILE_NAME_CONVERT='/u02/oradata/<SOURCE_DB_NAME>/','<DATA_DG>','/u03/oradata/<SOURCE_DB_NAME>/','<DATA_DG>'.

Expected output:

Finished Duplicate Db at ...

Step 4: Confirm the standby is mounted

[DEST]

SELECT db_unique_name, database_role, open_mode FROM v$database;

Expected output:

DB_UNIQUE_NAME   DATABASE_ROLE     OPEN_MODE
---------------- ----------------- --------------------
prodexa          PHYSICAL STANDBY  MOUNTED

Phase 5 — Data Guard configuration, redo apply, and gap catch-up

Add standby redo logs on the destination standby (required for redo transport and real-time apply), wire up redo transport and FAL on both sides, start managed recovery, then optionally enable the broker. The standby stays single-instance (CLUSTER_DATABASE=FALSE) throughout — RAC conversion happens after switchover in Phase 8.

Step 1: Add standby redo logs on the destination standby

[DEST]

ALTER DATABASE ADD STANDBY LOGFILE THREAD 1 SIZE <REDO_SIZE>;

Repeat for the required count (online groups per thread + 1). Confirm with SELECT group#, thread#, bytes FROM v$standby_log;.

Step 2: Set redo transport and FAL parameters on the standby

[DEST]

ALTER SYSTEM SET LOG_ARCHIVE_CONFIG='DG_CONFIG=(<SOURCE_DB_UNIQUE_NAME>,<DEST_DB_UNIQUE_NAME>)';
ALTER SYSTEM SET LOG_ARCHIVE_DEST_2='SERVICE=<SOURCE_DB_UNIQUE_NAME> ASYNC VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE) DB_UNIQUE_NAME=<SOURCE_DB_UNIQUE_NAME>';
ALTER SYSTEM SET FAL_SERVER='<SOURCE_DB_UNIQUE_NAME>';
ALTER SYSTEM SET FAL_CLIENT='<DEST_DB_UNIQUE_NAME>';
ALTER SYSTEM SET STANDBY_FILE_MANAGEMENT='AUTO';

SERVICE=<SOURCE_DB_UNIQUE_NAME> and FAL_SERVER='<SOURCE_DB_UNIQUE_NAME>' must resolve on the destination through an Oracle Net naming method to the source listener (the destination tnsnames.ora stanza referenced in Phase 2 Step 7).

Step 3: Set the matching transport parameters on the source

SERVICE=<DEST_DB_UNIQUE_NAME> and FAL_SERVER='<DEST_DB_UNIQUE_NAME>' must resolve on the source — staged in Phase 2 Step 7.

[SYS]

ALTER SYSTEM SET LOG_ARCHIVE_CONFIG='DG_CONFIG=(<SOURCE_DB_UNIQUE_NAME>,<DEST_DB_UNIQUE_NAME>)';
ALTER SYSTEM SET LOG_ARCHIVE_DEST_2='SERVICE=<DEST_DB_UNIQUE_NAME> ASYNC VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE) DB_UNIQUE_NAME=<DEST_DB_UNIQUE_NAME>';
ALTER SYSTEM SET FAL_SERVER='<DEST_DB_UNIQUE_NAME>';
ALTER SYSTEM SET FAL_CLIENT='<SOURCE_DB_UNIQUE_NAME>';

Step 4: Start managed recovery with real-time apply

[DEST]

ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;

Expected output:

Database altered.

Step 5: Confirm apply is running and the gap is closing

[DEST]

SELECT process, status, thread#, sequence# FROM v$managed_standby WHERE process LIKE 'MRP%';
SELECT name, value FROM v$dataguard_stats WHERE name IN ('transport lag','apply lag');
SELECT * FROM v$archive_gap;

Expected output:

PROCESS   STATUS       THREAD#  SEQUENCE#
--------- ------------ -------- ---------
MRP0      APPLYING_LOG        1      4567

NAME           VALUE
-------------- -------------
transport lag  +00 00:00:00
apply lag      +00 00:00:00

(v$archive_gap: no rows selected)

Step 6 (optional): Enable the broker

The broker is optional for this migration — Phase 7 provides a SQL*Plus switchover path that does not require it. If you choose to use the broker, it requires static _DGMGRL listener entries on both sides. After adding those entries:

[DEST]

DGMGRL> CONNECT sys/<SYS_PASSWORD>
DGMGRL> CREATE CONFIGURATION 'dg_config' AS PRIMARY DATABASE IS '<SOURCE_DB_UNIQUE_NAME>' CONNECT IDENTIFIER IS '<SOURCE_DB_SERVICE>';
DGMGRL> ADD DATABASE '<DEST_DB_UNIQUE_NAME>' AS CONNECT IDENTIFIER IS '<DEST_DB_UNIQUE_NAME>';
DGMGRL> ENABLE CONFIGURATION;

Optional convenience note: DBCA can wrap the duplicate with -createDuplicateDB -createAsStandby. These flags are documented in the 19c Data Guard Concepts and Administration guide (section 3.4) and produce only a mounted standby — no standby redo logs, no managed apply, no broker. They are not a substitute for the configuration work in this phase. The primary path here uses explicit RMAN duplication plus the steps above.

Phase 6 — Pre-switchover gap verification

Quiesce the apply pipeline state to a clean, verifiable point immediately before cutover. The standby is still single-instance (CLUSTER_DATABASE=FALSE); no structural RAC work happens here. Stop managed recovery, confirm the gap is closed, then proceed to switchover.

Step 1: Stop managed recovery

[DEST]

ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL;

Step 2: Verify the apply gap before switchover

[DEST]

SELECT name, value FROM v$dataguard_stats WHERE name IN ('transport lag','apply lag');
SELECT * FROM v$archive_gap;

Expected output:

NAME           VALUE
-------------- -------------
transport lag  +00 00:00:00
apply lag      +00 00:00:00

(v$archive_gap: no rows selected)

Both lags at or near zero and no archive gap is the green light for switchover. The VERIFY option of the switchover statement (Phase 7) re-checks readiness, but confirm here first.

Note: Switchover requires Redo Apply to be active on the target standby. Re-enable managed recovery before issuing the switchover so the target is in the required state:

[DEST]

ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;

Phase 7 — Switchover (cutover)

Warning: Switchover transfers the primary role. Confirm apply lag is ~0 and there is no archive gap (Phase 6 Step 2) before proceeding. The application outage begins now and lasts seconds to minutes.

The SQL*Plus path (Step 1) is the primary, broker-independent method and works whether or not the broker is configured. If you enabled the broker in Phase 5 Step 6, use the DGMGRL alternative (Step 2) instead — do not run both.

Step 1: Switch over using SQL*Plus (primary method)

The single-command switchover prerequisites are: the primary is open and Redo Apply is active on the target standby. Run VERIFY first, then the switchover. Both statements run on the current primary (the source), so they are [SYS].

[SYS]

ALTER DATABASE SWITCHOVER TO <DEST_DB_UNIQUE_NAME> VERIFY;

Expected output:

Database altered.

Database altered. means the target is ready. ORA-16470: Redo Apply is not running on switchover target means managed recovery is not active on the destination — start it (Phase 6 final note) and re-run VERIFY. After a clean VERIFY, perform the switchover:

[SYS]

ALTER DATABASE SWITCHOVER TO <DEST_DB_UNIQUE_NAME>;

Expected output:

Database altered.

After this statement the original primary (source) transitions to the physical standby role with no shutdown/restart. The destination becomes the primary but is still mounted (it was a managed-recovery standby, not Active Data Guard). Open it on the destination:

[DEST]

ALTER DATABASE OPEN;

Expected output:

Database altered.

The former source is now a mounted physical standby; redo apply does not auto-start with the SQL path. Re-start managed recovery on the new standby (remote SYS to the former source) so it applies redo from the new primary:

[SYS]

ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;

Expected output:

Database altered.

Skip Step 2 — the switchover is complete. Proceed to Phase 8.

Step 2: Switch over using DGMGRL (broker alternative)

Use this only if you enabled the broker in Phase 5 Step 6. DGMGRL opens the new primary automatically.

[DEST]

DGMGRL> SWITCHOVER TO <DEST_DB_UNIQUE_NAME>;

Expected output:

Performing switchover NOW, please wait...
Switchover succeeded, new primary is "<DEST_DB_UNIQUE_NAME>"

Step 3: Verify the destination is now an open primary

[DEST]

SELECT db_unique_name, database_role, open_mode FROM v$database;

Expected output:

DB_UNIQUE_NAME   DATABASE_ROLE  OPEN_MODE
---------------- -------------- --------------------
prodexa          PRIMARY        READ WRITE

Phase 8 — RAC conversion on the open primary

Convert the now-open single-instance primary into a 2-node RAC database. Structural changes — adding a second redo thread, its standby redo logs, and a second undo tablespace — run against the open database. Set the cluster and per-instance parameters in the SPFILE, register the database and both instances with CRS as a PRIMARY started OPEN, then bounce under Clusterware so the second instance comes up.

Step 1: Add the second redo thread, its standby redo logs, and a second UNDO

Run on the open primary. Each RAC instance requires its own redo thread and its own undo tablespace.

[DEST]

ALTER DATABASE ADD LOGFILE THREAD 2 SIZE <REDO_SIZE>;
ALTER DATABASE ADD STANDBY LOGFILE THREAD 2 SIZE <REDO_SIZE>;
ALTER DATABASE ENABLE PUBLIC THREAD 2;
CREATE UNDO TABLESPACE UNDOTBS2 DATAFILE '<DATA_DG>' SIZE 4G AUTOEXTEND ON;

Repeat the redo/standby-redo statements to reach the required group count per thread.

Step 2: Move the SPFILE to ASM and set the cluster and per-instance parameters

[DEST]

CREATE SPFILE='<DATA_DG>/<DEST_DB_UNIQUE_NAME>/spfile<DEST_DB_UNIQUE_NAME>.ora' FROM PFILE;

Set CLUSTER_DATABASE=TRUE and the unique per-instance values (INSTANCE_NUMBER, THREAD, UNDO_TABLESPACE must be unique per instance):

[DEST]

ALTER SYSTEM SET CLUSTER_DATABASE=TRUE SCOPE=SPFILE;
ALTER SYSTEM SET INSTANCE_NUMBER=1 SID='<INST1>' SCOPE=SPFILE;
ALTER SYSTEM SET INSTANCE_NUMBER=2 SID='<INST2>' SCOPE=SPFILE;
ALTER SYSTEM SET THREAD=1 SID='<INST1>' SCOPE=SPFILE;
ALTER SYSTEM SET THREAD=2 SID='<INST2>' SCOPE=SPFILE;
ALTER SYSTEM SET UNDO_TABLESPACE='UNDOTBS1' SID='<INST1>' SCOPE=SPFILE;
ALTER SYSTEM SET UNDO_TABLESPACE='UNDOTBS2' SID='<INST2>' SCOPE=SPFILE;

Step 3: Register the database and instances with CRS

The database is now a primary, so register it with -role PRIMARY and -startoption OPEN (both are the srvctl add database defaults). -diskgroup names omit the leading +.

[DEST]

srvctl add database -db <DEST_DB_UNIQUE_NAME> -oraclehome <DEST_ORACLE_HOME> \
  -spfile <DATA_DG>/<DEST_DB_UNIQUE_NAME>/spfile<DEST_DB_UNIQUE_NAME>.ora \
  -pwfile $ORACLE_HOME/dbs/orapw<AUX_INSTANCE> \
  -dbtype RAC -role PRIMARY -startoption OPEN \
  -diskgroup "<DATA_DG_NAME>,<RECO_DG_NAME>"
srvctl add instance -db <DEST_DB_UNIQUE_NAME> -instance <INST1> -node <NODE1>
srvctl add instance -db <DEST_DB_UNIQUE_NAME> -instance <INST2> -node <NODE2>

Step 4: Shut down the manually-started instance before Clusterware takes over

Warning: Shut down the instance that was started manually (the single instance running since the duplicate) before starting the database under Clusterware. Starting under CRS while a manually-started instance is up will collide on the same instance.

[DEST]

SHUTDOWN IMMEDIATE;

Step 5: Start the database under CRS

[DEST]

srvctl start database -db <DEST_DB_UNIQUE_NAME>
srvctl status database -db <DEST_DB_UNIQUE_NAME>

Expected output:

Instance <INST1> is running on node <NODE1>
Instance <INST2> is running on node <NODE2>

Step 6: Verify roles on both instances

[DEST]

SELECT inst_id, database_role, open_mode FROM gv$database;

Expected output:

   INST_ID DATABASE_ROLE    OPEN_MODE
---------- ---------------- --------------------
         1 PRIMARY          READ WRITE
         2 PRIMARY          READ WRITE

Phase 9 — Post-cutover memory parameters and BIGFILE consolidation

Set memory parameters on the CDB root, then consolidate user tablespaces into BIGFILE tablespaces per-PDB using online segment relocation. Introduce no compression: preserve every segment's existing compression attributes on move.

Step 1: Set memory parameters at the CDB root

Run in CDB$ROOT.

[DEST]

ALTER SYSTEM SET SGA_TARGET=<SGA_TARGET> SCOPE=SPFILE SID='*';
ALTER SYSTEM SET PGA_AGGREGATE_TARGET=<PGA_TARGET> SCOPE=SPFILE SID='*';

Bounce per instance with srvctl if the values require a restart.

Step 2: Generate the relocation list per PDB (with an exclusion list)

Switch into the target PDB first. State the container context before each divergent command.

[DEST]

ALTER SESSION SET CONTAINER = <PDB_NAME>;

Build the list of user segments to move, excluding SYSTEM, SYSAUX, UNDO, and TEMP (those are handled separately or skipped) so no MOVE is generated for dictionary segments. Exclude partitioned objects (none should exist — Phase 0 flags them).

[DEST]

SELECT 'ALTER TABLE "'||owner||'"."'||table_name||'" MOVE ONLINE TABLESPACE <NEW_TS>;'
FROM   dba_tables
WHERE  owner NOT IN ('SYS','SYSTEM')
AND    tablespace_name NOT IN ('SYSTEM','SYSAUX')
AND    tablespace_name NOT LIKE 'UNDO%'
AND    tablespace_name NOT LIKE 'TEMP%'
AND    (partitioned = 'NO' OR partitioned IS NULL)
AND    nested = 'NO'
AND    iot_type IS NULL
UNION ALL
SELECT 'ALTER INDEX "'||owner||'"."'||index_name||'" REBUILD ONLINE TABLESPACE <NEW_TS>;'
FROM   dba_indexes
WHERE  owner NOT IN ('SYS','SYSTEM')
AND    tablespace_name NOT IN ('SYSTEM','SYSAUX')
AND    partitioned = 'NO';

Step 3: Create the BIGFILE tablespace and relocate segments online

For each smallfile user tablespace <OLD_TS>:

[DEST]

CREATE BIGFILE TABLESPACE <NEW_TS> DATAFILE '<DATA_DG>' SIZE 100G AUTOEXTEND ON NEXT 8G MAXSIZE UNLIMITED;

Run the generated relocation statements (Step 2 output):

[DEST]

ALTER TABLE "<SCHEMA>"."<TABLE>" MOVE ONLINE TABLESPACE <NEW_TS>;
ALTER INDEX "<SCHEMA>"."<INDEX>" REBUILD ONLINE TABLESPACE <NEW_TS>;

MOVE ONLINE and REBUILD ONLINE are core Enterprise Edition and introduce no chargeable option. Compression attributes carried by the segments are preserved unchanged.

Warning: Segments that online MOVE rejects — clusters, IOTs with LOBs, online LOB relocation, materialized views, and any partitioned tables found in Phase 0 — must be relocated with DBMS_REDEFINITION, not ALTER TABLE ... MOVE PARTITION. MOVE PARTITION invokes the Partitioning option, which is out of license scope.

For rejected segments:

[DEST]

BEGIN
  DBMS_REDEFINITION.START_REDEF_TABLE(
    uname        => '<SCHEMA>',
    orig_table   => '<TABLE>',
    int_table    => '<TABLE>_INT',
    options_flag => DBMS_REDEFINITION.CONS_USE_ROWID);
END;
/

Complete with COPY_TABLE_DEPENDENTS and FINISH_REDEF_TABLE per the package reference, targeting <NEW_TS>.

Step 4: Rename and drop the old tablespace

Warning: DROP TABLESPACE ... INCLUDING CONTENTS AND DATAFILES permanently deletes the datafiles. Verify all segments moved out of <OLD_TS> before dropping.

[DEST]

ALTER TABLESPACE <OLD_TS> RENAME TO <OLD_TS>_OLD;
ALTER TABLESPACE <NEW_TS> RENAME TO <OLD_TS>;
DROP TABLESPACE <OLD_TS>_OLD INCLUDING CONTENTS AND DATAFILES;

Step 5: Leave SYSTEM and SYSAUX as-is

SYSTEM and SYSAUX cannot be renamed, dropped, or taken offline. Leave them unchanged.

Step 6: Migrate UNDO to BIGFILE (per RAC instance, LOCAL_UNDO aware)

Check whether local undo is enabled:

[DEST]

SELECT property_value FROM database_properties WHERE property_name = 'LOCAL_UNDO_ENABLED';

With local undo on (TRUE), each PDB/instance has its own undo tablespace; set the container accordingly before each statement. Create a bigfile undo, switch the instance to it, then drop the old. Repeat per instance/thread.

Warning: DROP TABLESPACE ... INCLUDING CONTENTS AND DATAFILES permanently deletes the undo datafiles. Switch the instance to the new undo tablespace (ALTER SYSTEM SET UNDO_TABLESPACE) and confirm no active transactions reference the old one before dropping.

[DEST]

CREATE BIGFILE UNDO TABLESPACE UNDOTBS1_BF DATAFILE '<DATA_DG>' SIZE 8G AUTOEXTEND ON;
ALTER SYSTEM SET UNDO_TABLESPACE='UNDOTBS1_BF' SID='<INST1>';
DROP TABLESPACE UNDOTBS1 INCLUDING CONTENTS AND DATAFILES;

Step 7: Migrate TEMP to BIGFILE

Warning: DROP TABLESPACE ... INCLUDING CONTENTS AND DATAFILES permanently deletes the tempfiles. Set the new tablespace as the database default temporary tablespace before dropping the old one.

[DEST]

CREATE BIGFILE TEMPORARY TABLESPACE TEMP_BF TEMPFILE '<DATA_DG>' SIZE 32G AUTOEXTEND ON;
ALTER DATABASE DEFAULT TEMPORARY TABLESPACE TEMP_BF;
DROP TABLESPACE TEMP INCLUDING CONTENTS AND DATAFILES;

Phase 10 — Migration account revert and staging teardown

Optionally revert the password-expiry change, then tear down the chroot account and ACFS volume on both nodes.

Step 1 (optional): Revert password expiry on the new primary

The profile change made in Phase 2 lives in the database that was replicated and switched over, so the migration account's profile is now on the new primary (the destination). The revert therefore runs on the destination as [DEST], not against the old source. This is optional hardening — the profile change persists harmlessly through the migration. Restore the original PASSWORD_LIFE_TIME (e.g., the default 180):

[DEST]

ALTER PROFILE <PROFILE_NAME> LIMIT PASSWORD_LIFE_TIME <ORIGINAL_VALUE>;

Step 2: Remove the sshd Match block and reload (both nodes)

As root on both nodes, delete the Match User <TRANSFER_USER> block from /etc/ssh/sshd_config, then:

[DEST]

sshd -t
systemctl reload sshd

Step 3: Remove the transfer account and group (both nodes)

[DEST]

userdel -r <TRANSFER_USER>
groupdel <TRANSFER_GROUP>

Step 4: Shred staged secrets and dismount the ACFS volume

Warning: acfsutil registry -d and voldelete permanently remove the staging filesystem and its ADVM volume.

[DEST]

shred -u <CHROOT_HOME>/orapw* <CHROOT_HOME>/spfile* <CHROOT_HOME>/tnsnames.ora
acfsutil snap info <ACFS_MOUNT>
umount <ACFS_MOUNT>
acfsutil registry -d <ACFS_MOUNT>
asmcmd voldisable -G <DG_NAME> <VOLUME_NAME>
asmcmd voldelete -G <DG_NAME> <VOLUME_NAME>
rmdir <ACFS_MOUNT>

Validation

Quick check

If you enabled the broker (Phase 5 Step 6), confirm the configuration:

[DEST]

DGMGRL> SHOW CONFIGURATION;

Expected output:

Configuration - dg_config
  Protection Mode: MaxPerformance
  Members:
  prodexa          - Primary database
  prodcdb          - Physical standby database
Configuration Status:
SUCCESS   (status updated ...)

If the broker is not in use, confirm roles directly instead:

[DEST]

SELECT database_role, open_mode FROM v$database;

Expected output:

DATABASE_ROLE  OPEN_MODE
-------------- ------------
PRIMARY        READ WRITE

The new Exadata RAC database as primary confirms the migration completed.

Full validation

  1. Both instances open as primary

[DEST]

srvctl status database -db <DEST_DB_UNIQUE_NAME>

Expected output:

Instance <INST1> is running on node <NODE1>
Instance <INST2> is running on node <NODE2>
  1. Roles and cluster status

[DEST]

SELECT database_role, open_mode, cluster_database FROM v$database;
SELECT thread#, status, enabled FROM v$thread;

Expected output:

DATABASE_ROLE  OPEN_MODE    CLUSTER_DATABASE
-------------- ------------ ----------------
PRIMARY        READ WRITE   TRUE

   THREAD# STATUS ENABLED
---------- ------ --------
         1 OPEN   PUBLIC
         2 OPEN   PUBLIC
  1. BIGFILE consolidation, per PDB

[DEST]

SELECT con_id, tablespace_name, bigfile FROM cdb_tablespaces WHERE bigfile = 'YES';
SELECT con_id, COUNT(*) AS datafiles FROM cdb_data_files GROUP BY con_id;
SELECT tablespace_name FROM dba_tablespaces WHERE tablespace_name IN ('SYSTEM','SYSAUX');

Expected output:

-- consolidated user tablespaces show BIGFILE = YES
-- cdb_data_files count is sharply lower than the original hundreds
-- SYSTEM and SYSAUX still present (left intact)
  1. Compliance re-check (no chargeable option introduced)

[DEST]

SELECT con_id, wrl_type, status FROM v$encryption_wallet;
SELECT compression, compress_for, COUNT(*) FROM cdb_tables WHERE compression='ENABLED' GROUP BY compression, compress_for;

Re-run options_packs_usage_statistics.sql (MOS 1317265.1) on the new primary and confirm no new chargeable option appears. Encryption views still show none; compression remains BASIC only.

  1. No archive gap on the new standby (the former source)

[SYS]

SELECT * FROM v$archive_gap;
SELECT process, status FROM v$managed_standby WHERE process LIKE 'MRP%';

Expected output:

(v$archive_gap: no rows selected)
PROCESS   STATUS
--------- ------------
MRP0      APPLYING_LOG

Troubleshooting

Problem Cause Solution
RMAN-04006: error from auxiliary database / ORA-01017: invalid username/password on CONNECT AUXILIARY Password file missing or SYS password differs between source and destination auxiliary Recreate both password files with the same <SYS_PASSWORD>: orapwd file=$ORACLE_HOME/dbs/orapw<SID> password=<SYS_PASSWORD> format=12 force=y; set REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE on the source
ORA-12514 / TNS:listener does not currently know of service when connecting to the auxiliary NOMOUNT instance did not self-register; no static listener entry Add the SID_LIST static entry for <AUX_STATIC_SERVICE>, run lsnrctl reload, and confirm lsnrctl status shows the service before DUPLICATE
ORA-19505: failed to identify file / file-naming errors during DUPLICATE DB_CREATE_FILE_DEST/DB_RECOVERY_FILE_DEST not set, NOFILENAMECHECK omitted with different hosts, or (with explicit mapping) a DB_FILE_NAME_CONVERT source path doesn't match the real file location Set DB_CREATE_FILE_DEST='<DATA_DG>' and DB_RECOVERY_FILE_DEST='<RECO_DG>' in the SPFILE SET clause and include NOFILENAMECHECK — these place the control file/logs even when explicit conversion is used. If using explicit DB_FILE_NAME_CONVERT, verify each 'source' path matches the real source datafile/redo directories (one pair per mount point); keep DB_CREATE_FILE_DEST set so the control file is still placed
ORA-09925: Unable to create audit trail file on STARTUP NOMOUNT of the auxiliary audit_file_dest directory does not exist on disk mkdir -p <AUX_AUDIT_DIR> (matching audit_file_dest in the auxiliary pfile) before STARTUP NOMOUNT
ORA-28001: the password has expired mid-build Migration account password aged out during the multi-day seed Set ALTER PROFILE <PROFILE_NAME> LIMIT PASSWORD_LIFE_TIME UNLIMITED and re-set the password before starting (Phase 2 Step 3)
Redo transport fails after switchover; ORA-12154/TNS:could not resolve the connect identifier in the standby alert log SERVICE/FAL_SERVER name not resolvable on the database where LOG_ARCHIVE_DEST_n is set Add the matching tnsnames.ora stanza (<DEST_DB_UNIQUE_NAME> on the source, <SOURCE_DB_UNIQUE_NAME> on the destination) pointing at the correct SCAN/service (Phase 2 Step 7)
bad ownership or modes for chroot directory on SFTP login <ACFS_MOUNT> or a parent is not root:root or is group/world-writable chown root:root <ACFS_MOUNT>; chmod 755 <ACFS_MOUNT>; keep the writable drop dir as a user-owned subdir inside the chroot
Standby falls behind; V$ARCHIVE_GAP shows missing sequences Source archived-redo retention too small for the seed window Increase the source FRA / archive destination size; re-ship the gap via FAL_SERVER, or roll forward with RECOVER STANDBY DATABASE FROM SERVICE
MRP not applying; V$MANAGED_STANDBY shows no MRP0 Managed recovery not started, or no standby redo logs on the destination Add standby redo logs on the destination (Phase 5 Step 1), then ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION
ORA-16470: Redo Apply is not running on switchover target on SWITCHOVER TO ... VERIFY Managed recovery is not active on the destination standby Start managed recovery on the destination (Phase 6 final note), then re-run the VERIFY and switchover statements
SWITCHOVER fails — ORA-16775 / target not ready Apply gap not zero, or standby redo logs missing on one side Resolve the gap (lags ~0, no V$ARCHIVE_GAP) and ensure standby redo logs exist on both databases and both threads, then retry
Second instance fails to start under srvctl start database A manually-started instance still holds the database, colliding with Clusterware SHUTDOWN IMMEDIATE the manually-started instance (Phase 8 Step 4) before srvctl start database
ALTER TABLE ... MOVE ONLINE reports that the operation is not supported for the segment Segment type (cluster, IOT-with-LOB, MV) not eligible for online move Relocate with DBMS_REDEFINITION instead; never use MOVE PARTITION (Partitioning option is unlicensed)
srvctl add database rejects the disk group argument Leading + included in -diskgroup Use disk group names without +: -diskgroup "<DATA_DG_NAME>,<RECO_DG_NAME>"

References