The goal of our solution is to export as fast as possible CLOB data from an events table. The concept is to logically partition the data with a partition_id column such that every thread from the external application (e.g. Spring Boot) does not interfere with the other threads. Approaches like CDC with Streaming APIs have been discussed and cannot be used in our case. Our goal is to use "basic" Oracle 12c/19c features that were highly optimized like the cost-based optimizer. There is a solution in place with advance queueing which is by far too slow and for internal reasons cannot be followed up.
We have discussed the following options.
Option 1, 'Mark events as processed in the events table'
The external thread of the application (e.g. Spring Boot) ...
- registers via the partitions table for a dedicated partition (e.g. partition_id=10),
- bulk selects a certain amount of data (e.g. rownum <= 100 ) of the events table of the partition (eg. partition_id=10) and processed the events externally,
- bulk updates the processed ids in the events table (status -> ack),
- de-registers the execution of the thread in the partitions table for another thread.
create table events(
id number(15),
status_id number(2), -- whether the events was successfully uploaded
insert_timestamp timestamp,
partition_id number(9), --> partitions
event clob -- the events paylod to export
);
create table partitions
(
id number(15),
is_running varchar2(1), -- when a thread of the external application reserved a partition for an execution, a flag is set e.g. '+'
last_execution timestamp -- when an execution of an export was done, the current timestamp is set
);
create index msg_idx on events (partition_id, status_id, id);
Question Option 1: As there will be multiple threads inserting new data, may be 10-50 threads bulk selecting data, and as many threads bulk updating the same table - however, always different rows!! - is there any risk that Oracle cannot handle the processing efficiently and maybe e.g. locks the whole table etc.? What would be important to consider using this approach?
Option 2, 'Save the highest processed commit sequence id ORA_ROWSCN in a partitions table as offset'
This option would make use of the ROWDEPENDENCIES feature. As the insert/commit sequence is known as number it can be used as "last_processed offset". The processing is similar for the registration of thread. The selection on the events table would start based on the last offset ORA_ROWSCN (like Apache Kafka consumers).
create table events
(
id number(15),
insert_timestamp timestamp,
partition_id number(9), --> partitions
event clob -- the event paylod to export
) ROWDEPENDENCIES;
create table partitions
(
id number(15),
is_running varchar2(1), -- when a thread of the external application reserved a partition for an execution, a flag is set e.g. '+'
last_offset number(15), -- ORA_ROWSCN of the last execution
last_execution timestamp -- when an execution of an export was done, the current timestamp is set
);
Question Option 2: The advantage of this option would be, that events are only inserted and selected, never updated. However, as ORA_ROWSCN cannot be indexed, we would create an appropriate table with a materialized view.
- How should be the refresh of the materialized view configured ideally for such cases?
- What kind of risks comes with these solutions in term of hard to reproduce production issues?
Option 3, 'Save all processed event ids in a separate table'
The partition registration is the same as with the other options. The difference to option 1 is, that the events that were processed are not updated in the events table with a status, but inserted in a dedicated table. The selection would join the events and processed_events table to detect the events to be processed either with 'join', 'not in' etc.
create table events
(
id number(15),
insert_timestamp timestamp,
partition_id number(9), --> partitions
event clob -- the event paylod to export
);
create table processed_events(
id number(15) -- the event that where processed
,...
)
create table partitions
(
id number(15),
is_running varchar2(1), -- when a thread of the external application reserved a partition for an execution, a flag is set e.g. '+'
last_execution timestamp -- when an execution of an export was done, the current timestamp is set
);
Question Option 3: Are there any known performance aspects that should be considered?
For sure we will try the variants. However, the behavior on test systems with few CPUs might be quite different than on an instance with more than 100 CPUs etc. Therefore, we are very grateful for feedback of any kind.
Thanks for reading :)