We have a large Oracle 11gR2 instance with some tables that have recently become an annoyance when deleting data. This database powers a large optimization cluster that takes input metadata from the DB and stores files (using 11g's SecureFiles) and 1D-4D output data used in visualizations. Reads/writes from the DB are really fast, and really things have been good until we needed to clean out data.
The software engineers were allowed to run free (read: me) and setup this system without any partitioning, naively assuming deletes would work well. Our table structure is below, and previously relied on ON DELETE CASCADE
to remove data per Case
where DeleteFlag = 1
.
/* Metadata tables */
Case(CaseId, DeleteFlag, ...) On Delete Cascade CaseId
OptimizationRun(OptId, CaseId, ...) On Delete Cascade OptId
OptimizationStep(StepId, OptId, ...) On Delete Cascade StepId
/* Data tables */
Files(FileId, CaseId, Blob) /* deletes are near instantateous here */
/* Data per run */
OnedDataX(OptId, ...)
TwoDDataY1(OptId, ...) /* packed representation of a 1D slice */
/* Data not only per run, but per step */
TwoDDataY2(StepId, ...) /* packed representation of a 1D slice */
ThreeDDataZ(StepId, ...) /* packed representation of a 2D slice */
FourDDataZ(StepId, ...) /* packed representation of a 3D slice */
/* ... About 10 or so of these tables exist */
The fan-out looks like: 1 case = 50 runs = 2000 steps = 5-35M rows across the data tables. Sum total of the data tables is ~500GB and just shy of 1B rows.
With some help on the big sister site I've arrived at a partitioning scheme likely to work for our setup (i.e. partition by CaseId
). It uses LIST-REFERENCE composite partitioning, like so:
/* Metadata tables */
Case(CaseId, DeleteFlag, ...)
OptimizationRun(OptId, CaseId, ...)
Partition By List (CaseId)
( partition default_case values (default) )
/* Some PL/SQL is run when we add a Case to do:
* alter table OptimizationRun split partition ...
*/
OptimizationStep(StepId, OptId, ...)
Partition By Reference (Fk_OptId)
/* Data Tables */
OnedDataX(OptId, ...)
Partition By Reference (Fk_OptId)
...
FourDDataZ(StepId, ...) /* packed representation of a 3D slice */
Partition By Reference(Fk_StepId)
Which was painless to setup, improves a lot of the plans for our usual suspect queries, and gives us the ability to delete all of the data for a case with a single DROP PARTITION
. This is where I have run into a problem: index rebuilding!
The current pseudo-PL/SQL to do the delete looks like:
for casedel in (select CaseId from Case where DeleteFlag = 1)
loop
execute immediate 'alter table OptimizationRun'
|| ' drop partition case_' || casedel.CaseId
|| ' update indexes';
end loop;
delete from Case where DeleteFlag = 1;
commit;
We end up with errors about some of the indexes on the data tables that are unrelated to the foreign key used in the REFERENCE-partitioning.
Do I really have to create the ALTER INDEX blah REBUILD
states for each of these ancillary indexes, or is there a smarter way?
Update: some actual DDL for your reading pleasure.
create table opt_case (
case_id number not null,
name varchar2(240) not null,
delete_date date,
constraint pk_optcase primary key (case_id) using index
);
create table opt_run (
opt_run_id number not null,
case_id number not null,
constraint pk_opt_run primary key (opt_run_id) using index,
constraint fk_opt_run_case
foreign key (case_id) references opt_case (case_id)
)
partition by list (case_id)
(
-- This is the catch-all partition. It is an error to insert into
-- this partition; it will be unused. Instead this is the
-- partition which is split when a new CASE_ID is added to
-- OPTCASE.
partition default_case values (default)
);
create index idx_fk_opt_run_case on opt_run (case_id);
create table opt_step (
opt_step_id number not null,
opt_run_id number not null,
step number not null,
step_value number(7,2),
constraint pk_opt_step primary key (opt_step_id) using index enable,
constraint fk_opt_step_opt_run
foreign key (opt_run_id) references opt_run (opt_run_id)
)
-- This is the golden ticket from here on out for partitioning.
-- Attach to the foreign key on OPT_RUN_ID to enjoy the
-- parent table partitioning on CASE_ID.
partition by reference (fk_opt_step_opt_run);
create index idx_fk_opt_step_opt_run on opt_step (opt_run_id);
-- One of the bigger data tables for reference
create table twod_data (
opt_step_id number not null,
spatial_coord_id number not null,
in_group number(1,0),
z_value number(3,0),
y_value number(5,3),
x_value number(5,1),
constraint pk_twod_data primary key (opt_step_id, spatial_coord_id),
constraint fk_twod_data_spat_coord
foreign key (spatial_coord_id)
references twod_spatial_cord (spatial_coord_id),
constraint fk_2d_data_opt_step
foreign key (opt_step_id) references opt_step (opt_step_id)
)
partition by reference (fk_2d_data_opt_step);
create index idx_fk_2d_data_opt_step on twod_data (opt_step_id);
Indexes like idx_fk_2d_data_opt_step
end up needing to be rebuilt after the DROP PARTITION
clause.
The inserts into the data tables are uninteresting, simple INSERT INTO blah
. The Case
creation procedure now looks like:
-- Add the CASE
insert into opt_case (name) values (p_CaseName) returning case_id into v_CaseId;
commit;
-- Add partition for the CASE
execute immediate 'alter table opt_run'
|| ' split partition default_case values (' || v_CaseId || ')'
|| ' into (partition case_' || v_CaseId ||', partition default_case)';
-- Because we split an empty partition, no index rebuild required
ORA-01502: index ‘%s.%s’ or partition of such index is in unusable state
. This was not during the ALTER or the DELETE, but rather subsequent queries that ended up using the indexes. Note that none of these are partition local indexes.OptimizationRun
table or on its children tables? the UPDATE INDEX clause might work only for the main table (and not those that were affected by the drop partition cascade).