Migration einer non-CDB auf File System zu einer PDB auf ASM mittels DataGuard

Eine Oracle 19 non-CDB auf File System wird mittels Physical Standby und Data Guard auf eine andere Maschine migriert. Auf der Ziel-Maschine wird die Datenbank in eine Pluggable Database (PDB) konvertiert. Die alte non-CDB wird dabei als Fallback-Lösung erhalten. Auf der Zielmaschine läuft bereits eine CDB$ROOT die ASM nutzt. Dieses Vorgehen kann so für eine Testmigration als auch für die eigentliche Migration angewendet werden.

Vorhandene Struktur und Ausgangssituation

             Quelle                    Ziel
--------------------------------------------------
Host: oracle-srv4 oracle-srv6
DB_NAME: TISLI ILTIS
PATCH: 19.21.0.0.231017 19.21.0.0.231017
CDB nonCDB CDB
PDB nein ja, Name: ILTIS
ASM nein ja, Diskgroup: DATA01

TEIL 1: Physical Standby erzeugen

In diesem Schritt wird auf der Ziel-Maschine eine Physical Standby Datenbank erzeugt, welche direkt ASM nutzt.

Primary Database vorbereiten

Force Logging und dg_broker Parameter einstellen

alter database force logging;
select name, force_logging from v$database;
alter system set dg_broker_start=true scope=both;

Standby Logfiles auf Primary erzeugen

Anzahl Standby Logfile [n] = Thread * Redologfiles[n] + 1

Die Größe der Standby Logfiles sollte hierbei der Größe der Redologfiles entsprechen.

alter database add standby logfile group 11 ('/u01/app/oracle/oradata/TISLI/standby11.log') size 200M;
alter database add standby logfile group 12 ('/u01/app/oracle/oradata/TISLI/standby12.log') size 200M;
alter database add standby logfile group 13 ('/u01/app/oracle/oradata/TISLI/standby13.log') size 200M;
alter database add standby logfile group 14 ('/u01/app/oracle/oradata/TISLI/standby14.log') size 200M;

Oracle Net Konfiguration

tnsnames.ora der oracle-srv4

LISTENER_TISLI =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-srv4.example.cnh)(PORT = 1522))


TISLI =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-srv4.example.cnh)(PORT = 1522))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = TISLI.example.cnh)
)
)


ILTIS =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-srv6.example.cnh)(PORT = 1522))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = ILTIS.example.cnh)
(UR = A)
)
)

tnsnames.ora der oracle-srv6

ILTIS_LISTENER =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-srv6.example.cnh)(PORT = 1522))


ILTIS =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-srv6.example.cnh)(PORT = 1522))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = ILTIS.example.cnh)
(UR = A)
)
)

TISLI =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-srv4.example.cnh)(PORT = 1522))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = TISLI.example.cnh)
)
)

listener.ora der oracle-srv4

TISLI_LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-srv4.example.cnh)(PORT = 1522))
)
)

SID_LIST_LISTENER_TISLI =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = TISLI_DGMGRL.example.cnh)
(SERVICE_NAME = TISLI.example.cnh)
(SID_NAME = TISLI)
(ORACLE_HOME=/u01/app/oracle/product/19.0.0/db19300)
)
)

listener.ora der oracle-srv6

ILTIS_LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-srv6.example.cnh)(PORT = 1522))
)
)

SID_LIST_ILTIS_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = ILTIS_DGMGRL.example.cnh)
(SERVICE_NAME = ILTIS.example.cnh)
(SID_NAME = ILTIS)
(ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1)
)
)

Listener auf Ziel-Maschine starten

 lsnrctl start ILTIS_LISTENER

PFILE von der Quell-DB erzeugen

create pfile='/home/oracle/initTISLI.ora' from spfile;

Das PFILE:

TISLI.__data_transfer_cache_size=0
TISLI.__db_cache_size=788529152
TISLI.__inmemory_ext_roarea=0
TISLI.__inmemory_ext_rwarea=0
TISLI.__java_pool_size=0
TISLI.__large_pool_size=16777216
TISLI.__oracle_base='/u01/app/oracle'#ORACLE_BASE set from environment
TISLI.__pga_aggregate_target=402653184
TISLI.__sga_target=1191182336
TISLI.__shared_io_pool_size=67108864
TISLI.__shared_pool_size=301989888
TISLI.__streams_pool_size=0
TISLI.__unified_pga_pool_size=0
*.aq_tm_processes=0
*.audit_file_dest='/u01/app/oracle/admin/TISLI/adump'
*.audit_trail='db'
*.compatible='19.0.0'
*.control_files='/u01/app/oracle/oradata/TISLI/control01.ctl','/u01/app/oracle/fast_recovery_area/TISLI/control02.ctl'
*.db_block_size=8192
*.db_create_file_dest='/u01/app/oracle/oradata/'
*.db_domain='example.cnh'
*.db_name='TISLI'
*.db_recovery_file_dest='/u01/app/oracle/fast_recovery_area'
*.db_recovery_file_dest_size=1000m
*.diagnostic_dest='/u01/app/oracle'
*.dispatchers='(PROTOCOL=TCP) (SERVICE=TISLIXDB)'
*.fal_server=''
*.job_queue_processes=0
*.local_listener='LISTENER_TISLI'
*.log_archive_dest_state_2='RESET'
*.log_archive_format='%t_%s_%r.dbf'
*.nls_language='AMERICAN'
*.nls_territory='AMERICA'
*.open_cursors=300
*.pga_aggregate_target=379m
*.processes=300
*.remote_login_passwordfile='EXCLUSIVE'
*.sga_target=1136m
*.undo_tablespace='UNDOTBS1'

PFILE auf Ziel-Maschine kopieren und anpassen

Das pfile kopieren, in initILTIS.ora umbenennen und anpassen. Wichtig ist hierbei, dass db_name='TISLI' ist, da sonst das RMAN duplicate from active database abbricht.

*.audit_file_dest='/u01/app/oracle/admin/ILTIS/adump'
*.audit_trail='db'
*.compatible='19.0.0'
*.control_files='+DATA01/ILTIS/CONTROLFILE/current.262.1174986935','+DATA01/ILTIS/CONTROLFILE/current.262.1174986935'
*.log_file_name_convert='/u01/app/oracle/oradata/TISLI/','+DATA01/ILTIS/ONLINELOG/'
*.db_file_name_convert='/u01/app/oracle/oradata/TISLI/','+DATA01/ILTIS/DATAFILE/'
*.standby_file_management='AUTO'
*.db_create_file_dest='+DATA01'
*.db_block_size=8192
*.db_domain='example.cnh'
*.db_name='TISLI'
*.db_unique_name=ILTIS
*.db_recovery_file_dest='+DATA01'
*.db_recovery_file_dest_size=1000m
*.diagnostic_dest='/u01/app/oracle'
*.dispatchers='(PROTOCOL=TCP) (SERVICE=ILTISXDB)'
*.local_listener='ILTIS_LISTENER'
*.log_archive_format='%t_%s_%r.dbf'
*.nls_language='AMERICAN'
*.nls_territory='AMERICA'
*.open_cursors=300
*.pga_aggregate_target=379m
*.processes=300
*.remote_login_passwordfile='EXCLUSIVE'
*.sga_target=1136m
*.undo_tablespace='UNDOTBS1'

Parameter standby_file_management='AUTO' setzen, da sonst neu erzeugte Datenfiles der Primary leider dann auf der Standby im File System angelegt werdern. Und das obwohl auf der Standby der Parameter db_create_file_dest=+DATA01 für die Diskgruppe im ASM eingestellt ist.

Password File auf Ziel-Maschine für Standby DB erstellen

orapwd file=/u01/app/oracle/product/19.0.0/dbhome_1/orapwILTIS password=oracle entries=10 format=12

Eintrag in der /etc/oratab auf Ziel-Maschine erstellen

ILTIS:/u01/app/oracle/product/19.0.0/dbhome_1:N

Notwendige Directories auf Ziel-Maschine anlegen

mkdir -p /u01/app/oracle/admin/ILTIS/adump

Standby Instanz auf Ziel-Maschine hochfahren, SPFILE erzeugen und durchstarten

Das spfile Ist notwendig, da sonst das RMAN Duplicate abbricht.

. oraenv    --> ILTIS
sqlplus "/ as sysdba"
startup nomount pfile='/u01/app/oracle/product/19.0.0/dbhome_1/initILTIS.ora';
create spfile from pfile='/u01/app/oracle/product/19.0.0/dbhome_1/dbs/initILTIS.ora';
shutdown immediate;
startup nomount;

ORACLE Net Konfiguration prüfen

Jede der beiden Instanzen muss von jeder der beiden Maschinen oracle-srv4 und oracle-srv6 erreichbar sein.

# oracle-srv4:
      sqlplus sys/oracle@TISLI as sysdba
      select instance_name, host_name from v$instance;

      sqlplus sys/oracle@ILTIS as sysdba
      select instance_name, host_name from v$instance;

# oracle-srv6:
      sqlplus sys/oracle@TISLI as sysdba
      select instance_name, host_name from v$instance;

      sqlplus sys/oracle@ILTIS as sysdba
      select instance_name, host_name from v$instance;

RMAN duplicate database for standby from active database durchführen

In RMAN wird die Quell-DB als „target“ bezeichnet und die Ziel-DB als auxiliary! RMAN wird hier auf der Standby Seite aufgerufen.

rman target sys/oracle@TISLI auxiliary sys/oracle@ILTIS
run
{
allocate channel t1 device type disk;
allocate channel t2 device type disk;
allocate auxiliary channel t3 device type disk;
allocate auxiliary channel t4 device type disk;
duplicate target database for standby from active database dorecover nofilenamecheck;
}

Standby Logfiles auf der Standby erzeugen

Sollten diese noch fehlen, weil vor dem RMAN Duplicate keine auf der Primary angelegt wurden, kann das auf der Standby sowie auf der Primary noch nachgeholt werden.

alter database add standby logfile group 11 '+DATA01' size 200M;
alter database add standby logfile group 12 '+DATA01' size 200M;
alter database add standby logfile group 13 '+DATA01' size 200M;
alter database add standby logfile group 14 '+DATA01' size 200M;

TEMP Tablespace auf Standby prüfen

set lin 180
set pages 4000
col FILENAME format a50
--
SELECT TMP.NAME FILENAME, BYTES/1024/1024 as "MB", TS.NAME TABLESPACE FROM V$TEMPFILE TMP, V$TABLESPACE TS WHERE TMP.TS#=TS.TS#;

Es ist unerheblich, wenn z.B. die Größe 0 ist. Das wird später beim aktivieren der Standby verschwinden. Wichtig ist, dass ein TEMP File da ist.

Data Guard Broker Parameter auf Standby setzen

Der Parameter ist notwendig, sonst lässt sich die Standby nicht in der Data Guard Broker Konfiguration hinzufügen. Auf der Primary wurde dieser ja schon zu Beginn gesetzt.

alter system set dg_broker_start=true scope=both;

Data Guard Broker Konfiguration erzeugen

dgmgrl sys/oracle@TISLI #Hier geht auch dgmgrl sys/oracle@ILTIS
CREATE CONFIGURATION FOR_MIGRATION AS PRIMARY DATABASE IS TISLI CONNECT IDENTIFIER IS TISLI;
ADD DATABASE ILTIS AS CONNECT IDENTIFIER IS ILTIS MAINTAINED AS PHYSICAL;
ENABLE CONFIGURATION;
SHOW CONFIGURATION;

Auf der PRIM einige Logswitches machen.

alter system archive log current;
alter system archive log current;
alter system archive log current;

Data Guard Broker Konfiguration prüfen. Kann 1-2 Minuten dauern bis der Status SUCCESS angezeigt wird.

SHOW CONFIGURATION;

Bei großen Datenbanken kann die so erstellte Physical Standby Datenbank nun bis zum beabsichtigten (Test-)Migrations-Termin weiterlaufen.

TEIL 2: Migration non-CDB to PDB

Bei einer Testmigration wird die alte Primary unterbrechungsfrei weiter produktiv verwendet und die neu erzeugte Primary nur für bestimmte Tests bereitgestellt. Bei der eigentlichen Migration wird die alte Primary rechtzeitig gesperrt und am Ende natürlich nicht mehr verfügbar gemacht.

User auf der Primary sperren und Process Parameter setzen

***Hinweis: Das ist bei einer Testmigration nicht notwendig und die betreffende Datenbank kann weiter ohne Unterbrechung und Einschränkungen genutzt werden.

alter user YOUR_DB_USER account lock;
alter system set job_queue_processes=0 scope=both;
alter system set aq_tm_processes=0 scope=both;

Data Guard Redo Transport und Apply stoppen

Auf der PRIM einige Logswitches machen.

alter system archive log current;
alter system archive log current;
alter system archive log current;

Redo Transport und Apply stoppen.

dgmgrl sys/oracle@TISLI
EDIT DATABASE 'TISLI' SET STATE=TRANSPORT-OFF;
EDIT DATABASE 'iltis' SET STATE='APPLY-OFF';

Data Gurard Broker configuration entfernen

dgmgrl sys/oracle@TISLI
disable CONFIGURATION;
remove CONFIGURATION;

Standby aktivieren

Die Standby wird aktiviert und somit zu einer Primary umgewandelt. Dies wird natürlich auf der Standby-Seite gemacht.

sqlplus / as sysdba
select open_mode, database_role from v$database;
ALTER DATABASE ACTIVATE PHYSICAL STANDBY DATABASE;
select open_mode, database_role from v$database;
SHUTDOWN IMMEDIATE;
STARTUP;

*** Hinweis: Die folgenden Schritte sind auf der alten Primary nur notwendig, wenn diese weiter genutzt werden soll. Wie z.B. bei einer Testmigration oder eben im Falle eines Fallback-Szenario. Zur besseren Übersicht sind diese Schritte in () dargestellt.

(Alte Primary wieder zur Verfügung stellen)

(Auf alter Primary Parameter zurücksetzen)

alter system set dg_broker_start=false scope=both;
alter system reset FAL_SERVER;
alter system reset log_archive_config;

-- Also check if remote archive destination is set, LOG_ARCHIVE_DEST_n
alter system reset log_archive_dest_2 scope=both;
alter system reset log_archive_config scope=both;

(Auf alter Primary die Standby Logfiles droppen)

ALTER DATABASE DROP STANDBY LOGFILE GROUP 11;
ALTER DATABASE DROP STANDBY LOGFILE GROUP 12;
ALTER DATABASE DROP STANDBY LOGFILE GROUP 13;
ALTER DATABASE DROP STANDBY LOGFILE GROUP 14;

(User auf alter Primary wieder entsperren und Process Parameter zurücksetzen)

alter user YOUR_DB_USER account lock;
alter system set job_queue_processes=30 scope=both;
alter system set aq_tm_processes=1 scope=both;

Damit ist die alte Primary (wieder) Verfügbar.

Auf der neuen Primary Data Guard Parameter zurücksetzen

alter system set dg_broker_start=false scope=both;
alter system reset FAL_SERVER;
alter system reset log_archive_config;
alter system reset log_archive_config scope=both;

Auf der neuen Primary die Standby Logfiles droppen

ALTER DATABASE DROP STANDBY LOGFILE GROUP 11;
ALTER DATABASE DROP STANDBY LOGFILE GROUP 12;
ALTER DATABASE DROP STANDBY LOGFILE GROUP 13;
ALTER DATABASE DROP STANDBY LOGFILE GROUP 14;

Konvertieren der neuen Primary von non-CDB nach PDB

Folgende Schritte sind notwendig, um die neue Primary von einer non-CDB zu einer PDB Datenbank zu konvertieren.

Shutdown non-CDB und read only öffnen

select name, cdb, open_mode from v$database;
shutdown immediate;
startup mount;
alter database open read only;

DBMS_PDB.DESCRIBE ausführen

Das DBMS_PDB.DESCRIBE Package erzeugt ein xml-file der non-CDB, welches anschließend zur Prüfung der PDB-Kompatibilität und zur Erzeugung der neuen PDB verwendet wird.

BEGIN
DBMS_PDB.DESCRIBE(
pdb_descr_file => '/home/oracle/noneCDB.xml');
END;
/

Shutdown non-cdb database

shutdown immediate;

Kompatibilität des Describe-Files durchführen mit CHECK_PLUG_COMPATIBILITY

Das DBMS_PDB.CHECK_PLUG_COMPATIBILITY wird bereits dann in der CDB ausgeführt!

-- connect to CDB$ROOT and run the DBMS_PDB.CHECK_PLUG_COMPATIBILITY function 
-- to determine whether the non-CDB is compatible with the CDB

SET SERVEROUTPUT ON
DECLARE
compatible CONSTANT VARCHAR2(3) :=
CASE DBMS_PDB.CHECK_PLUG_COMPATIBILITY(
pdb_descr_file => '/home/oracle/noneCDB.xml',
pdb_name => 'ILTIS')
WHEN TRUE THEN 'YES'
ELSE 'NO'
END;
BEGIN
DBMS_OUTPUT.PUT_LINE(compatible);
END;
/

-- If the output is "YES", then the non-CDB is compatible, and you can continue with the next step.
-- Otherwise check v$PDB_PLUG_IN_VIOLATIONS, fix issues and start again with DBMS_PDB.DESCRIBE (when fixes made in nonCDB) oracle
-- with DBMS_PDB.CHECK_PLUG_COMPATIBILITY check when fixes made in new CDB($ROOT) database

Die PDB_PLUG_IN_VIOLATIONS prüfen und ggf. Fehler fixen

Die PDB_PLUG_IN_VIOLATIONS auf Fehler überprüfen und die Feher beheben. Warnings können ignoriert werden.

set lin 180
set pages 400
col NAME format a30
col MESSAGE format a50
col CAUSE format a20
--
select NAME, TYPE, CAUSE, MESSAGE, STATUS from PDB_PLUG_IN_VIOLATIONS WHERE status <> 'RESOLVED';

Nachdem die Fehler behoben sind, noch einmal dass DBMS_PDB.DESCRIBE ausführen und erneut einen CHECK_PLUG_COMPATIBILITY durchführen. Anschließend wieder die PDB_PLUG_IN_VIOLATIONS überprüfen.

Plug in non-CDB als PDB in die CDB

Die nonCDB als PDB in die CDB pluggen. Achtung bei größeren Datenbanken MOVE nehmen, sonst werden die Datenfiles kopiert.

CREATE PLUGGABLE DATABASE ILTIS USING '/home/oracle/ILTIS.xml' MOVE;

Die neue Pluggable Database ist dann im Mount Status und darf nicht geöffnet werden. Das kommt erst weiter unten!

Erneut PDB_PLUG_IN_VIOLATIONS prüfen

select NAME, TYPE, CAUSE, MESSAGE, STATUS from PDB_PLUG_IN_VIOLATIONS WHERE status <> 'RESOLVED'order by time desc;

nonCDB zu einer PDB konvertieren

Dieser Schritt ist das eigentliche Convert von einer non-CDB zu einer PDB.

alter session set container=ILTIS;
@$ORACLE_HOME/rdbms/admin/noncdb_to_pdb.sql

Status der neuen PDB prüfen

set lin 180
set pages 4000
--
select name, open_mode, restricted, open_time, inst_id, con_id, dbid, con_uid from gv$pdbs;

Die neue PDB zum ersten Mal read write öffnen

Die Plugable database das erste mal read write öffnen (wichtig!)

ALTER PLUGGABLE DATABASE OPEN;

Status der neuen PDB prüfen

set lin 180
set pages 4000
--
select name, open_mode, restricted, open_time, inst_id, con_id, dbid, con_uid from gv$pdbs;
select PDB_ID, PDB_NAME, STATUS, LOGGING from CDB_PDBS

Ein abschließdender Blick in das alert.log kann auch nicht schaden 🙂

Quellen