- physical standby: where the standby database contains exactly the same as the primary database, and normal recovery is performed using the archived redo stream of the primary database
- logical standby: where the standby database may deviate from the primary database, so no physical consistency exists. The redo stream of the primary database is mined for SQL statements which are applied transactionally on the standby database, making the standby database logically look the same as the primary database.
With setting up a standby environment we immediately are confronted with the big difference between the Standard Edition 2 (SE2) and Enterprise Edition (EE) flavors of the Oracle RDBMS. With EE we have a plethora of options, from manual to almost full automated setups and even with opening the standby database (in snapshot mode, or - in case the Active Data Guard license has been bought - read-write mode). SE2 doesn't have all this functionality. There it comes down to feeding archived redo to the standby database to have the standby database remain up to par with the primary database. This particular post will focus on the EE version of Oracle.
Preparations
Setting up a standby database environment first of all requires a machine where we can run such a standby database. As we already have created a VM called "primary" with a full stack of Oracle software installed it seems obvious that we use that VM as basis for the to-be-created "standby" VM. VirtualBox allows you to create a full clone of a VM using a few clicks. Follow along! The steps to clone involve:- Cleanly shut down the primary database VM. This is required to get a consistent state of both VMs.
- Next, right-click on the primary VM and choose "Clone..." from the popup menu:
- A new dialog will open where we'll enter the details of the clone VM. Note that I specifically choose to change the MAC addresses for all network adapters:
- Specify that we wish to have to full clone so the standby VM will have a completely independent lifespan of the primary VM: When pressing the "Finish" button the clone operation will commence:
Prerequisites for a physical standby database
So... what do we need for a physical standby database? Well, that's quite easy:- First we need a running instance on the standby side with of course the same database name set (governed through the DB_NAME instance parameter), but with a different unique name (DB_UNIQUE_NAME instance parameter).
- We also need a copy of the primary password file. If one doesn't exist at the primary site, then create one and copy that one to the standby site.
- Let's not forget the static listener entries en TNS aliases which we'll use for the databases to communicate with each other.
- In order to create a copy of your primary database you need a full database backup of the primary database, including a standby control file.
- We also need to set a bunch of instance parameters for proper file creation, file name conversion between primary and standby sites, etc.
creating the primary database
First create the primary database if it doesn't already exist. For this we'll use a response file named /u01/stage/cdb.rsp with the following contents:responseFileVersion=/oracle/assistants/rspfmt_dbca_response_schema_v12.2.0
gdbName=thehague.mydomain.com
sid=thehague
databaseConfigType=SI
RACOneNodeServiceName=
policyManaged=false
createServerPool=false
serverPoolName=
cardinality=
force=false
pqPoolName=
pqCardinality=
createAsContainerDatabase=true
numberOfPDBs=1
pdbName=mypdb1
useLocalUndoForPDBs=true
pdbAdminPassword=Welcome1
nodelist=
templateName=/u01/app/oracle/product/19.20/dbhome_1/assistants/dbca/templates/General_Purpose.dbc
sysPassword=oracle
systemPassword=oracle
serviceUserPassword=
emConfiguration=
emExpressPort=5500
runCVUChecks=FALSE
dbsnmpPassword=oracle
omsHost=
omsPort=0
emUser=
emPassword=
dvConfiguration=false
dvUserName=
dvUserPassword=
dvAccountManagerName=
dvAccountManagerPassword=
olsConfiguration=false
datafileJarLocation=/u01/app/oracle/product/19.20/dbhome_1/assistants/dbca/templates/
datafileDestination=/u01/oradata/thehague/
recoveryAreaDestination=/u01/fra/thehague/
storageType=FS
characterSet=AL32UTF8
nationalCharacterSet=AL16UTF16
registerWithDirService=false
walletPassword=
listeners=
variablesFile=
variables=ORACLE_BASE_HOME=/u01/app/oracle/product/19.20/dbhome_1,DB_UNIQUE_NAME=thehague,ORACLE_BASE=/u01/app/oracle,PDB_NAME=mypdb1,DB_NAME=thehague,ORACLE_HOME=/u01/app/oracle/product/19.20/dbhome_1,SID=thehague
initParams=audit_trail=none,audit_file_dest=/u01/app/oracle/admin/thehague/adump,compatible=19.0.0,db_block_size=8192,db_domain=mydomain.com,db_name=thehague,db_unique_name=thehague,nls_language=American,nls_territory=America,db_create_file_dest=/u01/oradata/thehague/,db_create_online_log_dest_1=/u01/oradata/thehague/,db_create_online_log_dest_2=/u01/fra/thehague/,db_recovery_file_dest=/u01/fra/thehague/,db_recovery_file_dest_size=8G,undo_tablespace=UNDOTBS1,open_cursors=300,processes=200,sga_target=4G,pga_aggregate_target=512M,diagnostic_dest=/u01/app/oracle,dispatchers=(PROTOCOL=TCP)(SERVICE=thehagueXDB),remote_login_passwordfile=EXCLUSIVE
sampleSchema=false
databaseType=MULTIPURPOSE
automaticMemoryManagement=false
Creation of a database is done by simply executing:
gdbName=thehague.mydomain.com
sid=thehague
databaseConfigType=SI
RACOneNodeServiceName=
policyManaged=false
createServerPool=false
serverPoolName=
cardinality=
force=false
pqPoolName=
pqCardinality=
createAsContainerDatabase=true
numberOfPDBs=1
pdbName=mypdb1
useLocalUndoForPDBs=true
pdbAdminPassword=Welcome1
nodelist=
templateName=/u01/app/oracle/product/19.20/dbhome_1/assistants/dbca/templates/General_Purpose.dbc
sysPassword=oracle
systemPassword=oracle
serviceUserPassword=
emConfiguration=
emExpressPort=5500
runCVUChecks=FALSE
dbsnmpPassword=oracle
omsHost=
omsPort=0
emUser=
emPassword=
dvConfiguration=false
dvUserName=
dvUserPassword=
dvAccountManagerName=
dvAccountManagerPassword=
olsConfiguration=false
datafileJarLocation=/u01/app/oracle/product/19.20/dbhome_1/assistants/dbca/templates/
datafileDestination=/u01/oradata/thehague/
recoveryAreaDestination=/u01/fra/thehague/
storageType=FS
characterSet=AL32UTF8
nationalCharacterSet=AL16UTF16
registerWithDirService=false
walletPassword=
listeners=
variablesFile=
variables=ORACLE_BASE_HOME=/u01/app/oracle/product/19.20/dbhome_1,DB_UNIQUE_NAME=thehague,ORACLE_BASE=/u01/app/oracle,PDB_NAME=mypdb1,DB_NAME=thehague,ORACLE_HOME=/u01/app/oracle/product/19.20/dbhome_1,SID=thehague
initParams=audit_trail=none,audit_file_dest=/u01/app/oracle/admin/thehague/adump,compatible=19.0.0,db_block_size=8192,db_domain=mydomain.com,db_name=thehague,db_unique_name=thehague,nls_language=American,nls_territory=America,db_create_file_dest=/u01/oradata/thehague/,db_create_online_log_dest_1=/u01/oradata/thehague/,db_create_online_log_dest_2=/u01/fra/thehague/,db_recovery_file_dest=/u01/fra/thehague/,db_recovery_file_dest_size=8G,undo_tablespace=UNDOTBS1,open_cursors=300,processes=200,sga_target=4G,pga_aggregate_target=512M,diagnostic_dest=/u01/app/oracle,dispatchers=(PROTOCOL=TCP)(SERVICE=thehagueXDB),remote_login_passwordfile=EXCLUSIVE
sampleSchema=false
databaseType=MULTIPURPOSE
automaticMemoryManagement=false
$ export ORACLE_HOME=/u01/app/oracle/product/19.20/dbhome_1
$ PATH=${ORACLE_HOME}/bin:${PATH}
$ dbca -silent -createDatabase -responseFile /u01/stage/cdb.rsp
After a little while we'll have a container database called "thehague" we can use for creating a standby. But... first we need to make sure to implement an important action that frequently is forgotten: the configuration of the primary database using similar parameters in case the roles between primary and standby databases are switched and the old primary database then becomes standby! So, on the primary side we need to perform the following actions:
$ PATH=${ORACLE_HOME}/bin:${PATH}
$ dbca -silent -createDatabase -responseFile /u01/stage/cdb.rsp
$ sqlplus "/ as sysdba"
SQL> ALTER SYSTEM SET db_file_name_convert='berlin','thehague','BERLIN','THEHAGUE' sid='*' scope=spfile;
SQL> ALTER SYSTEM SET log_file_name_convert='berlin','thehague','BERLIN','THEHAGUE' sid='*' scope=spfile;
SQL> ALTER SYSTEM SET standby_file_management=auto sid='*' scope=spfile;
SQL> SHUTDOWN IMMEDIATE;
Now start the database instance again for the above parameters to take effect, and at the same time make sure that ARCHIVELOG mode is enabled:
SQL> ALTER SYSTEM SET db_file_name_convert='berlin','thehague','BERLIN','THEHAGUE' sid='*' scope=spfile;
SQL> ALTER SYSTEM SET log_file_name_convert='berlin','thehague','BERLIN','THEHAGUE' sid='*' scope=spfile;
SQL> ALTER SYSTEM SET standby_file_management=auto sid='*' scope=spfile;
SQL> SHUTDOWN IMMEDIATE;
SQL> STARTUP MOUNT;
SQL> ALTER DATABASE ARCHIVELOG;
SQL> ALTER DATABASE OPEN;
SQL> EXIT
Now that the database is running in ARCHIVELOG mode, make sure we prepare it for a Data Guard setup:
SQL> ALTER DATABASE ARCHIVELOG;
SQL> ALTER DATABASE OPEN;
SQL> EXIT
$ rman target /
RMAN> CONFIGURE CONTROLFILE AUTOBACKUP on;
RMAN> CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY BACKED UP 1 TIMES TO DISK;
RMAN> EXIT
Ignore the RMAN-8591 message given.
RMAN> CONFIGURE CONTROLFILE AUTOBACKUP on;
RMAN> CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY BACKED UP 1 TIMES TO DISK;
RMAN> EXIT
Configuring the password file
When a password file doesn't exist already for the primary database, create one at the primary site using:$ orapwd file=${ORACLE_HOME}/dbs/orapwthehague
The password file (name is orapw${ORACLE_SID} and is residing in ${ORACLE_HOME}/dbs) should then be copied over to the standby.mydomain.com machine for use with the standby database:
$ scp ${ORACLE_HOME}/dbs/orapwthehague standby.mydomain.com:/u01/app/oracle/product/19.20/dbhome_1/dbs/orapwberlin
Configuring the network connectivity
For two databases to communicate with one another we have to set up TNS aliases so we can refer to logical names when we wish to access a particular database address. Next to that of course we need a listener configured which services incoming database requests. Let's start with the listener on the primary database side. Create the /u01/app/oracle/product/19.20/dbhome_1/network/admin/listener.ora file with the following contents:
LISTENER =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = tcp)(HOST = primary.mydomain.com)(PORT = 1521))
(ADDRESS = (PROTOCOL = ipc)(KEY = LSNR_PRIM))
)
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = thehague_dgb)
(SID_NAME = thehague)
(ORACLE_HOME = /u01/app/oracle/product/19.20/dbhome_1)
)
(SID_DESC =
(SID_NAME = thehague)
(ORACLE_HOME = /u01/app/oracle/product/19.20/dbhome_1)
)
)
After creation of the listener.ora, start the listener:
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = tcp)(HOST = primary.mydomain.com)(PORT = 1521))
(ADDRESS = (PROTOCOL = ipc)(KEY = LSNR_PRIM))
)
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = thehague_dgb)
(SID_NAME = thehague)
(ORACLE_HOME = /u01/app/oracle/product/19.20/dbhome_1)
)
(SID_DESC =
(SID_NAME = thehague)
(ORACLE_HOME = /u01/app/oracle/product/19.20/dbhome_1)
)
)
$ lsnrctl start
Similarly we need a /u01/app/oracle/product/19.20/dbhome_1/network/admin/listener.ora on the standby.mydomain.com machine:
LISTENER =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = tcp)(HOST = standby.mydomain.com)(PORT = 1521))
(ADDRESS = (PROTOCOL = ipc)(KEY = LSNR_STBY))
)
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = berlin_dgb)
(SID_NAME = berlin)
(ORACLE_HOME = /u01/app/oracle/product/19.20/dbhome_1)
)
(SID_DESC =
(SID_NAME = berlin)
(ORACLE_HOME = /u01/app/oracle/product/19.20/dbhome_1)
)
)
On the standby we need to start the listener after creation of the listener.ora as well:
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = tcp)(HOST = standby.mydomain.com)(PORT = 1521))
(ADDRESS = (PROTOCOL = ipc)(KEY = LSNR_STBY))
)
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = berlin_dgb)
(SID_NAME = berlin)
(ORACLE_HOME = /u01/app/oracle/product/19.20/dbhome_1)
)
(SID_DESC =
(SID_NAME = berlin)
(ORACLE_HOME = /u01/app/oracle/product/19.20/dbhome_1)
)
)
$ lsnrctl start
Next, we'll create a /u01/app/oracle/product/network/admin/tnsnames.ora file (as we don't have an LDAP server at hand) with 2 aliases per database:
- One for the redo transport. We'll call this alias <dbname>_dgt.
- One for instance management. We'll call this alias <dbname>_dgmgrl.
thehague_dgt =
(DESCRIPTION =
(ADDRESS =
(PROTOCOL = tcp)
(HOST = primary.mydomain.com)
(PORT = 1521)
)
(CONNECT_DATA =
(SERVICE_NAME = thehague)
(SERVER = dedicated)
)
)
thehague_dgmgrl =
(DESCRIPTION =
(ADDRESS =
(PROTOCOL = tcp)
(HOST = primary.mydomain.com)
(PORT = 1521)
)
(CONNECT_DATA =
(SERVICE_NAME = thehague)
(SERVER = dedicated)
(UR = A)
)
)
berlin_dgt =
(DESCRIPTION =
(ADDRESS =
(PROTOCOL = tcp)
(HOST = standby.mydomain.com)
(PORT = 1521)
)
(CONNECT_DATA =
(SERVICE_NAME = berlin)
(SERVER = dedicated)
)
)
berlin_dgmgrl =
(DESCRIPTION =
(ADDRESS =
(PROTOCOL = tcp)
(HOST = standby.mydomain.com)
(PORT = 1521)
)
(CONNECT_DATA =
(SERVICE_NAME = berlin)
(SERVER = dedicated)
(UR = A)
)
)
Create this file on the primary side and copy it over to the standby machine in the corresponding directory.
(DESCRIPTION =
(ADDRESS =
(PROTOCOL = tcp)
(HOST = primary.mydomain.com)
(PORT = 1521)
)
(CONNECT_DATA =
(SERVICE_NAME = thehague)
(SERVER = dedicated)
)
)
thehague_dgmgrl =
(DESCRIPTION =
(ADDRESS =
(PROTOCOL = tcp)
(HOST = primary.mydomain.com)
(PORT = 1521)
)
(CONNECT_DATA =
(SERVICE_NAME = thehague)
(SERVER = dedicated)
(UR = A)
)
)
berlin_dgt =
(DESCRIPTION =
(ADDRESS =
(PROTOCOL = tcp)
(HOST = standby.mydomain.com)
(PORT = 1521)
)
(CONNECT_DATA =
(SERVICE_NAME = berlin)
(SERVER = dedicated)
)
)
berlin_dgmgrl =
(DESCRIPTION =
(ADDRESS =
(PROTOCOL = tcp)
(HOST = standby.mydomain.com)
(PORT = 1521)
)
(CONNECT_DATA =
(SERVICE_NAME = berlin)
(SERVER = dedicated)
(UR = A)
)
)