Oracle et les PDBs, épisode II : l'attaque des clones

Il y a bien longtemps, dans une galaxie lointaine, très lointaine...

Vous vous souvenez sûrement de l'article sur les pluggables databases d'Oracle où je présentais via un cas concret comment les copies de PDBs peuvent nous faciliter la vie.

Aujourd'hui, nous allons voir comment effectuer des copies instantanées de PDB via les snapshots du filesystem BTRFS.

En effet, le besoin de notre client ayant évolué, il souhaitait avoir deux bases de DEV rafraîchies tous les jours par la production. Comme ce client est en "Standard Edition 2", il n'était pas possible d'avoir plusieurs PDBs dans la même instance. Nous avons donc créé une seconde instance.

Pour rappel, voici le schéma d'architecture :

PDBcopy.png

Voici comment se déroulait le rafraîchissement sur la plateforme de DEV jusqu'à présent :

côté standby :

  • Mise en pause du process de recover des archivelogs sur la standby
  • Copie sur le montage NFS du filer via lien 10Gb/s de l’ensemble des datafiles de la PDB
  • Génération du fichier de description xml de la PDB sur le montage NFS du filer
  • Réactivation du process de recover des archivelogs de la standby

côté serveur de DEV :

  • Si le fichier xml présent sur le montage NFS du filer a moins de 20h (en cas de reprise en journée), suppression de la PDB actuelle et de ses datafiles
  • Recopie des datafiles de la PDB de production depuis le montage NFS du filer vers les disques locaux
  • Création de la PDB à partir du fichier de description xml de la production présent sur le montage NFS du filer

C'est cette partie que l'on va faire évoluer. Comme notre filesystem est de type BTRFS, nous allons créer un subvolume pour la PDB avant la copie des données
et nous allons ensuite effectuer un clone de ce subvolume via la commande btrfs subvolume snapshot. Ce clone devient le subvolume de la PDB de la seconde instance.

Le script ressemble désormais à ça :
A noter : le script de cet article est fourni à titre indicatif et ne doit pas être utilisé tel quel.


#!/bin/bash
#
# Plug the production PDB in this environment
#

# check user
if [ "$USER" != "oracle" ]
then
echo -e "\nPlease execute this script as oracle user.\n"
exit 1
fi

# export variables
export ORACLE_HOME=/u01/app/oracle/product/12.1/dbhome_1
export PATH=${PATH}:${ORACLE_HOME}/bin
export ORACLE_SID1=ORADEV1
export ORACLE_SID2=ORADEV2
export ORACLE_SID=${ORACLE_SID1}
export ORAENV_ASK=NO
. /usr/local/bin/oraenv

export PDB_NAME=MYPDB
export PDB_USER=PDBUSER
export PDB_PASS=pass1
export PDB_PASS2=pass2
export ORADB_ROOT_PATH=/u01/app/oracle/oradata
export PDBS_ROOT_PATH=${ORADB_ROOT_PATH}/${ORACLE_SID1}
export ORA2_PDBS_ROOT_PATH=${ORADB_ROOT_PATH}/${ORACLE_SID2}

export NASPATH=/nas/backup/Standby_PDB
export XMLPATH=${NASPATH}/mypdb.xml
export SRC_PDB_NAME=MYPDB
export SRC_PDBS_ROOT_PATH=/u01/app/oracle/oradata/ORAPROD
export NEWPDBGUID=fakeguid
export refreshDEV2=y

# check PDB files freshness
DATAFRESHNESS=$((($(date +%s)-$(date -r ${XMLPATH} +%s))/3600))

if [ ${DATAFRESHNESS} -gt 20 ]
then
echo "Standby PDB files are too old, re-run the copy_PDB.sh script on the standby server."
exit 1
fi

# Get GUID of the futur PDB
NEWPDBGUID=$(/usr/bin/xml2 < ${XMLPATH} | grep "/PDB/guid=" | cut -d "=" -f2)

for ORACLE_DB in ${ORACLE_SID1} ${ORACLE_SID2}
do
export ORACLE_SID=${ORACLE_DB}

# check if oracle instance is running
check_stat=`ps -ef|grep ${ORACLE_SID}|grep pmon|wc -l`;
oracle_num=`expr $check_stat`
if [ $oracle_num -lt 1 ]
then
echo -e "\nERROR : The oracle instance ${ORACLE_SID} is not running !\n"
if [ "${ORACLE_SID}" == "${ORACLE_SID1}" ]
then
exit 1
else
refreshDEV2=n
fi
else
# Get GUID of the actual PDB
PDBGUID=$(sqlplus -s /nolog << EOF
conn / as sysdba
set pagesize 0 feedback off verify off heading off echo off;
select guid from v\$pdbs where name = '${PDB_NAME}';
exit
EOF
)

#check if the PDB GUID is a 32 hexadecimal characters
if ! [[ ${PDBGUID} =~ ^[0-9A-F]{32}$ ]]
then
echo -e "\nERROR : The PDB GUID ${PDBGUID} is not valid !\n"
exit 1
else
# drop the PDB
sqlplus -s /nolog << EOF
conn / as sysdba
alter PLUGGABLE DATABASE ${PDB_NAME} close immediate;
drop PLUGGABLE DATABASE ${PDB_NAME} including datafiles;
shutdown immediate;
exit
EOF
sleep 3
sqlplus -s /nolog << EOF
conn / as sysdba
startup;
exit
EOF
sleep 3
# drop the PDB's btrfs subvolume
sudo /usr/sbin/btrfs subvolume list "${ORADB_ROOT_PATH}/${ORACLE_SID}/${PDBGUID}" && sudo /usr/sbin/btrfs subvolume delete "${ORADB_ROOT_PATH}/${ORACLE_SID}/${PDBGUID}"
fi
fi
done

# create the PDB's btrfs subvolume for the first instance
sudo /usr/sbin/btrfs subvolume list "${PDBS_ROOT_PATH}/${NEWPDBGUID}" || sudo /usr/sbin/btrfs subvolume create "${PDBS_ROOT_PATH}/${NEWPDBGUID}"
sudo /usr/bin/chown oracle:oinstall "${PDBS_ROOT_PATH}/${NEWPDBGUID}"

# copy all datafiles from the filer for the first instance
cp -r ${NASPATH}/${SRC_PDB_NAME}/${NEWPDBGUID}/* ${PDBS_ROOT_PATH}/${NEWPDBGUID}/

# clone the btrfs subvolume for the second Oracle instance
if [ ${refreshDEV2} == "y" ]
then
sudo /usr/sbin/btrfs subvolume snapshot "${PDBS_ROOT_PATH}/${NEWPDBGUID}" "${ORA2_PDBS_ROOT_PATH}/${NEWPDBGUID}"
sudo /usr/bin/chown oracle:oinstall "${ORA2_PDBS_ROOT_PATH}/${NEWPDBGUID}"
fi

# create the PDB on first instance ( wich is registred on the standard 1521 listener port )
export ORACLE_SID=${ORACLE_SID1}
sqlplus -s /nolog << EOF
conn / as sysdba
create pluggable database ${PDB_NAME} using '${XMLPATH}' SOURCE_FILE_NAME_CONVERT=('${SRC_PDBS_ROOT_PATH}','${PDBS_ROOT_PATH}') nocopy tempfile reuse;
alter pluggable database ${PDB_NAME} open;
exit
EOF

# set the password of this environment in the pdb of the first instance
sqlplus -s /nolog << EOF
conn / as sysdba
alter session set container = ${PDB_NAME};
alter user ${PDB_USER} identified by "${PDB_PASS}";
alter user ${PDB_USER} account unlock;
exit
EOF

# create the PDB on second instance wich is registred on a dedicated listener (port 1522)
if [ ${refreshDEV2} == "y" ]
then
export ORACLE_SID=${ORACLE_SID2}
sqlplus -s /nolog << EOF
conn / as sysdba
create pluggable database ${PDB_NAME} using '${XMLPATH}' SOURCE_FILE_NAME_CONVERT=('${SRC_PDBS_ROOT_PATH}','${ORA2_PDBS_ROOT_PATH}') nocopy tempfile reuse;
alter pluggable database ${PDB_NAME} open;
exit
EOF

# set the password of this environment in the pdb of the second instance
sqlplus -s /nolog << EOF
conn / as sysdba
alter session set container = ${PDB_NAME};
alter user ${PDB_USER} identified by "${PDB_PASS2}";
alter user ${PDB_USER} account unlock;
exit
EOF
fi

Et voilà !
La création de la PDB de la deuxième instance se fait en quelques secondes grâce au clone BTRFS et comme peu de données sont modifiées dans chacune des PDBs des instances ORADEV1 et ORADEV2, le gain en espace disque est lui aussi considérable !

Alors, allez-vous passer du Côté Obscur de la Force ?