Monday, July 6, 2020

Configuring Statspack for Standby Database

Statspack provide separate set of scripts for setting it up for a standby database in a data guard configuration. If enterprise edition is used with diagnostic pack then Remote Management Framework could be configured to get AWR reports of the standby. Depending on the data guard configuration (multiple standbys, snapshot data needed only from read only instances etc) the statspack setup would result in less effort and complexity than setting up RMF for AWR.
RMF has database link from both primary to standby and vice versa. Before a role transition additional work must be done by way of creating database link between future primary and future standby. In contrast the statspack only create one database link between primary and standby. As long as the TNS entry used for creating the DB link is available in the future primary then the statspack would continue to function. This is especially useful in a configuration where only subset of instances are candidate to become primary and all others are part of a reader farm.
In a multi-tenant architecture the data guard works on CDB level. As such the standby statspack is installed on the root container. This will create two users perfstat and stdbyperf. To accomplish this the statspack and standby statspack must be installed using catcon.pl script (Refer 2020285.1). The setup of standby consists of running spcreate.sql (normal statspack) and sbcreate.sql (standby statspack) scripts.
A script defining the statspack setup parameters could be used to automate the statspack creation. Output below shows content of a such script, which defines tablespace to create the statspack related tables and the password for perfstat user among other things.
cat sp.sql
define default_tablespace='STATSPACKTBS'
define temporary_tablespace='TEMP'
define perfstat_password='asanga123'
@?/rdbms/admin/spcreate.sql;
@/home/oracle/statpack/chng.sql;
The chng.sql contains the additional work must be done after setting up statspcak. This include issues mentioned in 382993.1 and 2437142.1. Content of this files is shown below.
cat chng.sql
alter table perfstat.stats$mutex_sleep disable constraint STATS$MUTEX_SLEEP_PK;
create index perfstat.STATS$MUTEX_SLEEP_PK on STATS$MUTEX_SLEEP(SNAP_ID,DBID,INSTANCE_NUMBER,MUTEX_TYPE,LOCATION);
insert into stats$idle_event select name from v$event_name where wait_class='Idle' 
minus select event from stats$idle_event;
If perl binary out of ORACLE_HOME is not in the path then following error will occur when running catcon.pl
perl $ORACLE_HOME/rdbms/admin/catcon.pl -u sys -s -e -c 'CDB$ROOT' -n 1 -b st_spcreate sp.sql
Can't locate Term/ReadKey.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 . /opt/cx/app/oracle/product/19.x.0/dbhome_1/rdbms/admin/) at /opt/cx/app/oracle/product/19.x.0/dbhome_1/rdbms/admin//catcon.pm line 497.
BEGIN failed--compilation aborted at /opt/cx/app/oracle/product/19.x.0/dbhome_1/rdbms/admin//catcon.pm line 497.
Compilation failed in require at /opt/cx/app/oracle/product/19.x.0/dbhome_1/rdbms/admin/catcon.pl line 165.
BEGIN failed--compilation aborted at /opt/cx/app/oracle/product/19.x.0/dbhome_1/rdbms/admin/catcon.pl line 165.
To rectify this issue put perl binary out of OH into the path and run the catcon.pl specifying sp.sql script.
export PATH=$ORACLE_HOME/perl/bin:$PATH
$ which perl
/opt/cx/app/oracle/product/19.x.0/dbhome_1/perl/bin/perl

perl $ORACLE_HOME/rdbms/admin/catcon.pl -u sys -s -e -c 'CDB$ROOT' -n 1 -b st_spcreate sp.sql


Next step is to create the standby statspack. A similar script could be created for that as well. The most important parameter in this case is the TNS alias which will be used to create the database link that will connect to the standby.
cat sb.sql
define default_tablespace='STATSPACKTBS'
define temporary_tablespace='TEMP'
define stdbyuser_password='asanga321'
define perfstat_password='asanga123'
define key='y'
define tns_alias='ppdbxdb5tns'
@?/rdbms/admin/sbcreate.sql;
@/home/oracle/statpack/sbchng.sql;
The sbchng.sql has additional work must be done on standby statspack. As stdbyperf user doesn't have access to v$event_name the idle event related workaround must be done after the setup. Also the STATS$MUTEX_SLEEP on standby statspack doesn't have DBID or instance number columns.
SQL> desc stdbyperf.stats$mutex_sleep
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SNAP_ID                                   NOT NULL NUMBER
 DB_UNIQUE_NAME                            NOT NULL VARCHAR2(30)
 INSTANCE_NAME                             NOT NULL VARCHAR2(16)
 MUTEX_TYPE                                NOT NULL VARCHAR2(32)
 LOCATION                                  NOT NULL VARCHAR2(40)
 SLEEPS                                             NUMBER
 WAIT_TIME                                          NUMBER
Instead db_unique_name and instance_name must be used
cat sbchng.sql
alter table stdbyperf.stats$mutex_sleep disable constraint STATS$MUTEX_SLEEP_PK;
CREATE INDEX stdbyperf.STATS$MUTEX_SLEEP_PK ON stdbyperf.STATS$MUTEX_SLEEP(SNAP_ID,DB_UNIQUE_NAME,INSTANCE_NAME,MUTEX_TYPE, LOCATION);
Run the standby statspack creation with
perl $ORACLE_HOME/rdbms/admin/catcon.pl -u sys -s -e -c 'CDB$ROOT' -n 1 -b st_sbcreate sb.sql
Once standby statspack is created run as sys the following to update the idle events in stdbyperf user schema.
insert into stdbyperf.stats$idle_event select event from perfstat.stats$idle_event minus select event from stdbyperf.stats$idle_event;
Additional standby instaces could be added to the standby statspack by using sbaddins.sql. This must be run using stdbyperf and must specify the TNS entry to the standby db. A script similar to below could be used for accomplishing this. As all the standby database instnaces use same set of tables no need to do the additional work on the subsequent standby instances.
cat sbaddin.sql
connect stdbyperf/asanga321
define key='y'
define tns_alias='ppdbxdb6tns'
define perfstat_password='asanga123'
@?/rdbms/admin/sbaddins.sql;

perl $ORACLE_HOME/rdbms/admin/catcon.pl -u sys -s -e -c 'CDB$ROOT' -n 1 -b st_sbaddins sbaddin.sql
Login as stdbyperf and run the below to view the current statspack standby configuration. This only shows the standby instances added to statspack. Not the actual data guard configuation.
select * from stats$standby_config ;

DB_UNIQUE_NAME                 INST_NAME        DB_LINK                          PACKAGE_NAME
------------------------------ ---------------- -------------------------------- ----------------------------
ppdbxdb5                       ppdbxdb5         STDBY_LINK_ppdbxdb5tns           STATSPACK_ppdbxdb5_ppdbxdb5
ppdbxdb6                       ppdbxdb6         STDBY_LINK_ppdbxdb6tns           STATSPACK_ppdbxdb6_ppdbxdb6


As mentioned earlie the standby statspack uses common set of tables to store data from all the standby instances. Only unique objects created for each standby instance are the database link and PL/SQL package which has the name STATSPACK_<db_unique_name>_<instance_name>. The corresponding package for the particular standby instance must be used in order to work with the instance. This inlcuding taking snapshots, changing statspack parameters and etc. For example in orde to change the snap level on ppdbxdb5 should run the STATSPACK_PPdbxDB5_PPdbxDB5 package. Similarly to change snap level on ppdbxdb6 should use STATSPACK_PPdbxDB6_PPdbxDB6. Below output shows chaning snap level on ppdbxdb6.
exec STATSPACK_PPdbxDB6_PPdbxDB6.modify_statspack_parameter(i_snap_level => 7, i_modify_parameter=>'true');

select DB_UNIQUE_NAME,INSTANCE_NAME,snap_level from stats$statspack_parameter;

DB_UNIQUE_NAME                 INSTANCE_NAME    SNAP_LEVEL
------------------------------ ---------------- ----------
ppdbxdb5                       ppdbxdb5                  7
ppdbxdb6                       ppdbxdb6                  7
Standby statspack use a common sequence to create snap id. As such when multiple instances takes snapshots there will be gaps between two consecutive snap ids.
Manual snapshots could be taken by running STATSPACK_<db_unique_name>_<instance_name>.snap. Snapshot taking is not automated and has to be done manually. Scheduling it in a dbms_scheduler job would allow the snapshot taking to work even after swichover in a reader farm configuration mentioned at the begining of the post.
The user stdbyperf doesn't have permission to create scheduler job. Grant create job and execute on dbms_scheduler (if revoked from public) to stdbyperf. Could create one scheduler job for each standby or have a single wrapper procedure with snap call for each standby. Below shows two separate scheduler job for automating snapshot taking.
--ppdbxdb5                       

BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'STANDBY_STATSPACK_PPdbxDB5',
job_type => 'STORED_PROCEDURE',
job_action => 'STATSPACK_PPdbxDB5_PPdbxDB5.snap',
start_date => TO_TIMESTAMP('00' ,'MI'),
repeat_interval => 'FREQ=HOURLY',
enabled => TRUE);
END;
/

--ppdbxdb6
                       
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'STANDBY_STATSPACK_PPdbxDB6',
job_type => 'STORED_PROCEDURE',
job_action => 'STATSPACK_PPdbxDB6_PPdbxDB6.snap',
start_date => TO_TIMESTAMP('01' ,'MI'),
repeat_interval => 'FREQ=HOURLY',
enabled => TRUE);
END;
/
When having multiple scheduler jobs are invoked at the same time it is possible to run into the following error.
2020-06-29T18:02:49.768983+00:00
Errors in file /opt/cx/app/oracle/diag/rdbms/ppdbxdb1/ppdbxdb1/trace/ppdbxdb1_j001_119494.trc:
ORA-12012: error on auto execute of job "STDBYPERF"."STANDBY_STATSPACK_PPdbxDB5"
ORA-02049: timeout: distributed transaction waiting for lock
ORA-06512: at "STDBYPERF.STATSPACK_PPdbxDB5_PPdbxDB5", line 3909
ORA-06512: at "STDBYPERF.STATSPACK_PPdbxDB5_PPdbxDB5", line 5486
ORA-06512: at "STDBYPERF.STATSPACK_PPdbxDB5_PPdbxDB5", line 101
ORA-06512: at line 1
2020-06-29T18:06:57.404823+00:00
To eliminate this have a time gap between two schedulers. Above schedulers are scheduled 1 minute apart.
Create standby statspack report by calling the sbreport. This will prompt to select the db_unique_name and instance_name and prompt the range of snap ids to create the statspack report.
SQL> @?/rdbms/admin/sbreport

Instances in this Statspack schema
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DB Unique Name                 Instance Name
------------------------------ ----------------
ppdbxdb5                       ppdbxdb5
ppdbxdb6                       ppdbxdb6

Enter the DATABASE UNIQUE NAME of the standby database to report
Enter value for db_unique_name: ppdbxdb5
You entered: ppdbxdb5

Enter the INSTANCE NAME of the standby database instance to report
Enter value for inst_name: ppdbxdb5
You entered: ppdbxdb5


Specify the number of days of snapshots to choose from
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Entering the number of days (n) will result in the most recent
(n) days of snapshots being listed.  Pressing  without
specifying a number lists all completed snapshots.

Listing all Completed Snapshots
                                          Snap
Instance       Snap Id   Snap Started    Level Comment
------------ --------- ----------------- ----- --------------------
ppdbxdb5            14 30 Jun 2020 00:00     7
                    16 30 Jun 2020 01:00     7
                    18 30 Jun 2020 02:00     7
                    20 30 Jun 2020 03:00     7
                    22 30 Jun 2020 04:00     7
Standby statspack report will have a separate section on standby recovery
Recovery Progress Stats  DB/Inst: ppdbxdb5/ppdbxdb5  End Snap: 32
-> End Snapshot Time: 30-Jun-20 09:00:04
-> ordered by Recovery Start Time desc, Units, Item asc

Recovery Start Time Item                       Sofar Units   Redo Timestamp
------------------- ----------------- -------------- ------- ------------------
15-Jun-20 15:18:07  Log Files                 11,360 Files
15-Jun-20 15:18:07  Active Apply Rate            659 KB/sec
15-Jun-20 15:18:07  Average Apply Rat             12 KB/sec
15-Jun-20 15:18:07  Maximum Apply Rat         13,280 KB/sec
15-Jun-20 15:18:07  Redo Applied              14,936 Megabyt
15-Jun-20 15:18:07  Recovery ID                    0 RCVID
15-Jun-20 15:18:07  Last Applied Redo              0 SCN+Tim 30-Jun-20 09:00:15
15-Jun-20 15:18:07  Active Time               29,277 Seconds
15-Jun-20 15:18:07  Apply Time per Lo              1 Seconds
15-Jun-20 15:18:07  Checkpoint Time p              0 Seconds
15-Jun-20 15:18:07  Elapsed Time           1,273,511 Seconds
15-Jun-20 15:18:07  Standby Apply Lag              2 Seconds
          -------------------------------------------------------------
Purging of snapshots must also be done calling the purge procedure of the relavent standby instance package. Following procedure delete snapshots after 32 days of creation.
create table deleted_snaps(delete_time timestamp,
min_snapid number, 
max_snapid number, 
DB_UNIQUE_NAME VARCHAR2(30), 
INSTANCE_NAME VARCHAR2(16), primary key (DB_UNIQUE_NAME,INSTANCE_NAME,delete_time));

create or replace
Procedure Delete_Snaps As
min_snap_id stats$snapshot.snap_id%type;
max_snap_id stats$snapshot.snap_id%type;
db_unq_name stats$snapshot.DB_UNIQUE_NAME%type;
ins_name stats$snapshot.INSTANCE_NAME%type;
snapshots_purged pls_integer;
stm varchar2(500);
cursor delsnaps is select DB_UNIQUE_NAME,INSTANCE_NAME,min(snap_id) as min,
max(snap_id) as max from stats$snapshot 
where to_char(snap_time,'YYYY-MM-DD') = to_char(sysdate-32,'YYYY-MM-DD') 
group by DB_UNIQUE_NAME,INSTANCE_NAME;
begin
open delsnaps;
loop
fetch delsnaps into db_unq_name,ins_name,min_snap_id,max_snap_id;
Exit When Delsnaps%Notfound;
stm := 'begin STATSPACK_'||db_unq_name||'_'||ins_name||'.purge(i_begin_snap =>'||min_snap_id||', i_end_snap =>'|| max_snap_id||', i_snap_range  => true, i_extended_purge  => true, I_DB_UNIQUE_NAME  => '''||db_unq_name||''', I_INSTANCE_NAME => '''||ins_name||'''); end;';
dbms_output.put_line(stm);
execute immediate stm;
commit;
Insert Into Deleted_Snaps values(Systimestamp,Min_Snap_Id,Max_Snap_Id,db_unq_name,ins_name);  
end loop;
Close Delsnaps;
commit;
End;
/
Schedule the deletion of snapshots.
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'Delete_Snaps_job',
job_type => 'STORED_PROCEDURE',
job_action => 'Delete_Snaps',
start_date => trunc(SYSDATE + 1,'DD') + 15/1444,
repeat_interval => 'FREQ=DAILY',
enabled => TRUE);
END;
/
To remove a standby instance from statspack configuration use $ORACLE_HOME/rdbms/admin/sbdelins.sql.

To drop standby statspack and statspack from the database use catcon.pl.
#remove standby statspack
perl $ORACLE_HOME/rdbms/admin/catcon.pl -u sys -s -e -c 'CDB$ROOT' -n 1 -b st_sbdrop  $ORACLE_HOME/rdbms/admin/sbdrop.sql

# remove statspack
perl $ORACLE_HOME/rdbms/admin/catcon.pl -u sys -s -e -c 'CDB$ROOT' -n 1 -b st_spdrop  $ORACLE_HOME/rdbms/admin/spdrop.sql
Useful metalink notes
STATSPACK:Idle Wait Events Missing in STATS$IDLE_EVENT [ID 2673657.1]
STATSPACK REPORTS SHOW "IDLE" DATA GUARD WAIT EVENTS IN TOP 5 IN 12.2 [ID 2305287.1]
12.2 or later STATSPACK: Idle Wait Event Such as 'Data Guard: Timer' is Erroneously Included in Top 5 Timed Events [ID 2437142.1]
New Idle Events are Erroneously Listed in STATSPACK Report [ID 1998538.1]
STATSPACK:Idle Wait Events Missing in STATS$IDLE_EVENT [ID 2673657.1]

Related Posts
Statspack setup in Brief
AWR Reports on Standby when Active Data Guard is Used