Multi-Master Oracle Replication Using GoldenGate

Andy Kerber (@dbakerber), Senior Consultant

In this four part series, I will provide an example of how to set up replication using Oracle GoldenGate. In part one, we will set up Multi-Master replication between two Oracle databases. In part two, we will add one way replication to MySQL. In part three, we will add one way replication to PostgreSQL. In part four, we will add a third master database, Oracle version 11.2.0.1, where we cannot use integrated replicat and extact.

On the Oracle side we will use integrated replicat and extract in the Oracle 12.1.0.2.

This blog post should be read closely. We move from one environment to the other frequently and have color coded the sections accordingly.

Note: In the Oracle Multi-Master setup, I include only basic conflict resolution steps. Conflict detection and resolution is entire area of discussion of its own, which we will not discuss in depth here.

In addition, I am not going to go into great detail for the Oracle GoldenGate installation, as you can run the installer and follow the steps. You will also need to run the GoldenGate shell software (ggsci) from the installation directory. The Oracle database LD_LIBRARY_PATH will need to be in your path statement to run Oracle GoldenGate.

In this discussion, the variable $GGH points to the GoldenGate Home directory on the Oracle database servers.

Oracle Environment:

Oracle Enterprise Edition x86_64 on Linux. 12.1.0.2, Sid ggdemo on server ggvm (initial target server)

Oracle Enterprise Edition x86_64 on Linux, 12.1.0.2 Sid ggtest on server pgorcl (initial source server)

Replicated source schema: andy

Replicated destination schema: andy

Oracle GoldenGate Version: 12c

Oracle GoldenGate schema: GGADMIN

Normally the ggsci prompt will contain the server name and database sid, and a line number in addition to ggsci and will look something like this:

GGSCI (pgorcl.localdomain as ggadmin@ggtest) 19>

If you are not logged into the database, it will just have the server name and line number:

GGSCI (pgorcl.localdomain) 2>

For the purposes of this document, we are just using GGSCI @ server>

Any time the command ‘edit params <name>’ is entered, GGSCI> will open a text editor. Add the configuration lines that follow the command, and then save and exit the file.

Replication Setup

Set up 1-Way Replication and Verify

1. Configure Integrated Extract on the Source DB

The first step after installing GoldenGate in each environment is to start ggsci and create the subdirectories.

./ggsci

GGSCI >create subdirs

 

Run the above command on all Oracle and MySQL servers.

Configure the port settings by editing the params file for the manager and adding the line to set the port number in ggsci. Do this on all servers.

GGSCI @pgorcl > edit params mgr

port 7809

GGSCI @pgorcl> start mgr

 

GGSCI @ggvm > edit params mgr

port 7809

GGSCI @ggvm > start mgr

 

Next, set up your globals file in both environments.

GGSCI@pgorcl> Edit params ./GLOBALS

CHECKPOINTTABLE ggcheckpoint

 

GGSCI@ggvm> Edit params ./GLOBALS

CHECKPOINTTABLE ggcheckpoint

 

After editing the global parameters, make sure that the lines are in a file called GLOBALS in the $GGH directory. I have run into issues where it was created as globals.prm in the params directory. But, the settings need to be in the GLOBALS file in the $GGH directory.

Configure your database environment. Be sure that each database is configured in tnsnames.ora on both servers. Run these commands in each Oracle database (in SQLPLUS). Run them from $GGH, the SQL files reside there:

 SQLPLUS>alter database add supplemental log data
 SQLPLUS>alter system set enable_goldengate_replication=true scope=both;
 SQLPLUS>create tablespace ggs_data datafile '/u01/app/oracle/oradata/ggdemo/ggs_data01.dbf' size 1024m autoextend on;
 SQLPLUS>create user ggadmin identified by ggadmin default tablespace ggs_data temporary tablespace temp;
 SQLPLUS>grant connect,resource,create session, alter session to ggadmin;
 SQLPLUS>grant select any dictionary, select any table,create table to ggadmin;
 SQLPLUS>grant alter any table to ggadmin;
 SQLPLUS>grant execute on utl_file to ggadmin;
 SQLPLUS>grant flashback any table to ggadmin;
 SQLPLUS>grant execute on dbms_flashback to ggadmin;
 SQLPLUS>grant insert,update,delete on target.tcustmer to ggadmin;
 SQLPLUS>grant insert,update,delete on target.tcustord to ggadmin;
 SQLPLUS>@marker_setup.sql
 SQLPLUS>@ddl_setup.sql
 SQLPLUS>@role_setup.sql
 SQLPLUS>@ddl_enable.sql
 SQLPLUS>@sequence.sql
 SQLPLUS>grant connect, resource, create session to andy;

 

Note that the last line is not strictly necessary, but it does make debugging much easier.

Log into each database through GGSCI and create the checkpoint table. This is only required in the destination database, but since we are going to set up bi-directional replication, we will go ahead and create it in both environments.

GGSCI @pgorcl > dblogin userid ggadmin
 Password:
 Successfully logged into database.
 GGSCI@pgorcl > add checkpointtable ggadmin.ggcheckpoint

 

GGSCI @ggvm > dblogin userid ggadmin
 Password:
 Successfully logged into database.
 GGSCI@ggvm > add checkpointtable ggadmin.ggcheckpoint

 

In the source environment only, add the transaction data:

GGSCI@pgorcl > add trandata andy.* cols *

 

Find the current scn from sqlplus:

SQLPLUS> column current_scn format 99999999999

SQLPLUS> select current_scn from v$database;

 

Use datapump to export the data and import it into the destination (target) database:

SQLPLUS> create directory dumpdir as '/home/oracle/dump' ;

oracle@pgorcl$ expdp system/password directory=dumpdir full=y schemas=scott parallel=4 dumpfile=ora112_%u.dmp flashback_scn=&SCN_FROM_ABOVE

scp ora112*.dmp ggvm:/home/oracle/dump

 

(on ggvm)

sqlplus / as sysdba

SQLPLUS> create directory dumpdir as '/home/oracle/dump' ;

impdp@ggvm impdp system/password directory=dumpdir dumpfile=ora112_%u.dmp  parallel=4

 

In ggsci, on the source server, configure the combined extract process:

GGSCI@pgorcl > dblogin userid ggadmin
 Password:
 Successfully logged into database.

GGSCI@pgorcl > register extract insrc database

GGSCI@pgorcl > add extract insrc, integrated tranlog, begin now

GGSCI@pgorcl > edit params insrc

extract insrc
 SETENV(ORACLE_SID="ggtest")
 SETENV(ORACLE_HOME="/u01/app/oracle/product/12.1.0/dbhome_1")
 USERID ggadmin, PASSWORD ggadmin
 TRANLOGOPTIONS IntegratedParams (max_sga_size 256)
 EXTTRAIL ./dirdat/in
 LOGALLSUPCOLS
 UPDATERECORDFORMAT COMPACT
 TABLE andy.*;

GGSCI@pgorcl > add exttrail ./dirdat/in, extract insrc, megabytes 10

 

Create the pump extract process:

GGSCI@pgorcl > add extract pumpint, exttrailsource ./dirdat/in

GGSCI@pgorcl > edit params pumpint

EXTRACT pumpint
 RMTHOST ggvm, MGRPORT 7809
 RMTTRAIL ./dirdat/pn
 TABLE andy.*;

GGSCI@pgorcl > add rmttrail ./dirdat/pn, extract pumpint, megabutes 10

GGSCI@pgorcl > start extract intsrc

GGSCI@pgorcl > start extract pumpint

 

Tail the ggserr.log file on the source server in the GoldenGate home directory ($GGH). Watch for any errors, and resolve as required. If there are no errors, the extract process should be configured properly.

2. Configure Integrated Replicat on the Destination DB

Connect to the destination database server and start ggsci:

./ggsci

GGSCI@ggvm> edit params repintan
 replicat repintan
 SETENV(ORACLE_SID='ggdemo')
 DBOPTIONS INTEGRATEDPARAMS(parallelism 4)
 AssumeTargetDefs
 DiscardFile ./dirrpt/rpdw.dsc, purge
 USERID ggadmin, PASSWORD ggadmin
 MAP andy.*, target andy.*;

GGSCI@ggvm> dblogin userid ggadmin

password:

GGSCI@ggvm> add replicat repintan integrated exttrail ./dirdat/pn

GGSCI@ggvm> start replicat repintan

 

To show all processes are running, enter info all:

GGSCI@ggvm> info all

Program     Status     Group       Lag at Chkpt Time Since Chkpt

MANAGER     RUNNING

REPLICAT   RUNNING     REPINTAN   00:00:00     00:00:39
(on source server)

 

GGSCI@pgorcl> info all

Program     Status     Group       Lag at Chkpt Time Since Chkpt

MANAGER     RUNNING
 EXTRACT     RUNNING     INTEXTAN    00:00:07      00:00:07
 EXTRACT     RUNNING     PUMPINT     00:00:00      00:00:08

 

Now in SQLPLUS, run an dml statement to verify that replication is working.

SQLPLUJS@GGTEST> select count(1) from andy.emp;

COUNT(1)
 ----------
 13

 

SQLPLUS@GGDEMO> select count(1) from andy.emp;

COUNT(1)
 ----------
 13

 

SQLPLUJS@GGTEST>

 

delete from andy.emp where empno=7900;

1 row deleted.

SQLPLUJS@GGTEST> commit;
 Commit complete.

 

SQLPLUS@GGDEMO> select count(1) from andy.emp;

COUNT(1)
 ----------
 12

 

One way replication is now working.

3. Make the required changes for bi-directional replication

On source database, for each table:

SQLPLUS> alter table andy.emp add (last_updt_dt_tm timestamp);

 

Add last_updt_dt_tm to each table being replicated for purposes of collision avoidance. Because we are replicating DDL, the column is also added to the destination tables.

After adding the column, create triggers for each table like this:

create or replace trigger andy.updt_emp
before update on andy.emp
for each row
begin
:new.last_updt_dt_tm:=systimestamp;
end;
/

 

Note that GoldenGate will automatically suppress triggers for replicated objects.

On both source and target database:

SQLPLUS>    CREATE TABLE "GGADMIN"."EXCEPTIONS"
 (    "EXCP_DATE" TIMESTAMP (6) DEFAULT systimestamp,
 "REP_NAME" VARCHAR2(10),
 "TABLE_NAME" VARCHAR2(56),
 "ERRNO" NUMBER,
 "ERRTYPE" VARCHAR2(6),
 "OPTYPE" VARCHAR2(24),
 "TRANSIND" VARCHAR2(12),
 "TRANSIMGIND" VARCHAR2(8),
 "COMMITTIMESTAMP" VARCHAR2(26),
 "RECCSN" NUMBER,
 "RECSEQNO" NUMBER,
 "RECRBA" NUMBER,
 "RECTRANSPOS" NUMBER,
 "RECLENGTH" NUMBER,
 "LOGRBA" NUMBER,
 "LOGPOSITION" NUMBER,
 "GROUPTYPE" VARCHAR2(12),
 "FILENAME" VARCHAR2(50),
 "FILENO" NUMBER,
 "SRCROWID" VARCHAR2(40),
 "SRCDBCHARSET" VARCHAR2(40),
 "REPLAG" NUMBER,
 "CNT_CDR_CONFLICTS" NUMBER,
 "CNT_CDR_RESOLUTIONS" NUMBER,
 "CNT_CDR_FAILED" NUMBER
 ) SEGMENT CREATION IMMEDIATE
 PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
 NOCOMPRESS LOGGING
 STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
 TABLESPACE "GGS_DATA" ;

 

On target server:

GGGSCI@ggvm> edit params inextan2

extract inextan2
 USERID ggadmin, PASSWORD ggadmin
 TRANLOGOPTIONS IntegratedParams (max_sga_size 256)
 EXTTRAIL ./dirdat/in
 LOGALLSUPCOLS
 UPDATERECORDFORMAT COMPACT
 DDL INCLUDE MAPPED OBJNAME ANDY.*
 TABLE ANDY.*;

GGSCI@ggvm> dblogin userid ggadmin
 Passsword:

GGSCI@ggvm> add trandata andy.* cols *

GGSCI@ggvm> register extract inextan2 database

GGSCI@ggvm> add extract inextan2, integrated tranlog, begin now

GGSCI@ggvm> add exttrail ./dirdat/in, extract inextan2, megabytes 10

GGSCI@ggvm> edit params pumpint2

EXTRACT pumpint2
 RMTHOST pgorcl, MGRPORT 7809
 RMTTRAIL ./dirdat/pn
 TABLE ANDY.*;

GGSCI@ggvm> add rmttrail ./dirdata/pn, extract pumpint2, megabytes 10

GGSCI@ggvm> start extract pumpint2

GGSCI@ggvm> start extract inextan2

 

On source server add the replicat:

GGSCI@pgorcl> edit params rpintan2

replicat rpintan2
 SETENV(ORACLE_SID='ggtest')
 DBOPTIONS INTEGRATEDPARAMS(parallelism 4)
 AssumeTargetDefs
 DiscardFile ./dirrpt/rpdw.dsc, purge
 USERID ggadmin, PASSWORD ggadmin
 DDLERROR DEFAULT DISCARD IGNOREMISSINGOBJECTS
 REPERROR (DEFAULT, EXCEPTION)
 MAP ANDY.*, target ANDY.*
 RESOLVECONFLICT (UPDATEROWEXISTS, (DEFAULT, USEMAX (last_updt_dt_tm))),
 RESOLVECONFLICT (DELETEROWEXISTS, (DEFAULT, OVERWRITE)),
 RESOLVECONFLICT (INSERTROWEXISTS, (DEFAULT, USEMAX (last_updt_dt_tm))),
 RESOLVECONFLICT (UPDATEROWMISSING, (DEFAULT, OVERWRITE)),
 RESOLVECONFLICT (DELETEROWMISSING, (DEFAULT, DISCARD)),
 MAPEXCEPTION
 (TARGET ggadmin.EXCEPTIONS, EXCEPTIONSONLY, INSERTALLRECORDS, COLMAP
 (
 excp_date = @DATENOW(),
 rep_name = @GETENV ('GGENVIRONMENT', 'GROUPNAME'),
 table_name = @GETENV ('GGHEADER', 'TABLENAME'),
 errno = @GETENV ('LASTERR', 'DBERRNUM'),
 errtype = @GETENV ('LASTERR', 'ERRTYPE'),
 optype = @GETENV ('LASTERR', 'OPTYPE'),
 transind = @GETENV ('GGHEADER', 'TRANSACTIONINDICATOR'),
 transimgind = @GETENV ('GGHEADER', 'BEFOREAFTERINDICATOR'),
 committimestamp = @GETENV ('GGHEADER', 'COMMITTIMESTAMP'),
 reccsn = @GETENV ('TRANSACTION', 'CSN'),
 recseqno = @GETENV ('RECORD', 'FILESEQNO'),
 recrba = @GETENV ('RECORD', 'FILERBA'),
 rectranspos = @GETENV ('RECORD', 'RSN'),
 reclength = @GETENV ('GGHEADAER', 'RECORDLENGTH'),
 logrba = @GETENV ('GGHEADER', 'LOGRBA'),
 logposition = @GETENV ('GGHEADER', 'LOGPOSITION'),
 grouptype = @GETENV ('GGENVIRONMENT', 'GROUPTYPE'),
 filename = @GETENV ('GGFILEHEADER', 'FILENAME'),
 fileno = @GETENV ('GGFILEHEADER', 'FILESEQNO'),
 srcrowid = @GETENV ('TRANSACTION', 'CSN'),
 srcdbcharset = @GETENV ('GGFILEHEADER', 'DBCHARSET'),
 replag = @GETENV ('LAG', 'SEC'),
 cnt_cdr_conflicts = @GETENV ('DELTASTATS','TABLE', @GETENV ('GGHEADER', 'TABLENAME'),'CDR_CONFLICTS'), cnt_cdr_resolutions = @GETENV ('DELTASTATS','TABLE', @GETENV ('GGHEADER', 'TABLENAME'),'CDR_RESOLUTIONS_SUCCEEDED'),
 cnt_cdr_failed = @GETENV ('DELTASTATS','TABLE', @GETENV ('GGHEADER', 'TABLENAME'),'CDR_RESOLUTIONS_FAILED')
 )
 );

GGSCI@pgorcl> dblogin userid ggadmin
 password

GGSCI@pgorcl> add replicat rpintan2 integrated exttrail ./dirdat/pn

GGSCI@pgorcl> start replicat rpintan2

 

On target server, add basic conflict detection and resolution:

edit params REPINTAN

replicat repintan
 SETENV(ORACLE_SID='ggdemo')
 DBOPTIONS INTEGRATEDPARAMS(parallelism 4)
 AssumeTargetDefs
 DiscardFile ./dirrpt/rpdw.dsc, purge
 USERID ggadmin, PASSWORD ggadmin
 DDLERROR DEFAULT DISCARD IGNOREMISSINGOBJECTS
 REPERROR (DEFAULT, EXCEPTION)
 MAP ANDY.*, target ANDY.*
 RESOLVECONFLICT (UPDATEROWEXISTS, (DEFAULT, USEMAX (last_updt_dt_tm))),
 RESOLVECONFLICT (INSERTROWEXISTS, (DEFAULT, USEMAX (last_updt_dt_tm))),
 RESOLVECONFLICT (DELETEROWEXISTS, (DEFAULT, OVERWRITE)),
 RESOLVECONFLICT (UPDATEROWMISSING, (DEFAULT, OVERWRITE)),
 RESOLVECONFLICT (DELETEROWMISSING, (DEFAULT, DISCARD)),
 MAPEXCEPTION
 (TARGET ggadmin.EXCEPTIONS, EXCEPTIONSONLY, INSERTALLRECORDS, COLMAP
 (
 excp_date = @DATENOW(),
 rep_name = @GETENV ('GGENVIRONMENT', 'GROUPNAME'),
 table_name = @GETENV ('GGHEADER', 'TABLENAME'),
 errno = @GETENV ('LASTERR', 'DBERRNUM'),
 errtype = @GETENV ('LASTERR', 'ERRTYPE'),
 optype = @GETENV ('LASTERR', 'OPTYPE'),
 transind = @GETENV ('GGHEADER', 'TRANSACTIONINDICATOR'),
 transimgind = @GETENV ('GGHEADER', 'BEFOREAFTERINDICATOR'),
 committimestamp = @GETENV ('GGHEADER', 'COMMITTIMESTAMP'),
 reccsn = @GETENV ('TRANSACTION', 'CSN'),
 recseqno = @GETENV ('RECORD', 'FILESEQNO'),
 recrba = @GETENV ('RECORD', 'FILERBA'),
 rectranspos = @GETENV ('RECORD', 'RSN'),
 reclength = @GETENV ('GGHEADAER', 'RECORDLENGTH'),
 logrba = @GETENV ('GGHEADER', 'LOGRBA'),
 logposition = @GETENV ('GGHEADER', 'LOGPOSITION'),
 grouptype = @GETENV ('GGENVIRONMENT', 'GROUPTYPE'),
 filename = @GETENV ('GGFILEHEADER', 'FILENAME'),
 fileno = @GETENV ('GGFILEHEADER', 'FILESEQNO'),
 srcrowid = @GETENV ('TRANSACTION', 'CSN'),
 srcdbcharset = @GETENV ('GGFILEHEADER', 'DBCHARSET'),
 replag = @GETENV ('LAG', 'SEC'),
 cnt_cdr_conflicts = @GETENV ('DELTASTATS','TABLE', @GETENV ('GGHEADER', 'TABLENAME'),'CDR_CONFLICTS'), cnt_cdr_resolutions = @GETENV ('DELTASTATS','TABLE', @GETENV ('GGHEADER', 'TABLENAME'),'CDR_RESOLUTIONS_SUCCEEDED'),
 cnt_cdr_failed = @GETENV ('DELTASTATS','TABLE', @GETENV ('GGHEADER', 'TABLENAME'),'CDR_RESOLUTIONS_FAILED')
 )
 );

 

Stop and start the replicat:

GGSCI@ggvm> stop replicat repintan

GGSCI@ggvm> start replicat repintan

 

Note that what we have done in the replicats is add very basic collision avoidance. This is an explanation of the options used and the results.

RESOLVECONFLICT (INSERTROWEXISTS, (DEFAULT, USEMAX (last_updt_dt_tm))),

RESOLVECONFLICT (UPDATEROWEXISTS, (DEFAULT, USEMAX (last_updt_dt_tm))),

 

From Oracle GoldenGate documentation:

Per DEFAULT, use the USEMAX resolution logic for all other columns in the table (the default column group), using the last_mod_time column as the resolution column. This column is updated with the current time whenever the row is modified; the value of this column in the trail is compared to the value in the target. If the value of last_mod_time in the trail record is greater than the current value of last_mod_time in the target database, the changes to name, phone, address, balance, comment and last_mod_time are applied to the target.

RESOLVECONFLICT (DELETEROWEXISTS, (DEFAULT, IGNORE)),
The DELETEROWEXISTS keyword means that if the row exists during a delete operation, apply the delete.

RESOLVECONFLICT (UPDATEROWMISSING, (DEFAULT, OVERWRITE)),
If the row does not exist during an update, change the update to an insert and apply it.

RESOLVECONFLICT (DELETEROWMISSING, (DEFAULT, DISCARD)),
Means if the deleted row is missing, discard and continue.

In order to prevent the replication from failing, all other errors are logged to the exceptions table.

Now run the same validation tests with two way replication running, and verify that everything is working properly.

In this article, we have configured bidirectional replication using Oracle GoldenGate, and added basic conflict detection and resolution. In my next post, I will cover adding one way replication to MySQL.

Please note: this blog contains code examples provided for your reference. All sample code is provided for illustrative purposes only. Use of information appearing in this blog is solely at your own risk. Please read our full disclaimer for details.

 

Table of Contents

Related Posts