HP NZ Database Services

2009/06/28

The Secret Life of LOBs

Now and then we are passed a problem that other people have been working on for a while, but despite lots of changes, it hasn’t been resolved.  In some cases this makes it impossible to obtain a clear description of the symptoms and history, and may even have introduced other issues.  I much prefer a ‘diagnostic approach’ rather the ‘try the usual suspects first method’.  One might ‘get lucky’ by simply changing a common tuning parameter, but I prefer to consider the risks, costs and testing effort the customer will incur, and so attempt to understand the problem and provide the best solution.

The case I’m going to briefly cover in this post was one of these problems with more rumour than substance to the problem history.  Significant changes had been made to the Oracle database, including changing the optimiser back and forward between cost and rule and cursor sharing between exact and force.  I’m not even sure if the problem I investigated was the original one or a side-effect of the various attempts at a solution.

Obvious Symptoms

The highest component of response time was the enqueue wait event.

There were about ten blocked sessions with the same SQL statement:

UPDATE dummytable SET nextId = nextId + 1 WHERE schemaId = 123;

My first thought was that the locking was due to the application incrementing a number field instead of using a sequence.  Then I noticed that each blocker relinquished the lock after a minute or so, and one of the other ten sessions became the blocker for another minute, and so on.  Assuming the application design hadn’t just changed, it was probably this 60 second delay while holding a lock that was at the heart of the problem.

The Not-so-Obvious

Finding what was happening during that 60 second delay was trickier than usual.  How so?  I’ll start by sharing a portion of the trace file.  Pay attention to the timing parts in blue.

PARSE #1:c=0,e=980,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=3,tim=31325192050363
EXEC #1:c=0,e=158,p=0,cr=4,cu=1,mis=0,r=0,dep=0,og=3,tim=31325192050680
WAIT #1: nam='SQL*Net message to client' ela= 1 p1=1413697536 p2=1 p3=0
FETCH #1:c=10000,e=64,p=0,cr=4,cu=0,mis=0,r=1,dep=0,og=3,tim=31325192050859
WAIT #1: nam='SQL*Net message from client' ela= 381 p1=1413697536 p2=1 p3=0
WAIT #33: nam='db file sequential read' ela= 103 p1=15 p2=1542515 p3=1
WAIT #33: nam='db file sequential read' ela= 317 p1=15 p2=1542520 p3=1
WAIT #33: nam='direct path write (lob)' ela= 178 p1=10 p2=2008755 p3=1
WAIT #0: nam='SQL*Net message to client' ela= 7 p1=1413697536 p2=1 p3=0
WAIT #0: nam='SQL*Net message from client' ela= 596 p1=1413697536 p2=1 p3=0
STAT #1 id=1 cnt=1 pid=0 pos=1 obj=0 op='FOR UPDATE  (cr=4 r=0 w=0 time=44 us)'
STAT #1 id=2 cnt=2 pid=1 pos=1 obj=42798 op='TABLE ACCESS BY INDEX ROWID table_a (cr=8 r=0 w=0 time=97 us)'
STAT #1 id=3 cnt=2 pid=2 pos=1 obj=42799 op='INDEX UNIQUE SCAN table_a_index (cr=6 r=0 w=0 time=57 us)'
=====================
PARSING IN CURSOR #1 len=66 dep=0 uid=23 oct=6 lid=23 tim=31325202616132 hv=4020366925 ad='ff8b078'
UPDATE table_a SET C2 = EMPTY_CLOB() WHERE C1 = '000000003637834'
END OF STMT
PARSE #1:c=0,e=2286,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=3,tim=31325202616118
EXEC #1:c=0,e=670,p=0,cr=7,cu=6,mis=0,r=1,dep=0,og=3,tim=31325202616968
WAIT #1: nam='SQL*Net message to client' ela= 2 p1=1413697536 p2=1 p3=0
WAIT #1: nam='SQL*Net message from client' ela= 313 p1=1413697536 p2=1 p3=0
STAT #1 id=1 cnt=1 pid=0 pos=1 obj=0 op='UPDATE  (cr=7 r=0 w=0 time=475 us)'
STAT #1 id=2 cnt=1 pid=1 pos=1 obj=42799 op='INDEX UNIQUE SCAN table_a_index (cr=3 r=0 w=0 time=114 us)'
=====================
PARSING IN CURSOR #1 len=63 dep=0 uid=23 oct=3 lid=23 tim=31325202618985 hv=1434031237 ad='156ec818'
SELECT C2 FROM table_a WHERE C1 = '000000003637834' FOR UPDATE
END OF STMT
PARSE #1:c=0,e=1203,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=3,tim=31325202618977
EXEC #1:c=0,e=217,p=0,cr=4,cu=1,mis=0,r=0,dep=0,og=3,tim=31325202619378
WAIT #1: nam='SQL*Net message to client' ela= 1 p1=1413697536 p2=1 p3=0
FETCH #1:c=0,e=90,p=0,cr=4,cu=0,mis=0,r=1,dep=0,og=3,tim=31325202619616
WAIT #1: nam='SQL*Net message from client' ela= 505 p1=1413697536 p2=1 p3=0
WAIT #34: nam='db file sequential read' ela= 146 p1=15 p2=1473089 p3=1
*** 2008-11-05 17:14:00.054
WAIT #34: nam='db file sequential read' ela= 950 p1=15 p2=1473090 p3=1
*** 2008-11-05 17:14:14.217
WAIT #34: nam='db file sequential read' ela= 138 p1=15 p2=1473091 p3=1
WAIT #34: nam='SQL*Net more data from client' ela= 110 p1=1413697536 p2=1 p3=0
WAIT #34: nam='direct path write (lob)' ela= 183 p1=10 p2=1595041 p3=1
WAIT #0: nam='SQL*Net message to client' ela= 8 p1=1413697536 p2=1 p3=0
WAIT #0: nam='SQL*Net message from client' ela= 579 p1=1413697536 p2=1 p3=0
STAT #1 id=1 cnt=1 pid=0 pos=1 obj=0 op='FOR UPDATE  (cr=4 r=0 w=0 time=56 us)'
STAT #1 id=2 cnt=2 pid=1 pos=1 obj=42798 op='TABLE ACCESS BY INDEX ROWID table_a (cr=8 r=0 w=0 time=135 us)'
STAT #1 id=3 cnt=2 pid=2 pos=1 obj=42799 op='INDEX UNIQUE SCAN table_a_index (cr=6 r=0 w=0 time=81 us)'
=====================
PARSING IN CURSOR #1 len=66 dep=0 uid=23 oct=6 lid=23 tim=31325238065528 hv=3338015855 ad='165cdab0'

We can see that the blocking session is eating up time:

31325202616132 – 31325192050859 = 10565273, or 10 seconds.

31325238065528 – 31325202619616 = 35445912, or 35 seconds.

Whatever is causing the delay, it hasn’t been assigned a wait event name.  One clue the trace does have for us is that there are reads to a couple of segments during the mysterious delay and just before writes to a LOB.  Using DBA_EXTENTS we can see that these segments are LOB indexes for TABLE_A.

Watching the blocking sessions revealed that they clock up a lot of CPU and consistent gets.  The LOB indexes for TABLE_A have high logical read counts. The buffer cache is also full of copies of the LOB index blocks. The picture is becoming clearer – something must be wrong with the LOB indexes.

Theory, Confirmation, Resolution

My best guess at this point was that this performance issue was caused by a big delete of data in TABLE_A.  The LOB chunks were marked as deleted (not free), so each time there was a new insert Oracle had to find re-usable space, change the deleted chunks to free and update the LOB index, (as well as manage the various bitmap blocks and undo blocks).  According to the Metalink Note:365156.1 this process can take a long time because it “has to traverse the empty LOB INDEX leaf blocks created by all the previous transactions”.  This would explain the high number of logical reads recorded against the LOB indexes during each insert/update.
This Oracle database had a long statspack history, so I was able to write some ad-hoc queries to generate a graph of enqueue waits over time to see when the first big locking problems really started, and I was also able to find some large delete statements on TABLE_A.  The archive log history showed a huge spike at the time of one of these deletes, and so it looked like the theory was right.  This data helped to jog a few memories and we learned that 90% of TABLE_A’s data had been deleted around the time the ‘slowness’ started.

I can hardly do the explanation justice here, but if you know how Oracle LOBS and indexes work in general, then this may make sense:
LOB segments don’t use undo like everything else, but instead mark old chunks of LOB data as re-usable and write over it a later time when an insert or update needs some space.  These re-usable chunks are tracked by the LOB index – the same LOB index that tracks the current chunks of LOB data.  The current data is in the left side of the index and the re-usable/undo/consistent read data is in the right side.  When data is deleted, (or updated), the old LOB locator entries are deleted from the index and new re-usable index rows are entered into the right side of the index.  The emptied leaf blocks on the left side of the index are reclaimed and used for leaf blocks during inserts, while the “re-usable” index entries are removed from the tail of the right half of the index with each insert.  After a big delete (or many small ones in a row) the freelist is full of blocks from the left side of the index… so as data is inserted again over time, a gap opens up with empty leaf blocks between the left and right sides (used and re-usable) of the index.  The empty blocks are on the freelist but not removed from the index structure until they are taken from the freelist.
So… when an insert happens it checks for re-usable space.  The index is range scanned for the first free chunk.  (Re-usable space is indexed chronologically).  Unfortunately,
this process takes a lot of CPU time to complete when there are large numbers of empty leaf blocks in the index and multiple LOBs in the same table; over a minute in the example in this blog.  Sometimes the delay can be smaller – just a few seconds, and so even harder to identify.

A dump of the index showed the problem clearly, (although it helped to a dump of a few of the index blocks at key points to be sure where the current data index entries stopped and where re-usable chunk list started).

A rebuild of the LOBs for TABLE_A resolved this problem.    I set the LOBs to cache before rebuilding them to speed the process up.

Moral of the Story

My recommendation to prevent this from happening again was the usual ‘rebuild/repack database segments after mass deletions, but even better is to purge in small amounts regularly’.  This case was an extreme example of why that is important.

A performance tuning lesson emphasised here is to use a multiple diagnostic tools, deductive reasoning and research to identify the real problem so that the best solution can be found and a strategy for preventing its re-occurrence can be designed.  Also, tuning LOB related activity can be tricky for those relying on the standard tuning tools and statistics.  In a future post, I’ll give another example of how LOB activity cause performance problems that aren’t obvious.

2009/06/24

Firewalls vs Users

Filed under: UNIX / Linux — hpdba @ 8:22 pm
Tags: , ,

While firewalls have their uses, they can also be an obstacle for users.

I have been called out to customer sites a few times, only to find the problem was a software firewall blocking useful traffic.  Once, it was the Windows firewall blocking FTP, once it was Zone Alarm blocking SQL*Net, and a couple of times the Linux iptables firewall has been to blame.

Here is a recent example of a software firewall that was inappropriately configured:

We wanted to install Oracle on an Oracle Enterprise Linux server, and needed to use VNC to do so.  We started up vncserver on the Linux server and tried to connect to it with a VNC client from a Windows PC.  It didn’t work.

So… we started to investigate.

From the PC command prompt we tried telnet NN.NN.NN.NN 5901 .  This timed out.  Hmmmm… not a “Connection refused” so it appeared to be a firewall issue.  The PC’s Windows firewall already had a VNC exception, (part of the VNC client installation), but even turning it off didn’t make a difference.

Next, we checked that vncserver was running on the Linux box, and ran netstat -a to confirm that vncserver was listening on port 5901.  All was as expected.  At this point we suspected a physical firewall, but once that was ruled out, it only left a Linux software firewall.  The commands show how to confirm this and disable the firewall.  (Rather than to reconfigure it).

[root@akadevsrv01 ~]# /etc/init.d/iptables status
$Table: nat
Chain PREROUTING (policy ACCEPT)
num  target     prot opt source               destination
Chain POSTROUTING (policy ACCEPT)
num  target     prot opt source               destination
1    MASQUERADE  all  --  192.168.122.0/24     0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
$Table: filter
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:53
2    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:53
3    ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:67
4    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:67
5    RH-Firewall-1-INPUT  all  --  0.0.0.0/0            0.0.0.0/0
Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination
1    ACCEPT     all  --  0.0.0.0/0            192.168.122.0/24    state RELATED,ESTABLISHED
2    ACCEPT     all  --  192.168.122.0/24     0.0.0.0/0
3    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
4    REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-port-unreachable
5    REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-port-unreachable
6    RH-Firewall-1-INPUT  all  --  0.0.0.0/0            0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
Chain RH-Firewall-1-INPUT (2 references)
num  target     prot opt source               destination
1    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
2    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 255
3    ACCEPT     esp  --  0.0.0.0/0            0.0.0.0/0
4    ACCEPT     ah   --  0.0.0.0/0            0.0.0.0/0
5    ACCEPT     udp  --  0.0.0.0/0            224.0.0.251         udp dpt:5353
6    ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:631
7    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:631
8    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
9    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:21
10   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:25
11   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22
12   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:443
13   REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited
[root@akadevsrv01 ~]# /etc/init.d/iptables stop
$Flushing firewall rules:                                  [$  OK  ]
$Setting chains to policy ACCEPT: nat filter               [$  OK  ]
$Unloading iptables modules:                               [$  OK  ]
[root@akadevsrv01 ~]# chkconfig iptables off
[root@akadevsrv01 ~]# /etc/init.d/iptables  status
$Firewall is stopped.

2009/06/20

Proving the Performance Problem is Outside of the Database

I have often been asked to investigate a performance problem, only to find to my disappointment that the main component of response time is outside of the Oracle database.  (This robs me of a challenging tuning exercise, and may limit how useful I can be to the customer).  The problem still needs to be identified and fixed, but this will only happen if the appropriate area is examined, so the DBA may have to convince the relevant technical staff to have closer look.

The “idle” wait event SQL*Net message from client is often ignored, but it is relevant during the period when the user is waiting for the application to respond.  It can be used to show that the issue is not one of database or SQL tuning, but elsewhere, eg networking devices or application server performance.  (Admittedly, the DBA may still be able to help in some cases, eg tuning the connection method, SQL*Net configuration and pre-fetch settings).

It doesn’t take long to trace a user’s session (with wait events), isolate the period during which the user is waiting for the application to respond, and then use tkprof to generate a report showing how much time was due to the database working and how much could be attributed to network/application server (SQL*Net message from client).

Sometimes this is enough for the DBA to stop investigating, but other times the people responsible for the network or application server need more evidence.

Monitoring the load (eg CPU) on the application server isn’t always going to be conclusive, especially when there are lots of round trips between the application server and the database.

In some cases the application server has been found to be the main cause of the delay, eg JVM garbage collections halting processing.  In the example I use below, it was proved to be a network problem, despite initial claims that it was not.

A useful next step in the situation where the Oracle database has been ruled out as a cause of the slow performance, is to narrow it down further by measuring the network packet arrivals and departures from the perspectives of the application server and the database server.  For one particular case, I used snoop and then fed the data into a free program called WireShark to analyse the results.

The snoop measurements from the Oracle database server side showed fast responses to incoming packets, which matched the SQL trace data.
The snoop measurements from the application server side also showed fast responses to incoming packets.

These results indicated that the ‘network’, (TCP stack, NIC, cable, switch, port, etc), was the cause of most of the delay.  I just needed a little more lower level evidence, so I tried pings from both servers, which showed RTTs of >10ms.  The servers were on different subnets, so there had to be at least one switch or router in between.  We needed more granularity.
A traceroute from each server showed this:

[dbserver:root]# traceroute appserver
 traceroute to appserver (nn.nn.40.49), 30 hops max, 40 byte packets
 1  nn.nn.38.2 (nn.nn.38.2)  0.521 ms  0.396 ms  0.322 ms
 2  appserver (nn.nn.40.49)  11.968 ms  8.708 ms  13.582 ms
[appserver:root]# traceroute dbserver
 traceroute to dbserver (nn.nn.38.166), 30 hops max, 40 byte packets
 1  nn.nn.40.2 (nn.nn.40.2)  1.149 ms  0.997 ms  0.740 ms
 2  dbserver (nn.nn.38.166)  11.134 ms  12.107 ms  13.027 ms

Hop 1 is to a switch/router and was consistently fast, proving that the cause wasn't local to one of the servers.
Hop 2 was always slow, between the switch/router and the remote server, in both directions. It must be the switch causing the problem!

These results were repeatable, so we had enough reason to ask the network engineer to take another look.

The network engineer found that the switch/router suffered from a "known problem".   Something about other ports on the same line card running at slower speeds but using the same buffer.... I think that means that the output queue might have been slower when sending large amounts of data to a slower network.

2009/06/16

Deadlock Investigation Example

Filed under: Troubleshooting and Errors — hpdba @ 3:08 am
Tags: , , ,

DBINST started to display the following error message:

ORA-00060: Deadlock detected. More info in file /u01/app/oracle/admin/DBINST/udump/DBINST_ora_1705.trc.

When the deadlocks occurred, the application was failing and logging errors too.

Oracle Concept Manual: “A deadlock can occur when two or more users are waiting for data locked by each other. Deadlocks prevent some transactions from continuing to work. … Oracle automatically detects deadlock situations and resolves them by rolling back one of the statements involved in the deadlock, thereby releasing one set of the conflicting row locks.

The start of the trace files were similar to each other, so it appeared to be the same piece of code causing the problem:

DEADLOCK DETECTED
Current SQL statement for this session:
delete from PORTDETAIL where ID=:1 and version=:2
The following deadlock is not an ORACLE error. It is a
deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following
information may aid in determining the deadlock:
Deadlock graph:
---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TM-0000e7bc-00000000        28     132    SX   SSX       25     135    SX   SSX
TM-0000e7bc-00000000        25     135    SX   SSX       28     132    SX   SSX
session 132: DID 0001-001C-0000052F     session 135: DID 0001-0019-00000B29
session 135: DID 0001-0019-00000B29     session 132: DID 0001-001C-0000052F
Rows waited on:
Session 135: obj - rowid = 0000E7DC - AAAOfcAAFAAAAjIAAA
(dictionary objn - 59356, file - 5, block - 2248, slot - 0)
Session 132: obj - rowid = 0000E7DC - AAAOfcAAFAAAAjIAAA
(dictionary objn - 59356, file - 5, block - 2248, slot - 0)
Information on the OTHER waiting sessions:
Session 135:
pid=25 serial=1301 audsid=134658 user: 60/EGSCHEMA
O/S info: user: , term: , ospid: 1234, machine: dbserver.local
program:
Current SQL Statement:
delete from PORTDETAIL where ID=:1 and version=:2
End of information on OTHER waiting sessions.

I looked noticed that this was a bit different than other deadlocks I’ve seen; it was dead locking on table locks instead of row locks (transactions). This made me suspect a foreign key wasn’t indexed. (It could be the result of an application doing something unusual, like issuing lock table commands or DDL even).

So in this case we should ignore the “Rows waited on” section, which is useful when we have blocking transactions (TX).

I checked which table was locked:

TM=table lock (DML lock)
0x0000e7bc=59324

select owner, object_name from dba_objects where DATA_OBJECT_ID=59324;
OWNER
------------------------------
OBJECT_NAME
------------------------------
EGSCHEMA
PORTSERVICEORDER

Note that this isn’t the table in the SQL statement for either session: PORTDETAIL.

So, is there a trigger or a foreign key that could cause a lock on PORTSERVICEORDER when a row from PORTDETAIL is deleted?

@dd
EGSCHEMA
PORTDETAIL
TYPE              NAME                           OWNER                          'SOF 'REFERENCED'  NULL
----------------- ------------------------------ ------------------------------ ---- ------------- ------------------------------
INDEX             PD_PSO_IDX                     EGSCHEMA                       HARD ON_OBJECT
INDEX             PK_PORTDETAIL                  EGSCHEMA                       HARD ON_OBJECT
TABLE             PORTITEM                       EGSCHEMA                       HARD FK_REF_OBJECT FK_PORTITEM_PORTDETAIL
TABLE             PORTSERVICEORDER               EGSCHEMA                       HARD FK_REF_OBJECT FK_PSO_APPROVEDPORTDETAILS
TABLE             PORTSERVICEORDER               EGSCHEMA                       HARD FK_REF_OBJECT FK_PSO_GSPPORTDETAILS
VIEW              VIEW_GC_LC_FOR_COMPLETED_PORTS EGSCHEMA                       SOFT REFERENCED

Yes! Two foreign keys.

Are these foreign keys indexed?

@$DBA_SQLPATH/apt/missing_fk_indexes.sql
TABLE_NAME                                       COLUMN_NAME
------------------------------------------------ ------------------------------
EGSCHEMA.PORTSERVICEORDER                        APPROVEDPORTDETAILS
EGSCHEMA.PORTSERVICEORDER                        GSPPORTDETAILS

No, neither of them are indexed.

My Interpretation

A row is being updated or deleted from the child PORTSERVICEORDER, and then a row is deleted from the parent table PORTDETAIL in the same transaction. There is no index on the foreign key so a lock is taken on the whole PORTSERVICEORDER table. When two sessions attempt these same steps concurrently, they would deadlock because they both hold an SX (row exclusive) lock in PORTSERVICEORDER and both are asking for an SSX (share sub-exclusive) lock on PORTSERVICEORDER.

In general, foreign keys should be indexed unless the parent table has very little DML on it. So… I asked the developers to index these foreign keys and we haven’t had deadlocks since.

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.