So we set up a Change Data Capture replication pipeline from Oracle to Firebolt and wanted to benchmark how it performs under a “typical” OLTP load. This article discusses the various characteristics of the benchmark.
I do want to mention here that Firebolt benchmark is a little different, and slightly slower than what it could possibly be and the reason being that our database server is in GCP while our Firebolt bucket is in AWS. That is definitely a network hop, that will do some latency, as opposed to going from GCP to GCP. This probably explains why Firebolt benchmarks are slightly slower.
Benchmark Details
The most important decision here was to determine how to do a replication benchmark, specifically what constitutes a typical load profile of an OLTP database. We realized that a typical OLTP database has the following characteristics
-
It has multiple tables.
-
It has some inserts.
-
It has somewhat more updates.
-
It has some deletes.
-
It has small transactions with lot of commits, mostly single row operations and commits.
Given the above characteristics we decided on doing the following for the benchmark.
-
We will have 10 tables.
-
We will have 2 million single row inserts.
-
We will have 6 million single row updates.
-
We will have 2 million single row deletes.
-
We will have auto commit on, causing all the above operations to have corresponding commit.
Table Structure
We will have 10 tables called firenibble 1 to 10.
CREATE TABLE app_db.firenibble1 (
f0 number(38) NOT NULL,
f1 number(38) ,
f2 number(38) ,
f3 int ,
f4 float ,
f5 float ,
f6 float ,
f7 float ,
f8 varchar2(128) ,
f9 varchar2(128) ,
f10 date ,
f11 date ,
f12 date ,
f13 varchar2(128) ,
f14 varchar2(128) ,
f15 varchar2(128) ,
primary key (f0)
);
Insert
Insert will essentially be new primary key insert.
insert into firenibble1 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
Update
Update will be updating of two columns on a primary key value.
update firenibble1 set f2 = 280396830734761092, f9 = 'n6ChQjN9dfxIl6nc6SNgIzSahfeXfAef' where f0 = 19998;
Delete
Delete will be essentially a primary key delete.
delete from firenibble1 where f0 = 19998;
DML Operations Sequence
Then came the question of how do we order the load so that we can rerun the load over and over again and never have to clean up the previous load. So we decided that the load will be such that we will start with 0 rows in all 10 tables, do a bunch of inserts, do a bunch of updates, do a bunch of deletes and end up with 0 rows in all tables. This kept the load and the tables rather clean.
So the exact sequence will look like this. I know we could have been more granular mixing the inserts, updates and deletes even more, and as we do more benchmarks we might try that strategy but for starters this looked pretty good. Also we wanted to see how our replication performed with large number of insert only, update only and delete only loads. Also note that there is no relationships between the load and the archivelogs, in the sense there is no alter system switch logfile happening anywhere causing the inserts, updates and deletes to cross binary log boundaries.
-
1 million inserts
-
3 million updates
-
1 million deletes
-
1 million inserts
-
3 million updates
-
1 million deletes.
We will also makes sure that all inserts, updates and deletes happen on 10 tables in a sequence so that the operations are granularily staggered over all tables and not contiguous on one table and the next. Which means, the inserts look like this (and so does the updates and deletes).
insert into firenibble1 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble2 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble3 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble4 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble5 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble6 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble7 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble8 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble9 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
insert into firenibble10 values (19998, 5963887289447355291, 6644703987735879175, -2094978734, 0.51946, 0.480314, 0.528386, 0.247536, 'Y4Jt79YtKat99KRV8CioT7p6bUVdtsm6', 'hC8SKt3MgvoGkOYJIljgKjdEaoY68YJe', '5354-04-29', '5741-02-18', '9548-08-16', 'K3fiVGzTGeiIB2AfChQDXOAQ7ANZBxEN', 'SETnGb7dxOVQthQtFdH6lbZ6yqVVoXPU', 'fJMS3fPnIJBnsi0CBxkFc2fZDf5gUiSJ');
Benchmark Sequence
A choice we had was to make was do one of the following
-
Have the wirekite replication running. Run the load and wait for the last event of the load to appear on the target. Measure the time between when the load started and the last event appeared.
-
Run the load and note down the binlog number and position at which the load started and wait for the load to finish. Now start the wirekite replication from the starting binlog number and position and wait for the last event of the load to appear on the target. Measure the time between when the wirekite replication started and the last event appeared on the target.
We decided to go with option 2. The primary reason being that in option 1 wirekite replication was waiting for the events to finish successfully on the database, and essentially we were not only benchmarking wirekite, but also the database, with the boundaries between the two completely blurred. So we preferred the pre-loaded database with the changes, so that we are only benchmarking wirekite and nothing else.
Machine Configuration
For Oracle we created an n2-standard-16 GCP instance with all the standard configurations - 8 CPU’s and 64 GB RAM in the us-central region. We installed Ubuntu version 24 and Oracle version 19. Our archive log size was 330 MB.
For Firebolt we created a Medium Size Warehouse.
=> show engines;
1: engine_name: my_engine
1: engine_owner: gjkemnitz
1: type: M
1: family: COMPUTE_OPTIMIZED
1: nodes: 5
1: clusters: 1
1: status: RUNNING
1: auto_start: 1
1: auto_stop: 20
1: initially_stopped: 0
1: url: account-1-fxrjk.api.us-east-1.app.firebolt.io?engine=my_engine
1: default_database:
1: version: 4.21.9
1: last_started: 2025-06-14 04:28:27.824
1: last_stopped: 2025-06-14 03:58:10.993
1: description:
1: fbu_rate: 40
I wish we could automatically create a Large Size Warehouse but we got the following error.
=> ALTER ENGINE my_engine SET TYPE = "L";
2025/06/14 04:49:00 ERROR query ALTER ENGINE my_engine SET TYPE = "L" failed: error during query execution: error during query request: Alter engine failed. 'L_COMPUTE_OPTIMIZED' node type is currently in preview. Reach out to Firebolt Support to enable 'L_COMPUTE_OPTIMIZED' node type for this account
We have been using the same configurations for all our benchmarks so that we are comparing apples and apples.
Oracle Run Around
We wanted to mention this a bit to talk about the uphill challenge that was getting oracle to be up and running and importing data into it.
First it was the installation which we thought we could do using the command line. But once we started playing with response files we realized it was almost impossible to get the right response file. There are hundreds of options that go in oracle response file which makes it impossible to find the right combination. We kept on running into errors.
So we decided to use the GUI. Getting X11 forwarding working took some time (that DISPLAY variable) but we were able to do it. But then Oracle GUI would show up with buttons missing from it - https://forums.oracle.com/ords/apexds/post/19c-runinstaller-shows-blank-screen-for-15-minutes-9680.
We realized that some given environment variable has to be set to make sure the GUI runs as intended. Once we did that we got the right GUI and got Oracle installed.
And then came the challenge of configuring the database with sensible values so that you can actually load a reasonable amount of data in it. We had to configure
-
Configure and start the LISTENER. Understand LISTENER.ORA and TNSNAMES.ORA.
-
Figure out the differences between PFILE and SPFILE and the workings of SCOPE.
-
Turn off Automatic Shared Memory Management - MEMORY_TARGET = 0
-
Turn off Automatic Management - SGA_TARGET = 0
-
Set SGA_MAX_SIZE
-
Set DB_CACHE_SIZE
-
Set SHARED_POOL_SIZE
-
Set LARGE_POOL_SIZE
-
Modify a few kernel parameters (SHMMAX, SHMALL, file open limits), modify sysctl.conf and limits.conf and reboot the instance a few times.
-
Create a separate TABLESPACE to hold the table with the right number and size of data files and make sure they extend automatically.
-
Grant privileges on this TABLESPACE to the right USER.
-
Create the table with the right TABLESPACE for data but also the right TABLESPACE for INDEX otherwise it will create index in the default tablespace.
-
Figure out SQL LOADER to load the data from a csv into the table. Make sure you have the right commit frequency.
-
Monitor DBA_FREE_SPACE and add data files when it comes close.
-
Play with V$SESSION and V$SESSION_WAIT to see what is happening.
-
Play with EXPLAIN PLANS since Oracle gives a thousand ways to see the actual explain plan.
No Airbyte Run
We could not do an airbyte run since it requires an enterprise level connector. So this is just our benchmark.

Wirekite Run
So how long did it take for wirekite to move 10 million changes — 5 minutes and 50 seconds.
cat wirekite_cdc.log | grep -E 'START|FINISH|ELAPSED'
START: Sat Jun 14 04:40:51 UTC 2025
FINISH: Sat Jun 14 04:46:41 UTC 2025
TOTAL ELAPSED TIME: 0 hrs 5 min 50 sec