Mathias Magnusson
Improving data move on EXADATA II
Writing log records
The last post in this series introduced the problem briefly. You find that post here.
In this post I’ll talk about the changes made to make that writing of log records fast enough. There were 50 million records that was written. Each of them pretty much in its own transaction. Of course the commit activity caused problem, as did log buffer issues. Some of this could be somewhat remedied with configuration.
The big issue though was that the writes themselves took too much time and too often many session ended up in long contention chains. Yes, it would have been great to have the luxury of redesigning the whole logging situation from the ground up. But, as is often the case, the solution was built such that all systems connecting were implemented in such a way that redesigning was not an option. Fixing the performance of this had to be done without requiring code changes to the systems performing the logging. Oh, joy.
So what caused the problem then? For the inserts it was pretty straight forward. Too many transactions making an insert and a commit. This caused indexes to be hotspots where all processes wanted to write at the same spot. Hash-partitioning had been introduced and that had led to less contention but slower performance. As the partitions existed on different parts on the disks the write head had to be constantly moved and that caused slower service times.
What could we do to make a big improvement while not affecting the code? We’re not talking about just 10-20% of improvement on any area in this case, and even more important was to make the performance stable. That is, the most important thing was to ensure that there were no spikes where an insert suddenly to 20 times longer than usual. The contention chains that was occurring made performance spike such that the whole system became unusable.
The solution here turned out to be something so far from advanced technologies as questioning assumptions. The first time I asked “why do we have these indexes”, most people in the room thought I was just joking around. Eventually they realised that I was serious. After an amusing period of silence where I could see them thinking “Do we need to inform him that indexes are needed to enforce uniqueness and to support referential integrity?”, someone went ahead and did just that. OK, now we were on to a productive discussion, as of course that wasn’t what I meant. The followup discussion about why we needed referential integrity and uniqueness for this set of data was very enlightening for everyone. To make a long story short, it was not needed at all. It was there because it had always been there and nobody had questioned the need before.
How come we didn’t need data to be unique? Well, this is log-data. That is it tells us what actions has been performed by the system. If some activity would be reported twice, it really wouldn’t be the end of the world. The possible problem that some activity isn’t logged cannot be handled with defining unique constraints. That is pure system design and nothing I could improve or worsen by removing some indexes.
Thus, the indexes was removed together with the foreign keys (referential integrity).
Sounds simple enough, but did it help? Did it ever! In one month after making the change, there has not been one report of one transaction that was anywhere close to take too long. This simple solution made the logging so fast that it is no longer a concern.
The next post in this series will discuss the solution for moving data to the history tables. This process took around 16 hours and it had to become at least three times as fast. As you’ll see, moving all these rows can be done much faster than so.
Faster data move on EXADATA I
Introduction
In my work among other things I tune and tweak solutions for EXADATA. Today I’ll write about a big improvement we achieved with a process that moves data from the operational tables to the ones where the history is stored.
This will not be a technical post. While I talk about using advanced technologies, I will not discuss code or deep details of them in this post.
And yes, when I say post, I mean a series of posts. This will be too long to be a single post. I’ll break it up into an introduction and then a post on each area of improvement.
Let’s first discuss the before situation. This set of tables are logged to during the day. These log records are needed both to investigate how transactions were executed as well as to satisfy legal requirements. It is in a highly regulated industry and for good reason as mistakes could put someone’s life in danger.
In this situation the solutions were writing around 50 million log records per day to five tables. These tables all had a primary key based on a sequence and there was also referential integrity set up. This means that for the indexes, all processes were writing to the same place on disk. The lookup on the referential integrity was also looking at the same place. An attempt to remedy some of this had been made by hash partitioning the tables. The write activity was intense enough during the day that most of the logging had to be turned off as the solution otherwise was too slow. This of course has legal as well as diagnostic implications.
What’s worse is that once all that data was written, it had to be moved to another database where the history is kept. This process was even slower and the estimate for how long it would take to move one days worth of data was 16 hours. It never did run for that long as it was not allowed to run during the day, it had to start after midnight and finish before 7 am. As a result the volume built up every night until logging was turned off for a while and the move then caught up a little every night.
This series will have the following parts:
- Introduction (this post)
- Writing log records
- Moving to history tables
- Reducing storage requirements
- Wrap-up and summary
The plan is to publish one part each week. Hopefully I’ll have time to publish some more technical posts between the posts in this series.
The power of using records in APEX III
In this post I’ll finish up the CRUD implementation using records, procedures and views. This series of blog posts started with this post which was followed by this.
At this point we have a working report that links to a form. The report is based on a view and the form is based on a procedure. At this point the form is only loading the record in using a procedure that uses a record in its signature. In this post we’ll complete the functionality by using the same form for insert, update, and delete functionality.
Let’s start with adding a mode page item to the form. We will use this to know if the form is invoked in insert or in update/delete mode.
- Right-click on “Form on Stored Procedure” on the page where your form is (page 4 in my example)
- Select “Create Page Item”
- Select Hidden
- Give the page item a name, like P4_MODE.
- Click “Next”
- Click “Next” (again)
- Select “Create Item”
You now have a new page item which is not displayed, but whose value you can reference on this page.
To make this example easy, we’ll start with just implementing update functionality. But first we’ll go to the report and change this link column to pass in a “U” for the mode page item when it is clicked.
In the page with the report, perform the following steps.
- Right-click “List of Employees”
- Click “Edit Report Attributes”
- Scroll down to “Link Column”
- For Item 2, enter the name and value we want the newly created page item to be set to. P4_MODE and U in my case.
- Apply Changes
Now with that done, let’s head back to the page with the form. But before we make any changes there, we need to change the package to add a procedure for updating a row in the emp table.
Here is the code for the procedure we’ll use.
create or replace package tb_access as
type t_emp_rec is record (empno emp.empno%TYPE
,ename emp.ename%TYPE);
procedure read_emp(p_in_empno in emp.empno%type
,p_out_emp_rec out t_emp_rec);
procedure write_emp(p_in_empno in emp.empno%type
,p_in_emp_rec in t_emp_rec);
end tb_access;
create or replace package body tb_access as
procedure read_emp(p_in_empno in emp.empno%type
,p_out_emp_rec out t_emp_rec) is
begin
select empno
,ename
into p_out_emp_rec.empno
,p_out_emp_rec.ename
from emp
where empno = p_in_empno;
end read_emp;
----------------------------------------------------
procedure write_emp(p_in_empno in emp.empno%type
,p_in_emp_rec in t_emp_rec) is
begin
update emp
set ename = p_in_emp_rec.ename
where empno = p_in_empno;
end write_emp;
end tb_access;
This code adds the procedure write_emp wich also takes empno and the record used for read_emp, only this time they are both in parameters as we’ll not return any data from the procedure that writes to the table. The actual code in the procedure is a very simple update that sets the name to the ename in the record for the row that has the empno passed in.
With this in place we’re ready to change the form to call this procedure upon submit. We do this by taking the code used for reading in the record (The process in page rendering) and put it into the “Run Stored Procedure” process we commented out in page processing.
The code we copy looks like this:
declare
in_emp_rec tb_access.t_emp_rec;
begin
tb_access.read_emp(p_in_empno => :p4_empno
,p_out_emp_rec => in_emp_rec);
:p4_ename := in_emp_rec.ename;
end;
Replace the code in “Run Stored Procedure” with the above and change it to look like this:
declare
in_emp_rec tb_access.t_emp_rec;
begin
in_emp_rec.ename := :p4_ename;
tb_access.write_emp(p_in_empno => :p4_empno
,p_out_emp_rec => in_emp_rec);
end;
In the “Run Stored Procedure” change the condition to “- Select condition type -” to not have a condition for when the process is executed. It is currently conditioned on running only when the save button is clicked, leave that condition on for now.
With that in place, it is time to test the functionality of the forms new functionality. Run the report, click on the link and update the name of an employee. Assuming you followed the above and got the changes made in the right places, the form will now update the name of the employee.
The next step in this will be to add insert functionality to the form. First off is to set up the package to support inserting an employee. As we’re now going to have both insert and update through the same procedure, we need to include the mode in the signature of the procedure.
Let’s first update the package specification so the write_emp looks like this:
procedure write_emp(p_in_empno in emp.empno%type
,p_in_mode in varchar2
,p_in_emp_rec in t_emp_rec);
The procedure in the package needs to be updated to look like this:
procedure write_emp(p_in_empno in emp.empno%type
,p_in_mode in varchar2
,p_in_emp_rec in t_emp_rec) is
begin
case p_in_mode
when 'U' then
update emp
set ename = p_in_emp_rec.ename
where empno = p_in_empno;
when 'I' then
insert into emp
(empno
,ename)
values((select max(empno) + 1 from emp)
,p_in_emp_rec.ename);
end case;
end write_emp;
Before the form is functional again, we need to change the call in “Run Stored Procedure” to match the new signature of write_emp.
declare
in_emp_rec tb_access.t_emp_rec;
begin
in_emp_rec.ename := :p4_ename;
tb_access.write_emp(p_in_empno => :p4_empno
,p_in_mode => :p4_mode
,p_in_emp_rec => in_emp_rec);
end;
With that change in place the form now works and the mode we set on the link column is now passed to the procedure to make sure an update is performed and not an insert.
To introduce insert functionality, we want to add a new button on the page with the report that invokes the form in insert mode. To add the button follow these steps on the page with the report.
- Right-click on “List of Employees”
- Select “Create Region Button”
- Set name and label to “Add”
- Click Next
- Set position to “Right of Interactive Report Search Bar”
- Set Alignment to “Left”
- Click Next.
- Select action “Redirect to Page in this Application”
- Set the page to your form-page (4 in my example)
- Set Request to INSERT
- Set “Clear Cache” to 4
- Set “Set these items” to P4_MODE
- Set “With these values” to I.
- Click Next.
- Click “Create Button”
If you test the page, you’ll see that it ends up immediately to the right of the search bar. Do not click on the button just yet.
Now go to the page with the form and open the process in rendering (Read Emp).
- Set “Condition Type” to “Value of item / column in expression 1 = expression 2″.
- Set Expression 1 to P4_MODE.
- Set Expression 2 to U
- Apply Changes
This ensures that we’re only reading a row from emp when updating data. When inserting, there is no data to be read and displayed.
You may also want to set the same condition on the page item for empno to hide it when inserting as there is no empno to display then.
Test the function now by running the report page and click on the “Add” button. It adds a row with the name you enter on the form. The rest of the columns will have null as their value. Adding more columns in the procedures and on the form is an exercise left to the reader.
With the function now supporting insert and update, it is time to add the delete function so we can get rid of some of the test employees we’ve created. This function will be implemented only on the form and by adding supporting code in the package. Thus, the delete will be performed by clicking on the link and then clicking on a delete button on the form.
To implement this we’ll update the implementation of the write_emp procedure once more.
procedure write_emp(p_in_empno in emp.empno%type
,p_in_mode in varchar2
,p_in_emp_rec in t_emp_rec) is
begin
case p_in_mode
when 'I' then
insert into emp
(empno
,ename)
values((select max(empno) + 1 from emp)
,p_in_emp_rec.ename);
when 'U' then
update emp
set ename = p_in_emp_rec.ename
where empno = p_in_empno;
when 'D' then
delete from emp
where empno = p_in_empno;
end case;
end write_emp;
With the package updated, our next step is to change the code in the “Run Stored Procedure” process on the form page.
declare
in_emp_rec tb_access.t_emp_rec;
v_mode varchar2(1);
begin
in_emp_rec.ename := :p4_ename;
v_mode := :p4_mode;
if :REQUEST = 'DELETE' then
v_mode := 'D';
end if;
tb_access.write_emp(p_in_empno => :p4_empno
,p_in_mode => v_mode
,p_in_emp_rec => in_emp_rec);
end;
The code is now changed such that a click on the delete button (not implemented yet) will set the mode to D. This is done by checking the REQUEST which we will set to DELETE for the button we’ll create now. REQUEST is a standard attribute available on buttons and branches in APEX. By setting it to a unique value, we can check for different invocation methods in code. This allows us to deal with different scenarios with just one block of code.
To add the button, follow these steps.
- On the form-page, right-click on “Form on Stored Procedure”.
- Select “Create Region Button”
- Set “Button Name” and “Label” to “Delete”.
- Click Next
- Set “Position” to “Top of Region”
- Set Alignment to “Left”
- Click Next
- Click Next (again)
- Click Create Button
The value of “Button Name” is used to set REQUEST when the page is submitted. With this in place our form should now support deleting rows also. Update the branch on the page to not have a condition on the button pressed. Take it out for a spin.
There is one thing left to do, hide the delete button when the form is invoked to add a row.
- Right-click the delete-button under rendering.
- Select “Edit”.
- Scroll down to conditions.
- Set “Condition Type” to “Value of item / column in expression 1 = expression 2″.
- Set Expression 1 to P4_MODE.
- Set Expression 2 to U
- Apply Changes
Now you’ve got a CRUD solution based on an interactive report with a form that supports insert, Update, and Delete. The total amount of code required is very small and you still have isolated the APEX-application from changes to the table structure by using a view, even the record can have fields added to it without impacting the functionality of your application. Developing using the demonstrated methods here allows you to create these applications almost as fast as with making them table based, but you have much more control and can make a lot of changes without touching the application,
Previous post hidden
I have hidden my previous post on the explicit request by Oracle Security Team. If you did read it or have it in your RSS, please do not forward or talk about it online until further notice. I’m not sure it is the right thing to do, but I have promised Oracle to not publish it for now. Please help me keep that promise.
Can table security be circumvented with a view? (Bug for peer review)
My collegue Daniel Ekberg stumbled on a very strange issue the other day. After having tested it in three separate environments and on both 10g and 11g (11.2.0.1 and 11.2.0.2) we have resigned to believing that this may actually be a bug. If it is, it is a LARGE one.
Essentially we can perform insert, update, and delete on a table on which we have only been given select rights. Yes, it sounds as if it really wouldn’t be that easy. All we need is select as and ability to create views.
Here is the setup we use.
First lets create the test users.
As a DBA-user:
create user user1 identified by “test”;
grant connect,resource to user1;
create user user2 identified by “test”;
grant connect,resource,create view to user2;
As user user1 execute these commands
create table tbl1 (col1 number,col2 number,col3 varchar(30));
grant select on tbl1 to user2;
insert into tbl1 select 1,level+2,null from dual connect by level <=4;
commit;
select * from tbl1;
That creates a table with three columns and adds for rows where the third column is null.
As user user2 execute these commands
create view view1 as select * from user1.tbl1;
update view1 set col3=’Whoops’; — Will not work, not granted update.
create view view2 as
select * from view1
where (col1,col2) in (select max (col1),col2 from view1 group by col2);
update view2 set col3=’Whoops’; — This now works, why???
commit;
select * from user1.tbl1;
The first view is a plain select * on the table user1 created. We can of course not update this table. However, the second view is based on the first and it uses an aggregation in the where clause. This view is now somehow allowing the user to update the data even though the user has only been granted select on this table.
Have you seen this. Is there a reasonable explanation? Is there a known fix?
Setting up the user to not have resource but only create session does not change this, the same escalation of acces occurs.
Please test the above in your environments and report on OS and version of database where you see this or if you find that it does not work as stated above in your environment.
Our base assumption here is that we have encountered a bug that is a giant security hole, but we hope to find that we’re missing something. This is why we’re throwing this out in the community to get a review and see if we have lost our minds completely here.



