OpenLogReplicator for Oracle 12c

Today's goal is to configure OpenLogReplicator for an Oracle 12c database (once again, working with ancient technology)

Installing OEL 7 (RHEL 7) and Oracle Database 12c (12.2)

The first thing to do, login to edelivery.oracle.com and download all software I need: Oracle 12c and OEL 7, nowdays I wouldn't recommend to anyone to install an OS this old, however, as a POC it's always a good idea to start with "certified" software versions.

I'd recommend checking a setup guide from somewhere else, though, I'm pretty sure I'm missing some steps here because this won't be a production system, or checking the Quick Installation Guide from Oracle (which is anything but quick...)

Next thing is to install the OS and all required packages based on the Oracle 12c installation guide

yum install binutils
yum install compat-libcap1
yum install compat-libstdc++-33
yum install compat-libstdc++-33.i686
yum install glibc
yum install glibc.i686
yum install glibc-devel
yum install glibc-devel.i686
yum install ksh
yum install libaio
yum install libaio.i686
yum install libaio-devel
yum install libaio-devel.i686
yum install libX11
yum install libX11.i686
yum install libXau
yum install libXau.i686
yum install libXi
yum install libXi.i686
yum install libXtst
yum install libXtst.i686
yum install libgcc
yum install libgcc.i686
yum install libstdc++
yum install libstdc++.i686
yum install libstdc++-devel
yum install libstdc++-devel.i686
yum install libxcb
yum install libxcb.i686
yum install make
yum install nfs-utils

yum install sysstat
yum install unixODBC
yum install unixODBC-devel

yum install smartmontools
yum install net-tools

Executing the OUI

While the database doesn't require an X11 Server itself, the Oracle Installer and the DBCA requires it (partially, it is still possible to run a headless setup using a rsp file)

Back in 2010 a common practice was to run a X11 server and allow connections to it, usually from the same server, but it is also possible to open a VNC session to the server without the need of having a full desktop environment.

However, we're in 2026, and the security protocols used back in the 2010's are outdated, if we try to issue a vnc connection with a simple command, such as vncviewer <host> it won't work, as we'll get the following error message: No valid VeNCrypt sub-type

From a modern vnc client, it is still possible to bypass the vencrypt handshake by requesting a plain (and insecure!) vncauth:

vncviewer SecurityTypes=VncAuth,TLSVnc

Creating an Oracle Database

As I'm not using DBCA and not really interested in setting up parameters like redolog groups, sga memory or anything of the like, I'm using an empty parameter file with only db_name set on it, that way Oracle will pick up all parameters and memory size from default values.

First I created a new pfile on a random location

/tmp/init.ora

db_name=orcl

Then I added a new entry on /etc/oratab

orcl:/u01/app/oracle/product/12.2.0/dbhome_1:Y

Finally, I create an actual database, again, using default values which is not recommended for a production database (or any other system that would be used for serving, even like a development environment)

[oracle@localhost ~]$ . oraenv
ORACLE_SID = [orcl] ? orcl
The Oracle base has been set to /u01/app/oracle
[oracle@localhost ~]$ sqlplus /nolog

SQL*Plus: Release 12.2.0.1.0 Production on Sat Mar 14 00:17:03 2026

Copyright (c) 1982, 2016, Oracle. All rights reserved.

SQL> conn / as sysdba
Connected to an idle instance.
SQL> startup nomount pfile=/tmp/init.ora
ORACLE instance started.

Total System Global Area 306184192 bytes
Fixed Size 8619936 bytes
Variable Size 239077472 bytes
Database Buffers 50331648 bytes
Redo Buffers 8155136 bytes
SQL> create database;

Database created.

SQL> create spfile from memory;

File created.

SQL> @?/rdbms/admin/catalog

SQL> @?/rdbms/admin/catproc

SQL> @?/rdbms/admin/catqm xdb2026 SYSAUX TEMP NO

Finally, I created a new systemd service for autostarting this database, it uses the dbstart and dbshut scripts, which relies on the entries set on /etc/oratab

/etc/systemd/system/oracle-db.service

[Unit]
Description=Oracle Database Service
After=syslog.target network.target

[Service]
LimitMEMLOCK=infinity
LimitNOFILE=65535
Type=forking
User=oracle
Group=oinstall
Environment="ORACLE_HOME=/u01/app/oracle/product/12.2.0/dbhome_1/"
ExecStart=/u01/app/oracle/product/12.2.0/dbhome_1/bin/dbstart $ORACLE_HOME
ExecStop=/u01/app/oracle/product/12.2.0/dbhome_1/bin/dbshut $ORACLE_HOME
RemainAfterExit=True
Restart=no

[Install]
WantedBy=multi-user.target

After this, the oracle database and listener can be started or stopped by the system:

# systemctl status oracle-db
● oracle-db.service - Oracle Database Service
Loaded: loaded (/etc/systemd/system/oracle-db.service; disabled; vendor preset: disabled)
Active: inactive (dead)

# systemctl start oracle-db
# systemctl status oracle-db
● oracle-db.service - Oracle Database Service
Loaded: loaded (/etc/systemd/system/oracle-db.service; disabled; vendor preset: disabled)
Active: active (running) since Sat 2026-03-14 17:58:01 EDT; 4s ago
Process: 21426 ExecStart=/u01/app/oracle/product/12.2.0/dbhome_1/bin/dbstart $ORACLE_HOME (code=exited, status=0/SUCCESS)
CGroup: /system.slice/oracle-db.service
├─21434 /u01/app/oracle/product/12.2.0/dbhome_1//bin/tnslsnr LISTENER -inherit
├─21537 ora_pmon_orcl
├─21539 ora_clmn_orcl
├─21541 ora_psp0_orcl
├─21543 ora_vktm_orcl
├─21548 ora_gen0_orcl
├─21550 ora_mman_orcl
├─21554 ora_gen1_orcl
├─21558 ora_diag_orcl
├─21560 ora_ofsd_orcl
├─21564 ora_dbrm_orcl
├─21566 ora_vkrm_orcl
├─21568 ora_svcb_orcl
├─21570 ora_pman_orcl
├─21572 ora_dia0_orcl
├─21574 ora_dbw0_orcl
├─21576 ora_lgwr_orcl
├─21578 ora_lg00_orcl
├─21580 ora_ckpt_orcl
├─21582 ora_lg01_orcl
├─21584 ora_smon_orcl
├─21586 ora_smco_orcl
├─21588 ora_reco_orcl
├─21590 ora_w000_orcl
├─21592 ora_lreg_orcl
├─21594 ora_w001_orcl
├─21596 ora_pxmn_orcl
├─21600 ora_mmon_orcl
├─21602 ora_mmnl_orcl
├─21604 ora_tmon_orcl
├─21624 ora_tt00_orcl
├─21626 ora_tt01_orcl
├─21628 ora_tt02_orcl
├─21630 ora_aqpc_orcl
├─21632 ora_cjq0_orcl
├─21646 ora_p000_orcl
├─21664 ora_p001_orcl
├─21677 ora_p002_orcl
├─21695 ora_p003_orcl
├─21709 ora_p004_orcl
├─21711 ora_p005_orcl
├─21713 ora_p006_orcl
├─21715 ora_p007_orcl
├─21717 ora_p008_orcl
├─21719 ora_p009_orcl
├─21721 ora_p00a_orcl
├─21723 ora_p00b_orcl
├─21725 ora_p00c_orcl
├─21727 ora_p00d_orcl
├─21729 ora_p00e_orcl
└─21731 ora_p00f_orcl

Mar 14 17:57:51 localhost.localdomain systemd[1]: Starting Oracle Database Service...
Mar 14 17:57:51 localhost.localdomain dbstart[21426]: Processing Database instance "orcl": log file /u01/app/oracle/product/12.2.0/dbhome_1/startup.log
Mar 14 17:58:01 localhost.localdomain systemd[1]: Started Oracle Database Service.

Again... while this database should work for our purpose, it is very poorly set, and this practice should be followed by caution.

Compiling OpenLogReplicator

As RHEL 7 is a very old system, I plan to compile OpenLogReplicator on my Gentoo system with static libraries

# on build folder
cmake -DCMAKE_BUILD_TYPE=Release \
-DWITH_STATIC=ON \
-DCMAKE_EXE_LINKER_FLAGS="-static" \
-DWITH_RAPIDJSON=/usr/include ..

make -j$(nproc)

Note. I had to include the location of the rapidJSON header, maybe because the recommended compilation method is using a docker image (while I like docker, I don't commune with wasting resources)

After uploading the binary file OpenLogReplicator to the RHEL 7 we're ready to begin setup.

As per OLR documentation, we need to setup:

archivelog mode

SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup mount;
ORACLE instance started.

Total System Global Area 306184192 bytes
Fixed Size 8619936 bytes
Variable Size 239077472 bytes
Database Buffers 50331648 bytes
Redo Buffers 8155136 bytes
Database mounted.
SQL> alter database archivelog;

Database altered.
SQL> alter database open;

Database altered.

supplemental log data

SQL> ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;

Database altered.

SQL> ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;

Database altered.

force loggging (recommended, as some operations might be skipped otherwise)

SQL> ALTER DATABASE FORCE LOGGING;

Database altered.

flashback

# at OS Level, create a destination for the Flashback Recovery Area
$ mkdir -p /u01/app/oracle/fra/orcl

SQL> ALTER SYSTEM SET DB_RECOVERY_FILE_DEST_SIZE = 100M scope = both;

System altered.

SQL> ALTER SYSTEM SET DB_RECOVERY_FILE_DEST = '/u01/app/oracle/fra/orcl' SCOPE=BOTH;

System altered.

SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup mount;
ORACLE instance started.

Total System Global Area 306184192 bytes
Fixed Size 8619936 bytes
Variable Size 239077472 bytes
Database Buffers 50331648 bytes
Redo Buffers 8155136 bytes
Database mounted.
SQL> ALTER SYSTEM SET DB_FLASHBACK_RETENTION_TARGET=1440 SCOPE=BOTH;

System altered.

SQL> ALTER DATABASE FLASHBACK ON;

Database altered.

SQL> ALTER DATABASE OPEN;

Database altered.

Now it's time to create the OLR user on the database, now here's something we need to consider before moving on:

OLR allows to extract information from the redolog/archivelog files in online and offline mode, while the offline mode sounds neat, it needs more configuration steps as the online mode, however, the online mode requires you to have OLR compiled using the OCI libraries dynamically linked.

Well, since Oracle 12c is "certified" on the ancient RHEL 7, it is complicated to have a proper build environment there, mostly because the OLR project requires CMAKE 3.16 at least and the version available on the repos for RHEL is 2.8, this leads us to a set of challenges:

As we did a static compilation previously, this OLR binary won't have daatabase connection capabilities.

Because of this, we need to take care of the initial schema information (the initial checkpoint file at a glance), while there's a script on the OLR project for this, the project itself suggests to first perform an online replication and then changing it to offline (I'd recommend also this approach, also)

What I did was to recompile OLR on my system with OCI capabilities, run it online and then copy the the schema file to the RHEL 7 system, however I think it's also possible to do it offline and I'll try to describe those steps here.

Download the gencfg.sql file from the OLR project

Change the database name and the schemas to be replicated on the script (for some reason, you have to do it manually)

Create a new checkpoint file using the database name, scn, resetlogs scn and the activation#.

Yeah, this is a complex setup, and that's why the recommended approach is to let OLR create it for you, however you can still try with this script I created based on the tutorials of OLR

export OLR_VERSION=${OLR_VERSION:=1.9.0}

mkdir -p sql
echo "- creating OpenLogReplicator configuration"
curl https://raw.githubusercontent.com/bersler/OpenLogReplicator/refs/tags/v${OLR_VERSION}/scripts/gencfg.sql -o sql/gencfg.sql
cat sql/gencfg.sql | sed "s/'DB'/'ORCL'/g" | sed "s/'USR1', 'USR2'/'SCOTT'/g" > sql/gencfg-ORCL.sql
sqlplus / as sysdba << EOF

spool gencfg-ORCL.out
@sql/gencfg
spool off
exit
EOF

SCN=`cat gencfg-ORCL.out | egrep "^SCN:" | sed "s/SCN: //g"`
RESETLOGS=`cat gencfg-ORCL.out | egrep "^{\"incarnation" | grep "\"status\":\"CURRENT\"" | sed "s/^.*\"resetlogs\"://g" | sed "s/\,.*$//g"`
ACTIVATION=`cat gencfg-ORCL.out | egrep "^{\"database" | sed "s/^.*activation\"://g" | sed "s/\,.*$//g"`
if [[ -z "${SCN}" ]] || [[ -z "${RESETLOGS}" ]] || [[ -z "${ACTIVATION}" ]]; then
echo "- error obtaining parameters from gencfg output"
exit 1
fi
cat gencfg-ORCL.out \
| grep -v "PL/SQL procedure successfully completed." \
| egrep -v "^SQL\>" \
| egrep -v "SCN: " \
| egrep -v "^CONTENT OF: " \
| egrep -v "^$" \
> checkpoint/ORCL-chkpt-${SCN}.json

cat <<EOF >checkpoint/ORCL-chkpt.json
{"database":"ORCL","scn":${SCN},"resetlogs":${RESETLOGS},"activation":${ACTIVATION}}
EOF

Here, ORCL is the DB Name and SCOTT is the schema we want to replicate, it should generate 2 files for us:

ORCL-chkpt-<scn>.json and ORCL-chkpt.json

Now if you try to execute it using the output from above script, OLR will complain about missing the db-id field, you may add it manually on the json payload, right before "con-id"

"context":"orcl",
"db-id":0,
"con-id":0,
"con-name":"orcl"

Next, for the setup file, it should be located on scripts/ directory

{
"version": "1.9.0",
"log-level": 3,
"state": {
"type": "disk",
"path": "/home/oracle/olr/checkpoint",
"interval-s": 600,
"interval-mb": 100,
"keep-checkpoints": 100,
"schema-force-interval": 20
},
"source": [
{
"alias": "S1",
"name": "ORCL",
"reader": {
"type": "offline",
"path-mapping": ["/u01/app/oracle/fra/orcl/orcl", "/u01/app/oracle/fra/orcl/ORCL"]
},
"format": {
"type": "json"
},
"filter": {
"table": [
{"owner": "SCOTT", "table": "EMP"}
]
}
}
],
"target": [
{
"alias": "FILE",
"source": "S1",
"writer": {
"type": "file",
"output": "/home/oracle/olr/output/arch-%t.json",
"max-file-size": 200240,
"append": 0
}
}
]
}

I'll highlight the following fields:

The "source" is, as its name implies, the data source, as you can see, we set the reader type "offline", and a path-mapping, if you scroll up on this post, you'll find that I created the database with orcl in lowercase, this is an uncommon practice and it came to shoot me here, the OMF standard creates a directory with the DBID in uppercases, that's why I had to map that path.

A worthy side note here is that you can map any path here in no specific order, this is important specially if you're running OLR on another server

"filter" of course filters the objects we're interested in replicating, nothing else to mention here.

"target" is the destination of the transactions we're interested, based on OLR documentation we can replicate to kafka,a zeromq queue, a network destination using protobuf protocol, or json files, on this scenario, we're going to write json files and here I'd like to mention 2 fields from the "writer" object:

"output" of course is the file we want to write, here's worth to note we're using a format flag %t, which should append the timestamp of a new file, however in order to make this to work, we also need the "max-file-size", which controls the max size of each json file.

If everything was set properly, running the ./OpenLogReplicator binary should display the following output

$ ./OpenLogReplicator
2026-03-14 22:21:51 INFO 00000 OpenLogReplicator v1.9.0 (C) 2018-2026 by Adam Leszczynski (aleszczynski@bersler.com), see LICENSE file for licensing information
2026-03-14 22:21:51 INFO 00000 arch: x86_64, build-arch: , system: Linux, release: 5.4.17-2136.338.4.2.el7uek.x86_64, build: Release, compiled: 2026-03-14 05:51, modules: static
2026-03-14 22:21:51 INFO 00000 adding source: S1
2026-03-14 22:21:51 INFO 00000 adding target: FILE
2026-03-14 22:21:51 INFO 00000 writer is starting with Writer: file:/home/oracle/olr/output/arch-%t.json
2026-03-14 22:21:51 INFO 00000 checkpoint - all confirmed till scn: 603193, idx: 0
2026-03-14 22:21:51 INFO 00000 reading metadata for ORCL for scn: 602417
2026-03-14 22:21:51 INFO 00000 reading schema for ORCL for scn: 601179
2026-03-14 22:21:51 INFO 00000 - found: SCOTT.EMP (dataobj: 21833, obj: 21833, columns: 8, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: XDB.X$QN2CVL9ACJAR0F7V0P8000000001 (dataobj: 21270, obj: 21270, columns: 4, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: XDB.X$PT2CVL9ACJAR0F7V0P8000000001 (dataobj: 21271, obj: 21271, columns: 2, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: XDB.X$NM2CVL9ACJAR0F7V0P8000000001 (dataobj: 21269, obj: 21269, columns: 2, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: XDB.XDB$TTSET (dataobj: 20377, obj: 20377, columns: 4, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: SYS.USER$ (dataobj: 10, obj: 22, columns: 30, lobs: 0, lob-idx: 0), part of cluster
2026-03-14 22:21:51 INFO 00000 - found: SYS.TS$ (dataobj: 6, obj: 16, columns: 32, lobs: 0, lob-idx: 0), part of cluster
2026-03-14 22:21:51 INFO 00000 - found: SYS.CCOL$ (dataobj: 29, obj: 32, columns: 11, lobs: 0, lob-idx: 0), part of cluster
2026-03-14 22:21:51 INFO 00000 - found: SYS.CDEF$ (dataobj: 29, obj: 31, columns: 21, lobs: 0, lob-idx: 0), part of cluster
2026-03-14 22:21:51 INFO 00000 - found: SYS.ECOL$ (dataobj: 151, obj: 151, columns: 4, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: SYS.LOBFRAG$ (dataobj: 827, obj: 827, columns: 16, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: SYS.COL$ (dataobj: 2, obj: 21, columns: 37, lobs: 0, lob-idx: 0), part of cluster
2026-03-14 22:21:51 INFO 00000 - found: SYS.LOB$ (dataobj: 2, obj: 108, columns: 18, lobs: 0, lob-idx: 0), part of cluster
2026-03-14 22:21:51 INFO 00000 - found: SYS.OBJ$ (dataobj: 18, obj: 18, columns: 37, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: SYS.LOBCOMPPART$ (dataobj: 830, obj: 830, columns: 24, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: SYS.TABPART$ (dataobj: 794, obj: 794, columns: 26, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: SYS.TAB$ (dataobj: 2, obj: 4, columns: 45, lobs: 0, lob-idx: 0), part of cluster
2026-03-14 22:21:51 INFO 00000 - found: SYS.TABCOMPART$ (dataobj: 816, obj: 816, columns: 35, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: SYS.DEFERRED_STG$ (dataobj: 81, obj: 81, columns: 25, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 - found: SYS.TABSUBPART$ (dataobj: 806, obj: 806, columns: 26, lobs: 0, lob-idx: 0)
2026-03-14 22:21:51 INFO 00000 online redo log: /u01/app/oracle/product/12.2.0/dbhome_1/dbs/log1orcl.dbf
2026-03-14 22:21:51 INFO 00000 online redo log: /u01/app/oracle/product/12.2.0/dbhome_1/dbs/log2orcl.dbf
2026-03-14 22:21:51 INFO 00000 timezone: -05:00, db-timezone: -04:00, log-timezone: -05:00, host-timezone: -05:00
2026-03-14 22:21:51 INFO 00000 Replicator for ORCL in offline mode is starting from scn: 603193
2026-03-14 22:21:51 INFO 00000 current resetlogs is: 1227831645
2026-03-14 22:21:51 INFO 00000 first data SCN: 603193
2026-03-14 22:21:51 INFO 00000 first schema SCN: 600429
2026-03-14 22:21:51 INFO 00000 last confirmed scn: 603193, starting sequence: 28, offset: 35688960
2026-03-14 22:21:51 INFO 00000 resume writer
2026-03-14 22:21:51 INFO 00000 found redo log version: 12.2.0.0, activation: 1755632861, resetlogs: 1227831645, page: 512, sequence: 28, SID: ORCL, endian: LITTLE
2026-03-14 22:21:52 INFO 00000 processing redo log: group: 2 scn: 560254 to 604800 seq: 28 path: /u01/app/oracle/product/12.2.0/dbhome_1/dbs/log2orcl.dbf offset: 35688960
2026-03-14 22:21:52 INFO 00000 processing redo log: group: 1 scn: 604800 to 0 seq: 29 path: /u01/app/oracle/product/12.2.0/dbhome_1/dbs/log1orcl.dbf offset: 1024

If the reader reached to this point, he can try that the replication works, on the current setup it stores json objects for each recorded transaction, like

$ cat arch-2026-03-14_21\:52\:55.json
{"scn":592984,"tm":1773542753000000000,"c_scn":592984,"c_idx":1,"xid":"0x0003.01d.000001eb","payload":[{"op":"begin"}]}
{"c_scn":592984,"c_idx":2,"xid":"0x0003.01d.000001eb","payload":[{"op":"c","schema":{"owner":"SCOTT","table":"EMP"},"after":{"EMPNO":1064,"ENAME":"Nicko"}}]}
{"c_scn":592984,"c_idx":3,"xid":"0x0003.01d.000001eb","payload":[{"op":"commit"}]}
{"scn":595336,"tm":1773543641000000000,"c_scn":595336,"c_idx":1,"xid":"0x000a.007.000001cd","payload":[{"op":"begin"}]}
{"c_scn":595336,"c_idx":2,"xid":"0x000a.007.000001cd","payload":[{"op":"c","schema":{"owner":"SCOTT","table":"EMP"},"after":{"EMPNO":1099,"ENAME":"JohnC","JOB":"Warrior","MGR":1064,"HIREDATE":1773525641000000000,"SAL":9999,"COMM":0,"DEPTNO":10}}]}
{"c_scn":595336,"c_idx":3,"xid":"0x000a.007.000001cd","payload":[{"op":"commit"}]}

Appendix

Setup an OLR user for online replication

-- Example of an OLR user creation
create user OLR identified by "Olr$PWD_Test";
grant create session to OLR;

@grants

Compiling OLR with OCI

While due to the nature of OLR this should be a straightforward operation, it needs some considerations

cmake needs the -DWITH_OCI=<path_to_OCI_libs> if we want to compile OCI, like

cmake -DCMAKE_BUILD_TYPE=Release -DWITH_OCI=/home/nicko/oracle/LINUX.X64_213000_client_home ..

Inside the CMakeLists.txt, you might need to change the reference of the nnz library accordingly to the client installed on your machine, for example:

@@ -131,7 +139,7 @@ add_subdirectory(src)
target_link_libraries(OpenLogReplicator Threads::Threads)

if (WITH_OCI)
- target_link_libraries(OpenLogReplicator clntshcore nnz clntsh)
+ target_link_libraries(OpenLogReplicator clntshcore nnz21 clntsh)
endif ()

I suppose this is one of the reasons why OLR author recommends to use docker for compilation and deployment, it's worth to mention that OCI is not freely available for download (well, yeah you can download it for free from oracle.com, but you need an account and login prior to download, but you can't just include the OCI binaries on your software)