La technologie Dataguard existe maintenant depuis plusieurs années pour offrir des serveurs de secours alimentés au fil de l’eau. J’ai récemment eu à le faire sur plusieurs bases du progiciel R3 de SAP avec peu de documentation ou alors obsolète. J’espère que ces notes vous aiderons à configurer rapidement un Dataguard pour des bases SAPSi vous êtes en train de lire ce billet, c’est que vous vous êtes rendus compte qu’à encapsuler les processus Oracle, SAP a réussi s’éloigner un peu du standard et des procédures que nous connaissons bien. Cela commence à l’installation:
- L’administrateur SAP ne connait pas le runInstaller, il va lancer son installeur SAP qui se terminera avec un base créée et alimentée pour un module de R3.
- Il ne pourra pas installer les binaires seuls, nous verrons que cela aura un impact plus tard.
- Contrairement à une installation oracle classique qui installe plusieurs moteurs sur une machine, qui à leurs tour hébergent plusieurs bases, ici nous avons une correspondance:
1 user linux = 1 oracle_home = 1 base
- R3 stocke le nom d’hôte dans les tables de sa base, il faut donc travailler avec un nom d’hôte virtuel pour la base (un peu comme le OUI_HOSTNAME)
- R3 répartit les fichiers de la base de données dans une grande arborescence de dossiers lorsque nous n’utilisons pas ASM
Scénario
- Nous avons 2 salles machines nommées jaune et rouge
- On y trouve les serveurs prdsapdbj01 et prdsapdbr01
- Le SID de la base est PR1, car ici nous travaillons avec le module retail
- Les bases seront respectivement pr1_rtj et pr1_rtr (prod retail jaune et rouge)
- Je déconseille TRES fortement de nommer une base ou un serveur de façon à décrire son rôle primaire ou secondaire (ex: orclp sur svrdbpri et orcls sur svrdbsby…)
Pré-requis
- Demander un nom d’hôte virtuel avec sa vip que nous attacherons au serveur primaire par une commande système (ifconfig): prdsaprtdb
- Sur le serveur secondaire, installer une base identique à la primaire, tous les fichiers de cette base seront supprimés ou écrasés ultérieurement.
- Le SID restera en majuscule, tous les autres db_name, global_db_name, service_name, etc. seront en minuscule.
Préparation de la base PR1
force_logging
sqlplus / as sysdba select FORCE_LOGGING from v$database; --- NO alter database force logging;
Paramètres de la base primaire
Ici plusieurs modifications en spfile, il faudra donc redémarrer l’instance à un moment:
alter system set db_file_name_convert='PR1','PR1' scope=spfile; alter system set db_unique_name='pr1_rtj' scope=spfile; alter system set fal_client='pr1_rtj'; alter system set fal_server='pr1_rtr'; alter system set log_archive_dest_1='LOCATION="/oracle/PR1/oraarch/PR1arch", valid_for=(online_logfile,all_roles)'; alter system set log_archive_dest_2='service=pr1_rtr lgwr async valid_for=(online_logfiles,primary_role) db_unique_name=pr1_rtr'; alter system set log_archive_config='DG_CONFIG=(pr1_rtj,pr1_rtr)'; alter system set local_listener='(ADDRESS=(PROTOCOL=TCP)(HOST=prdsapdbj)(PORT=1628))'; alter system set log_file_name_convert='PR1','PR1' scope=spfile; alter system set standby_file_management='AUTO';
Standby redo logs
Redo logs sur la secondaire dans lesquels la base primaire ira écrire les transactions au fil de l’eau. On les crée ici sur la primaire, ils seront reproduits sur la secondaire lors du rman duplicate.
- Vérifier la taille des redo logs actuels
select GROUP#,THREAD#,BYTES/1024/1024,STATUS from v$log; ---------- ---------- --------------- ---------------- 1 1 512 CURRENT 2 1 512 INACTIVE
Nous allons donc créer des standby redo logs de 512Mb, notons que l’installeur SAP crée 4 groupes de redologs multiplexés, pour les standby, nous rajouterons 2 autres groupes, également multiplexés.
ALTER DATABASE ADD STANDBY LOGFILE GROUP 5 ('/oracle/PR1/origlogA/sby_log_g11m1.dbf','/oracle/PR1/mirrlogA/sby_log_g11m2.dbf') SIZE 512M reuse; ALTER DATABASE ADD STANDBY LOGFILE GROUP 6 ('/oracle/PR1/origlogB/sby_log_g12m1.dbf','/oracle/PR1/mirrlogB/sby_log_g12m2.dbf') SIZE 512M reuse; ALTER DATABASE ADD STANDBY LOGFILE GROUP 7 ('/oracle/PR1/origlogA/sby_log_g13m1.dbf','/oracle/PR1/mirrlogA/sby_log_g13m2.dbf') SIZE 512M reuse; ALTER DATABASE ADD STANDBY LOGFILE GROUP 8 ('/oracle/PR1/origlogB/sby_log_g14m1.dbf','/oracle/PR1/mirrlogB/sby_log_g14m2.dbf') SIZE 512M reuse; ALTER DATABASE ADD STANDBY LOGFILE GROUP 9 ('/oracle/PR1/origlogA/sby_log_g15m1.dbf','/oracle/PR1/mirrlogA/sby_log_g15m2.dbf') SIZE 512M reuse; ALTER DATABASE ADD STANDBY LOGFILE GROUP 10 ('/oracle/PR1/origlogB/sby_log_g16m1.dbf','/oracle/PR1/mirrlogB/sby_log_g16m2.dbf') SIZE 512M reuse;
Modifier sqlnet.ora
Sur les 2 serveurs vérifier que le fichier $ORACLE_HOME/network/admin/sqlnet.ora contient:
NAMES.DIRECTORY_PATH=(TNSNAMES)
Modifications listener
Cette étape est une des 2 étapes les plus importantes dans la mise en œuvre d’un Dataguard:
- Arrêt du listener courant
lsnrctl stop
- Modification de $ORACLE_HOME/network/admin/listener.ora pour contenir 2 listeners:
- LISTENER un lié au nom d’hôte physique, ici sur le port 1628
- LISTENER_PR1 lié à la vip prdsaprtdb qui sera activé uniquement sur le serveur primaire sur le port d’origine ouvert lors de l’installation de la base PR1, ici 1528
ADMIN_RESTRICTIONS_LISTENER_PR1 = on LISTENER_PR1 = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC) (KEY = PR1.WORLD) ) (ADDRESS= (PROTOCOL = IPC) (KEY = PR1) ) (ADDRESS = (PROTOCOL = TCP) (COMMUNITY = SAP.WORLD) (HOST = prdsaprtdb) (PORT = 1528) ) ) SID_LIST_LISTENER_PR1 = (SID_LIST = (SID_DESC = (SID_NAME = PR1) (GLOBAL_DBNAME=pr1) (ORACLE_HOME = /oracle/PR1/112_64) ) (SID_DESC = (SID_NAME = PR1) (GLOBAL_DBNAME= pr1_DGMGRL) (ORACLE_HOME = /oracle/PR1/112_64) ) ) LISTENER = (ADDRESS = (PROTOCOL = TCP) (HOST = prdsapdbj01) (PORT = 1628) ) SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (SID_NAME = PR1) (GLOBAL_DBNAME=pr1) (ORACLE_HOME = /oracle/PR1/112_64) ) (SID_DESC = (SID_NAME = PR1) (GLOBAL_DBNAME= pr1_rtj_DGMGRL) (ORACLE_HOME = /oracle/PR1/112_64) ) ) STARTUP_WAIT_TIME_LISTENER = 0 CONNECT_TIMEOUT_LISTENER = 10 TRACE_LEVEL_LISTENER= OFF
(Re)démarrage des listeners du serveur primaire
lsnrctl start lsnrctl start LISTENER_PR1
Serveur secondaire, comme pour le serveur primaire, 2 listeners:
ADMIN_RESTRICTIONS_LISTENER_PR1 = on LISTENER_PR1 = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC) (KEY = PR1.WORLD) ) (ADDRESS= (PROTOCOL = IPC) (KEY = PR1) ) (ADDRESS = (PROTOCOL = TCP) (COMMUNITY = SAP.WORLD) (HOST = prdsaprtdb) (PORT = 1528) ) ) SID_LIST_LISTENER_PR1 = (SID_LIST = (SID_DESC = (SID_NAME = PR1) (GLOBAL_DBNAME=pr1) (ORACLE_HOME = /oracle/PR1/112_64) ) (SID_DESC = (SID_NAME = PR1) (GLOBAL_DBNAME= pr1_DGMGRL) (ORACLE_HOME = /oracle/PR1/112_64) ) ) LISTENER = (ADDRESS = (PROTOCOL = TCP) (HOST = prdsapdbr01) (PORT = 1628) ) SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (SID_NAME = PR1) (GLOBAL_DBNAME=pr1) (ORACLE_HOME = /oracle/PR1/112_64) ) (SID_DESC = (SID_NAME = PR1) (GLOBAL_DBNAME= pr1_rtr_DGMGRL) (ORACLE_HOME = /oracle/PR1/112_64) ) ) STARTUP_WAIT_TIME_LISTENER = 0 CONNECT_TIMEOUT_LISTENER = 10 TRACE_LEVEL_LISTENER= OFF
Démarrage des listeners secondaires, le listener_pr1 échouera car il tourne déjà sur le serveur primaire, là où est la vip:
lsnrctl start lsnrctl start LISTENER_PR1
Modification des tnsnames.ora
Il faut pouvoir adresser aussi bien la base primaire que la secondaire, il faut donc 2 entrées dans les tnsnames.ora:
- On garde l’alias WORLD sinon R3 n’aime pas
- On a 2 bases pr1_RetailJaune (pr1_rtj) et pr1_RetailRouge (pr1_rtr)
- Ces entrées servent à se connecter à une base non ouverte donc au listener statique et permanent sur le port alternatif, ici 1628
pr1_rtj.WORLD = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (COMMUNITY = SAP.WORLD) (PROTOCOL = TCP) (HOST = prdsapdbj01) (PORT = 1628) ) ) (CONNECT_DATA = (SID = PR1) (SERVICE_NAME = pr1) ) ) pr1_rtr.WORLD = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (COMMUNITY = SAP.WORLD) (PROTOCOL = TCP) (HOST = prdsapdbr01) (PORT = 1628) ) ) (CONNECT_DATA = (SID = PR1) (SERVICE_NAME= pr1) ) )
On peut coller ce même texte sur les deux serveurs. Chaque tnsnames.ora aura également une entrée pour le nom commun de la base utilisant la vip, qui aura été créé lors de l’installation de la base R3.
Fichiers de mots de passe
Copie fichier de mots de passe:
scp /oracle/PR1/11202/dbs/orapwPR1 orapr1@prdsapdbr01:/oracle/PR1/11202/dbs/orapwPR1
Fichier d’init
Sur prdsapdbr01, créer un fichier pfile temporaire:
echo "DB_NAME=PR1" > /oracle/PR1/11202/dbs/initPR1.ora
Au cas où il y ait un spfile pour PR1 sur ce même serveur, le supprimer.
Arborescence de stockage
L’arborescence des fichiers de la base de données est complexe et doit être recréée à l’identique sur le serveur de standby
Générer un script de création sur prdsapdbj01 en salle jaune:
ls -R /oracle/PR1/sapdata* | grep "^/" | sed 's/://' | sed 's/^/mkdir -p /' > make_dbdirs.sh
Copier le script sur prdsapdbr01 dans /oracle/PR1
Supprimer l’arborescence actuelle:
rm –rf /oracle/PR1/sapdata* find /oracle/PR1/ -name "*.dbf" -exec rm -f {} ;
Exécuter le script make_dbdirs.sh
Recréer les dossiers pour les controlfiles:
mkdir -p /oracle/PR1/origlogB/cntrl mkdir -p /oracle/PR1/origlogA/cntrl
Chargement instance secondaire
Test de connexion à l’instance secondaire
Histoire de vérifier qu’on n’a pas fait d’erreur dans la configuration Oracle Net (Listner + tns)
sqlplus sys@pr1_rtr as sysdba
Si ça marche, on passe au choses sérieuses en se connectant en ssh sur le serveur secondaire et en démarrant l’instance secondaire sur le pfile minimaliste créé un précédemment. Ne tentez pas de le faire par la connexion SQLNet depuis le serveur jaune, le succès sera illusoire…
sqlplus / as sysdba startup nomount pfile='?/dbs/initPR1.ora'
De nouveau sur le serveur primaire, on lance la double connexion RMAN à la base primaire ET à la base secondaire (auxiliary):
rman target / auxiliary sys@pr1_rtr
Ordre rman de duplication
Préparer ce bloc run dans un éditeur de texte à coté afin d’être bien sûr des valeurs, explications juste après:
run { allocate channel C1 type disk; allocate channel C2 type disk; allocate channel C3 type disk; allocate auxiliary channel SC1 type disk; DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE NOFILENAMECHECK spfile parameter_value_convert 'PR1','PR1','pr1_rtj','pr1_rtr' set db_file_name_convert='PR1','PR1' set db_unique_name='pr1_rtr' set fal_client='pr1_rtr' set fal_server='pr1_rtj' set local_listener='(ADDRESS=(PROTOCOL=TCP)(HOST=prdsapdbr01)(PORT=1628))' set log_archive_config='dg_config=(pr1_rtj,pr1_rtr)' set log_archive_dest_1='LOCATION="/oracle/PR1/oraarch/PR1arch" db_unique_name=pr1_rtr' set log_archive_dest_2='service=pr1_rtj LGWR async valid_for=(online_logfiles,primary_role) db_unique_name=pr1_rtj' set standby_file_management='AUTO' ; }
Quid:
"allocate channel"
De type disk même si l’on a une installation TSM ou autre ce sont des canaux ‘virtuels’, noter la nuance pour le seul canal dédié à la base de secours
DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE
L’ordre de duplication à la volée de la base primaire vers une secondaire, on est proche du concept du network import, car il n’y a pas de génération de fichiers de backup.
NOFILENAMECHECK
Sert à forcer les chemins de fichiers, sinon, on risque les fichiers au format OMF, beurk!
spfile
Les lignes qui suivent servent à générer le spfile de la base secondaire, comme si un alter system précédait chaque instruction set. Certains de ces paramètres sont le miroir de ceux positionnés sur la base primaire, pr1_rtj.
parameter_value_convert
Contient des paires de conversion entre apostrophes (quotes), pour une base RAC, on aurait aussi ‘SCAN_Rouge’,’SCAN_jaune’ par exemple
Lorsque la commande rman duplicate est terminée (donc les données ont été recopiées) il faut démarrer la pompe du coté de la secondaire. Donc on se connecte sur la base rouge en sql pour exécuter:
alter database recover managed standby database using current logfile disconnect;
Notre dataguard est maintenant actif, enfin devrait l’être et voici quelques requêtes à exécuter de chaque coté pour vérifier:
Tests
Status de la base
set lines 130 col SWITCHOVER_STATUS format a15 col LOG_MODE format a15 col OPEN_MODE format a15 col PROTECTION_MODE format a20 col DATABASE_ROLE format a18 col GUARD_STATUS format a15 col DB_UNIQUE_NAME format a15 select NAME,FORCE_LOGGING,LOG_MODE,OPEN_MODE,PROTECTION_MODE,DATABASE_ROLE, SWITCHOVER_STATUS, GUARD_STATUS,DB_UNIQUE_NAME from v$database;
Résultat sur la primaire:
NAME FOR LOG_MODE OPEN_MODE PROTECTION_MODE DATABASE_ROLE SWITCHOVER_STAT GUARD_STATUS --------- --- --------------- --------------- -------------------- ------------------ --------------- --------------- DB_UNIQUE_NAME --------------- PR1 YES ARCHIVELOG READ WRITE MAXIMUM PERFORMANCE PRIMARY TO STANDBY NONE pr1_rtj
Résultat sur la secondaire:
NAME FOR LOG_MODE OPEN_MODE PROTECTION_MODE DATABASE_ROLE SWITCHOVER_STAT GUARD_STATUS --------- --- --------------- --------------- -------------------- ------------------ --------------- --------------- DB_UNIQUE_NAME --------------- PR1 YES ARCHIVELOG MOUNTED MAXIMUM PERFORMANCE PHYSICAL STANDBY NOT ALLOWED NONE pr1_rtr
Transport des logs
La base secondaire est bien en PHYSICAL STANDBY et en état MOUNT, pour vérifier que les transactions sont bien transportées, utiliser la requête suivante:
set pages 30 select sequence#, to_char(FIRST_TIME,'DD-Mon-RR HH24:MI:SS') "First time", to_char(next_time,'DD-Mon-RR HH24:MI:SS') "Next time" from v$archived_log order by next_time;
Et des deux coté ont doit avoir le même numéro de séquence à environ la même heure:
SEQUENCE# First time Next time ---------- ------------------------ ------------------------ 1010 27-Jan-12 04:47:20 27-Jan-12 23:27:44 1011 27-Jan-12 23:27:44 28-Jan-12 04:49:31 1012 28-Jan-12 04:49:31 29-Jan-12 13:45:04 1013 29-Jan-12 13:45:04 29-Jan-12 15:06:39 1014 29-Jan-12 15:06:39 29-Jan-12 15:06:40 1015 29-Jan-12 15:06:40 30-Jan-12 04:48:44 1016 30-Jan-12 04:48:44 30-Jan-12 15:08:52 1017 30-Jan-12 15:08:52 31-Jan-12 04:46:15 1018 31-Jan-12 04:46:15 31-Jan-12 14:29:29 1019 31-Jan-12 14:29:29 31-Jan-12 21:12:09 1020 31-Jan-12 21:12:09 01-Feb-12 04:46:32
Faire un ‘alter system switch logfile’ sur la primaire et vérifier que la requête est incrémentée de chaque coté
Si la séquence (première colonne) n’est pas continue sur la base secondaire, c’est qu’il y a un trou: ‘gap’. Souvent l’archive log a déjà été purgé sur la primaire par un script de sauvegarde, il faudra le restaurer sur la primaire avec une commande RMAN pour qu’il soit immédiatement appliqué.
Par exemple, sur la secondairen, en SQL:
select * from v$archive_gap THREAD# LOW_SEQUENCE# HIGH_SEQUENCE# ---------- ------------- -------------- 1 30807 30819
Sur la primaire, connexion rman et, ici avec TSM:
run { allocate channel t1 type 'SBT_TAPE' parms 'ENV=(TDPO_OPTFILE=/opt/tivoli/tsm/client/oracle/bin64/tdpo.opt)' MAXOPENFILES=100; restore archivelog from sequence 30807 until sequence 30819 thread 1; }
Utilisation des Standby redologs
set lines 155 pages 9999 col thread# for 9999990 col sequence# for 999999990 col status format a10 col grp for 990 col fnm for a50 head "File Name" col "First SCN No" format 9999999999 col "First SCN Time" format a20 col "Last SCN Time" format a20 col archived format a8 select a.thread#,a.sequence#,a.group# grp,a.bytes/1024/1024 Size_MB,a.status,a.archived,a.first_change# "First SCN No" ,to_char(FIRST_TIME,'DD-Mon-RR HH24:MI:SS') "First SCN Time",to_char(LAST_TIME,'DD-Mon-RR HH24:MI:SS') "Last SCN Time" from v$standby_log a order by 1,2,3,4;
Vous devrez avoir un retour du genre:
THREAD# SEQUENCE# GRP SIZE_MB STATUS ARCHIVED First SCN No First SCN Time Last SCN Time -------- ---------- ---- ---------- ---------- -------- ------------ -------------------- -------------------- 0 0 8 512 UNASSIGNED YES 0 0 9 512 UNASSIGNED YES 0 0 10 512 UNASSIGNED YES 0 0 11 512 UNASSIGNED YES 0 0 12 512 UNASSIGNED YES 0 0 13 512 UNASSIGNED YES 0 0 14 512 UNASSIGNED YES 0 0 15 512 UNASSIGNED YES 0 0 16 512 UNASSIGNED YES 1 0 5 512 UNASSIGNED NO 1 0 7 512 UNASSIGNED NO 1 1021 6 512 ACTIVE YES 78914415 01-Feb-12 04:46:32 01-Feb-12 15:24:35
Post Configuration
Maintenant que notre Dataguard fonctionne nous allons ajouter les accessoires qui nous simplifieront la vie au moment d’une bascule
Configuration DGMGRL
Activation du DG Broker
Sur les 2 installations (primaires et secondaires) :
alter system set dg_broker_start=TRUE;
Connexion à DGMGRL, il faut connaître le mot de passe SYS
dgmgrl connect sys/$sysPwd
Configuration de DGMGRL
create configuration 'DGpr1' as primary database is 'pr1_rtj' connect identifier is pr1_rtj;
Ajout de la secondaire
add database 'pr1_rtr' as connect identifier is pr1_rtr; show configuration;
Vérification paramètres, et l’on constate une anomalie pour la base primaire pr1_rtj:
show database verbose 'pr1_rtj' … StandbyArchiveLocation = 'USE_DB_RECOVERY_FILE_DEST' show database verbose 'pr1_rtr'
Corriger pr1_rtj:
EDIT DATABASE 'pr1_rtj' SET PROPERTY 'StandbyArchiveLocation'='/oracle/PR1/oraarch/PR1arch'
Vérifier aussi le paramètre ‘StaticConnectIdentifier’ lié au local_listener
Activation
enable configuration;
Scripts
Pour aider les opérations de bascule, nous créerons 2 scripts par base, un pour le failover et l’autre pour le switchover. Les 2 scripts incorporeront des tests pour qu’ils ne puissent être lancés que sur une base secondaire, ce qui est logique en cas de failover de toutes façon. Ces scripts devront pouvoir lancer les commandes DGMGRL ainsi que faire les opérations système liées aux cartes réseau virtuelles, sur un serveur de base R3, ces scripts seront donc lancés par l’utilisateur root, ici le script de switchover de la base pr1 sur le serveur de la salle rouge:
changement de coté avec DGMGRL:
su - orapr1 -c "dgmgrl -silent sys/$sysPwd@pr1_rtr "switchover to 'pr1_rtr'""
Sortir du script principal si la bascule DGMGRL échoue, car dans ce cas la primaire reste là où elle est et il ne faut pas toucher aux vip!
rc=$? echo $rc if [ $rc -ne 0 ]; then echo "Failover/switchover a échoué" exit fi
Arrêt du listener sur l’ancienne primaire
ssh prdsapdbj01 "su - orapr1 -c "lsnrctl stop listener_pr1""
Faire tomber la vip sur l’ancien serveur primaire. Noter que l’alias est sur bond0:1, s’il y a plusieurs bases sur ce serveur, il faudra se fixer d’avoir base1 sur bond0:1, base2 sur bond0:2 etc.
ssh prdsapdbj01 "ifconfig bond0:1 down"
Activer la vip sur le nouveau serveur primaire
ifconfig bond0:1 192.168.25.121 netmask 255.255.252.0
Démarrage du listener sur la nouvelle base primaire
su - orapr1 -c "lsnrctl start listener_pr1"
Sauvegarde
Pensez à mettre en place des scripts de sauvegarde capable de s’adapter automatiquement au statut de la base: sur la primaire il doivent sauvegarder et sur la secondaire ils doivent se contenter de purger les archive logs, ceci sans avoir à modifier un paramètre de l’ordonnanceur, du crontab ou du script
Epilogue
Notre base PR1 est maintenant en Dataguard pouvant être basculée à tout moment grâce aux scripts exploitant les commandes DGMGRL et les commandes root.
Lors de nos tests la bascule prenait moins de 10s et les utilisateurs de l’application ne remarquaient pas la manœuvre à moins qu’ils ne fassent une requête au moment de la bascule.
Les serveurs de bases de données peuvent héberger plusieurs bases qui doivent pouvoir être basculées indépendamment, pour répartir la charge par exemple.
En cas de bascule par failover à cause d’une perte d’accès réseau au datacenter jaune, il est alors probable que les serveurs qui s’y trouvent tournent encore ignorant le fait qu’ils soient coupés du monde. Dans ce cas très sensible il faudra veiller à ce que les services offerts par ces serveurs ne soient plus accessible lorsque l’accès réseau à ceux-ci sera rétabli. Et en cas de failover, il faudra reconstruire la nouvelle base secondaire, mais là c’est un autre article ;o)