GoldenGate Discard file into Table

Facebook
Twitter
LinkedIn
Email

GoldenGate Discard file into Table : Ease of troubleshooting & Maintenance of discard History

In General, we would prefer to execute a simple query to identify why the replicat process has encountered an ABEND.
How can this be achieved ?
Just load the GoldenGate Discard file into Table of Database…..!!

GoldenGate discard file is a file that contains information about data that was unable to be applied during the data replication process. This can occur due to a variety of reasons, such as data type mismatches, primary key violations, or other data integrity issues. The discard file provides details on the specific data that was unable to be replicated, including the table name, row data, and error message. By reviewing the discard file, you can identify and troubleshoot issues with the data replication process.

With OCI GoldenGate’s UI functionality, you can only view the last 10 discards, However in some situation we might need to store these discards in a table for longer time and for future analysis, it is much more convenient to access the information this way instead of relying on the UI/CURL/Discard files stored on Disks.

To overcome and make our life simple, I have built a custom shell script to read the discard files and load the limited contents in to the database for quick troubleshooting.

Sample Architecture

GoldenGate Discard File Into Table

Prerequisites

  • Running OCI GoldenGate Service / MSA GoldenGate on VM
  • Adminclient installed on Virtual Machine that has access to OCI GoldenGate process. Download Adminclient from here.
  • SQLPLUS Client has to be installed on VM.
  • Bash & Shell capabilities.
  • Target Database to store the information of Discard files.
DDL for Table
				
					CREATE TABLE GGADMIN.DISCARD_TABLE
  (ID   VARCHAR2 (32) NOT NULL PRIMARY KEY,
   TIME_STAMP VARCHAR2 (32),
   REPLICAT_NAME VARCHAR2 (32),
   DML_OPERATION VARCHAR2 (32),
   TABLE_INFO CLOB,
   ABEND_ERROR CLOB,
   FULL_DISCARD CLOB);
				
			
Shell Script
USER=”ggadmin” —> GGADMIN Database username
PWD=”Password_123″ —> GGADMIN Database password
DBNAME=”atpdb_high” —> Connection string connection for Database. I am using Autonomous Database here
CURLUSER=”oggadmin”  —> OCI GoldenGate username
CURLPASS=”Password_123″ —> This is the password to login to Goldengate
OCIGG_URL=”https://xyz123456.deployment.goldengate.us-ashburn-1.oci.oraclecloud.com” —>  FQDN URL / IP Address
FILEPATH=”/DATA/OCIGG_FILES” —>  Path to store the discard files in VM
REPNAME=”RPSWING” —> Add multiple replicat name with space
set TNS_ADMIN in the script
Logic :
  • Script spools out 2 discard files from CURL command i.e. n & n-1 discard files.
  • Script checks the data has already been inserted into the table of database.
  • If the data exist in the table it will ignore, if not it will insert the record.
  • Key Columns of Comparison : TIME_STAMP, REPLICAT_NAME , DML_OPERATION
  • You can create UNIQUE Index for all these 3 columns.
				
					#!/bin/bash
USER="ggadmin"
PWD="Password_123"
DBNAME="atpdb_high"
CURLUSER="oggadmin"
CURLPASS="Password_123"
OCIGG_URL="https://xyz123456.deployment.goldengate.us-ashburn-1.oci.oraclecloud.com"
FILEPATH="/DATA/OCIGG_FILES"
REPNAME="RPSWING RPHIST"
dt=`date +\%Y-\%m-\%d-\%H-\%M-\%S`

##################################
export HOME=/home/opc
ORACLE_HOME=`cat $HOME/.bash_profile|grep $ORACLE_HOME|cut -d= -f2 | head -1`
export ORACLE_HOME
export TNS_ADMIN=$ORACLE_HOME/network/admin/ATP
###################################


if [[ -z "$ORACLE_HOME" ]]; then
    echo "ORACLE_HOME path needs to be set in .bash_profile" 1>&2
    exit 1
fi

if [[ -z "$FILEPATH" ]]; then
    echo "FILEPATH needs to be set in the script" 1>&2
    exit 1
fi

if [[ -z "$CURLUSER" ]]; then
    echo "CURLUSER needs to be set in the script" 1>&2
    exit 1
fi

if [[ -z "$OCIGG_URL" ]]; then
    echo "OCIGG_URL needs to be set in the script" 1>&2
    exit 1
fi

if [[ -z "$REPNAME" ]]; then
    echo "REPLICAT NAME needs to be set in the script" 1>&2
    exit 1
fi

PATH=$PATH:$ORACLE_HOME/bin

for p in $REPNAME
do
curl -s -u $CURLUSER:$CURLPASS -X  GET $OCIGG_URL/services/v2/replicats/$p/info/reports/$p.dsc | jq > $FILEPATH/$p.out
#echo $FILEPATH/$p.out
curl -s -u $CURLUSER:$CURLPASS -X  GET $OCIGG_URL/services/v2/replicats/$p/info/reports/"$p"0.dsc | jq > $FILEPATH/"$p"0.out
#echo $FILEPATH/"$p"0.out
done



RNAME=`awk '/lines/{y=1;next}y' $p.out | grep -oP '(?<=group )\w+'`
TABLE_INFO=`awk '/lines/{y=1;next}y' $p.out | grep "Problem replicating" | sed 's/\"//g' | sed 's/\,//g'`
ABEND_ERROR=`awk '/lines/{y=1;next}y' $p.out | grep Discarded | sed 's/\"//g' | sed 's/\,//g'`
FULL_DISCARD=`awk '/lines/{y=1;next}y' $p.out`
TIME_STAMP=`awk '/lines/{y=1;next}y' $p.out | grep -oP '(?<=opened: ).*' | tr -d ',"'`
#echo $TIME_STAMP


if awk '/lines/{y=1;next}y' $p.out | grep -oh "\w*update\w*" ; then
    DML_OP=UPDATE
elif
    awk '/lines/{y=1;next}y' $p.out | grep -oh "\w*delete\w*"; then
    DML_OP=DELETE
elif
    awk '/lines/{y=1;next}y' $p.out | grep -oh "\w*insert\w*"; then
    DML_OP=INSERT
fi


DB_VALUES=`$ORACLE_HOME/bin/sqlplus -s /nolog <<EOF
connect $USER/$PWD@$DBNAME
set pagesize 200
set linesize 121
set heading off feedback off verify off
SET TERMOUT OFF
select REPLICAT_NAME, TIME_STAMP, UPPER(DML_OPERATION) from GGADMIN.DISCARD_TABLE order by TIME_STAMP desc fetch first 1 row only;
exit
EOF`

DB_RNAME="`echo $DB_VALUES|awk '{ print $1}'`"
DB_TIME_STAMP="`echo $DB_VALUES|awk '{ print $2" " $3}'`"
DB_DML_OP="`echo $DB_VALUES|awk '{ print $4}'`"


if [[ -z "$TABLE_INFO" ]]; then
    REPDISCFILE0=0
    for p in $REPNAME
        do

                REPDISCFILE0=`awk '/lines/{y=1;next}y' "$p"0.out | grep -E "Mapping problem"`
                  if [[ -z "$REPDISCFILE0" ]]; then
                    exit 0
                  else
                        C_RNAME=`awk '/lines/{y=1;next}y' "$p"0.out | grep -oP '(?<=group )\w+'`
                        C_TABLE_INFO=`awk '/lines/{y=1;next}y' "$p"0.out | grep "Problem replicating" | sed 's/\"//g' | sed 's/\,//g'`
                        C_ABEND_ERROR=`awk '/lines/{y=1;next}y' "$p"0.out | grep Discarded | sed 's/\"//g' | sed 's/\,//g'`
                        C_FULL_DISCARD=`awk '/lines/{y=1;next}y' "$p"0.out`
                        C_TIME_STAMP=`awk '/lines/{y=1;next}y' "$p"0.out | grep -oP '(?<=opened: ).*' | tr -d ',"'`
                            if awk '/lines/{y=1;next}y' "$p"0.out | grep -oh "\w*update\w*" ; then
                                 C_DML_OP=UPDATE
                               elif
                              awk '/lines/{y=1;next}y' "$p"0.out | grep -oh "\w*delete\w*"; then
                                 C_DML_OP=DELETE
                               elif
                               awk '/lines/{y=1;next}y' "$p"0.out | grep -oh "\w*insert\w*"; then
                                 C_DML_OP=INSERT
                             fi
                fi
        if [ "$C_RNAME" == "$DB_RNAME" ] && [ "$C_TIME_STAMP" == "$DB_TIME_STAMP" ] && [ "$C_DML_OP" == "$DB_DML_OP" ]; then
        exit 0
         else
                $ORACLE_HOME/bin/sqlplus -s /nolog <<EOF
                connect $USER/$PWD@$DBNAME
                set pagesize 200
                set linesize 121
                set heading off feedback off verify off
                SET TERMOUT OFF
                insert into GGADMIN.DISCARD_TABLE values (SYS_GUID(), ('$C_TIME_STAMP'), ('$C_RNAME'), ('$C_DML_OP'), ('$C_TABLE_INFO'), ('$C_ABEND_ERROR'), ('$C_FULL_DISCARD'));
                commit;
                exit 1;
EOF
        fi
done
fi

if [[ -n "${TABLE_INFO}" ]]; then
    if [ "$RNAME" == "$DB_RNAME" ] && [ "$DB_DML_OP" == "$DML_OP" ] && [ "$TIME_STAMP" == "$DB_TIME_STAMP" ];
   then
   # echo "NOT RUNNING"
    exit 1;
else
echo "INSERTING"
$ORACLE_HOME/bin/sqlplus -s /nolog <<EOF
connect $USER/$PWD@$DBNAME
set pagesize 200
set linesize 121
set heading off feedback off verify off
SET TERMOUT OFF
insert into GGADMIN.DISCARD_TABLE values (SYS_GUID(), ('$TIME_STAMP'), ('$RNAME'), ('$DML_OP'), ('$TABLE_INFO'), ('$ABEND_ERROR'), ('$FULL_DISCARD'));
commit;
exit
EOF
fi
fi

				
			
Execution :
./Discard_Table.sh

Recommendation is to set up a cron job to execute this script every 2 or 5 minutes once.
Crontab Example : */5* * * * * /scripts/Discard_Table.sh >> /dev/null
OUTPUT
select * from GGADMIN.DISCARD_TABLE;  
Discard Output
Note : There is always scope for improvement in this code, and it can be customized further depending on your environment. If you encounter any issues with the script, please reach out to me through the comment section, and I will endeavor to improve and fix it.

More information about the discard files can be found here.
Hope you found this article useful.
Disclaimer: The views expressed on this document are my own and do not necessarily reflect the views of Oracle..