An Index Only Performs How Much Work ??? November 12, 2009

One of the main reasons suggested for performing periodic index rebuilds is to improve “performance”. After rebuilding indexes, applications now run so much faster. Users notice a significant improvement in performance. And so on.

There are of course situations when rebuilding specific indexes can indeed improve performance, here’s but one example I’ve discussed previously.

However, the question I always ask when someone claims an index rebuild has made things run faster is to simply ask why. Why is such a simple, but powerful question. Why have things improved ? What has specifically changed as a result of rebuilding the index, that Oracle has now reduced the overall work associated with performing the activity, to the point that things run noticeably faster.

Knowing why is really important because it confirms that indeed there was an improvement and that it was indeed associated directly with the index rebuild. It means when a similar situation arises again, you know how to directly resolve the problem appropriately and effectively next time. Also knowing why means you can determine the specific root cause, perhaps preventing things from deteriorating so bad in the first place, such that rebuilding the index becomes unnecessary. Prevention being the best cure …

Now the most common answer I get for why rebuilding an index has been so beneficial is because the index is now so much smaller after the rebuild that the overheads of reading the index have substantially reduced. If the index is smaller, it means one can read the same amount of index related data with less I/Os. Less I/Os means better performance.

For example, if you can reduce the index by half, you can fit the index into only half the number of leaf blocks and that’s a 50% performance improvement right there.

Well, firstly it assumes that the index has indeed reduced by half. It would actually be a relatively unusual index for it to be so poorly fragmented or for it to have so much “wasted” space that it could be rebuilt into only half the number of leaf blocks.

Possible but somewhat unusual.

However, it also assumes that by having a 50% performance improvement, reading the index blocks constitutes the vast majority of the work. Again possible in some scenarios.

With most index related activities though, reading the index blocks actually constitutes only a small percentage of the overall work. In most cases, the index only contributes a very small percentage of the overall I/O activity. Therefore by potentially only reducing a small percentage of the overall work by rebuilding just the index, the overall impact is generally going to be minimal.

I thought I might perform some basic mathematics to illustrate and put into perspective just what little impact index rebuilding can have in improving performance, even if by doing so the index dramatically reduces in size, because the index itself actually constitutes only a small percentage of the overall costs.

Let say we have one of these more unusual indexes that is so poorly fragmented that it has approximately 50% wasted space throughout the entire index structure. Let’s say rebuilding such an index reduces the overall size of the index by half.

Before the index rebuild, an index has 50% of wasted space and say:

Height of 3

1 Root Block

50 Intermediate Branch Blocks

20,000 Leaf blocks

After the rebuild, the index has no wasted space and has now:

Height of 3

1 Root Block

25 Intermediate Branch Blocks

10,000 Leaf Blocks

Let’s assume the table contains 2M rows and that they fit in 100,000 blocks (i.e. the index is about 1/10 that of the table and the average row size is such that we fit say 20 rows on average per block). Let’s also assume there’s nothing special about this index and that it has an “average” clustering factor of 1M, before and after the rebuild 😉 1M being somewhere in the middle of possible clustering factor values.

The first thing to note is that the height remains the same after such a rebuild, even though the index is only now half the size. It would be extremely unlikely and the index would have to be particularly small and within a very narrow range of sizes for all the contents of all the intermediate branch blocks to fit within just the one root block. The only way for the index height to reduce down from 3 would be for the contents of all intermediate branches to fit within the root block. Possible, but again quite unusual.

OK, let’s look at the cost of various scans before and after the rebuild, using the index costing formula I’ve discussed recently.

If we’re after just one row (a unique index perhaps), then to read the one row before the rebuild would be:

Or in percentage terms, a reduction of I/Os of approximately 1%. That’s just 1 tiny little percent …

So even an index that accesses 10,000 rows, a reasonable number and at 0.5% a reasonable percentage of the overall table, even an index that has reduced in size by half, a substantial reduction in size, only reduces the overall number of I/Os by an unimpressive 1% for such a query in the above scneario.

Would reducing I/Os on such a query by 1% really improve performance “substantially” ? Will users really notice much of a difference ?

It’s of course all in the numbers and in how much work the actual index is performing, in how many I/Os are actually performed by the index itself and in how much of a reduction in the overall I/Os an index rebuild will actually contribute. I’ll leave it to you to plug in different ranges of selectivity to see what impact rebuilding such an index with the above characteristics might have.

The point is that for the vast majority of index related queries, most of the work is performed in getting the data out of the table, not the index.

Therefore, reducing the size of an index, even by possibly a substantial amount, may not necessarily reduce the overall I/Os associated with a query if the index only performs a tiny fraction of all the work. You could eliminate the index entirely and providing Oracle could still magically retrieve just the 0.5% of rows of interest in the above example, performance for such a query would unlikely improve “significantly”.

So not only must an index reduce in size but the activities associated with directly reading the index must constitute a significant proportion of the overall work (eg. fast full index scan, index only scans, etc.) or something else must have changed for performance to have improved “significantly”.

Advertisements

Share this:

Like this:

Related

From statistical point of view you are right. But what about your experience ? I got a impression that for that bad index rebuild can help. Please correct me if I wrong. If index size was 20000 and after rebuild it was 10000 it mean that there was 50 % of unused space (ex. due to concurrent rows deletion and inserting). For unknown number of index block number of rows inside that block can be smaller that average 100 rows per index block (2M rows /20000 blocks) and for another one much bigger. As I understand number of LIO will be depended on condition values – for some conditions it can be better than for others (ex. for id=1 will be OK, for id=1000 not OK). After index rebuild your statistics calculations will be independent from condition values because all index blocks will have more less same number of rows.

For sure that kind of index is very unusual and for common indexes rebuild can help only in case of Index Full Scans

In my theoritical example, I’ve assumed the index is fragmented evenly throughout the entire index structure. Therefore, I’ve assumed you would save approximately 1/2 the associated index I/Os during an index scan, regardless of what part of the index you scan. Sure, there may be examples where an index is more fragmented in some parts of the index strucure and so more fragmented for specific index values than others.

However, in my experience, it’s quite rare for an index be so fragmented AND for an associated index related scan to constitute a significant proportion of the overall I/Os.

I think LIO on proper index-access (unique or range scan) will always be largely determined by just the B-Level and rebuilding will not make much difference, not in LIO on that index.
And a rebuild index has a tendency to re-grow to its “stable” size or even out-of-control anyway. hence limited benefit of rebuilds.

I’m sure you can come up with plenty exceptions to my statements here, but in general…B-Level, and not index-size determine the LIO.

However, I can see one additional case where significantly reducing the size of an index might be usefull: Efficient use of db_cache.
If buffers in db_cache are needed elsewhere, space-wastage is bad.
The reclaimed space will benefit other segments, at least until the application DML grows the index segment back to its bigger size

Just my two eurocents.
Nice to see you back on topic!
Look forward to some interesting posts still.

A small scan repeated many times within say a nested loop is going to be impacted by the height.

LIOs as a result of a large range scan, accessing many 1000s of rows is more likely to be impacted by the leaf blocks.

The buffer cache is certainly one consideration, however generally a moderate amount of additional memory is likely to counter-balance the net effects of additional PIOs in a more cost effective manner than constant rebuilding.

It’s arguable that making the index smaller improves the chance of avoiding physical reads, either on the index itself or on other segments (such as the table being accessed). It might be useful to have a look at v$segment_statistics to check the rate at which physical reads are being incurred as it’s generally pretty low, in my humble experience.

As I said to Piet, the caching characteristic of the database is certainly a consideration. However, note again in my example that it’s the table that generate by far the greater LIOs (and hence likely PIOs). If detected as an issue, with modern memory capabilities, a moderate increase in the buffer cache is often a cheaper, more cost effective solution anyways than constant index (and table) reorgs.

I think it is fair to say that if the application has many queries that use the index, resulting in index full scan or index fast-full scan, then the index rebuild, that results in large reduction in branch and leaf blocks, will help a lot (aka make all those queries perform better).
One question though. Is it possible that index rebuild will help (or appear to help) in cases where table is accessed using index having bad clustering factor, which results in index access contributing to the large percentage of work?
Something like a Throw-Away cases

It’s actually the other way around. An index with a good clustering factor is more likely to benefit from a rebuild as it therefore contributes a higher percentage of the overall LIOs.

Change the CF in my example to a better value and redo the maths.

The other point I would make is that if many queries use either a full index scan or a fast full index scan, especially in an OLTP environment, especially if the index is large, then something is likely not right as both are relatively expensive operations to perform. I would concentrate on why such an index is being constantly used in such a manner rather than simply rebuilding the index.

Not sure I understood the point. I thought the Throw-away case as mentioned in the documentation was returning many more no. of rows (thereby visiting many blocks) than those returned by corresponding table access. Isn’t it possible that such an index is reduced to half because of rebuild? If yes, won’t that have any positive impact on the query?
I must admit though I am bit confused about how exactly a plan like that mentioned in the documentation can occur. Tried to search for an example but could not find one. Would appreciate if you can point me to an example.

You have an index on customer_id, and an index on order_date. The optimizer chooses to use the the index on customer_id. But if the average customer has placed 150 orders, but only one in the last week, the row count statistics would be similar to the row counts shown in the throwaway example: 150 rows from the index for each row from the table.

In that case, rebuilding the index would be a total waste of effort, since almost all the work of the query would be done visiting 149 rows in the table to discard them because their dates were too early. The correct solution, obviously, would be to create the correct index, viz: (customer_id, order_date).

Now – this multi-column index probably would run at about 50% space utilisation (if every customer placed a lot of orders over a long period of time), but you might still be perfectly happy to ignore that “wastage” for various reasons – including the possibility that by the time your data size was large enough for it to matter you won’t want to rebuild an index that’s that big because of the time it takes to complete the job.

Thank you Jonathan for answering Narenda’s query and apologises to you Narenda for missing the link that was actually just in your previous post.

Again it comes back to my main point that if the I/Os associated directly with index blocks only make up a small percentage of the overall rows, then an index rebuild is unlikely to be benefical for such a query.

Also note that an index with a poor CF is even less likely to benefit from a rebuild as a poor CF generally means an even greater proportion of table block visits vs. index block visits.

Thank you for your detailed explanation and my apologies for late reply.
I just tried to create an example as you had described in order to observe the “Throw-away” behavior. But I could not get the results. Can you please help me?
SQL> create table orders as select object_id oid, object_name odesc,object_type cid, created from all_objects ;

Not sure what bit of the documentation you’re referring to ? If you can post a link to the specific part of the doco, I’ll see what relevance it has to the point I’m trying to make, which is simply that if index related I/Os are only (say) 1% of the total work within a execution plan step, then improving the index structure will only make a 1% improvement at best.

Let’s consider the case of an index that has a certain subset of blocks that are accessed often. They would be sitting out there in the SGA buffers. If those blocks are condensed to half their former size, that would be half of that part of the SGA, right?

Now, those are accessed often, so they would be pretty much always there in the SGA.

Now, let’s consider that a number of indices have these types of characteristics. Wouldn’t there be a case where the difference between these two size groups makes a difference on what other blocks are being cycled out of the buffers? There could be a large difference in performance when you compare a thrashed I/O because these popular blocks are big in toto, versus cpu buffer access. Given a large system with multiple apps that all have built up “kinda wrong” indexes, it doesn’t have to be a narrow edge case, does it?

So how rare is this kind of situation, where blindly rebuilding all indices could make a difference? More importantly, how would one quantify it to see if it is the situation?

I try to keep my examples as simple as possible, so they can illustrate the point in a clear manner. You’re confusing things a tad with the join and the type of join used. To see throwaway in action, you really only need the one table retrieving a number of rows via an index but filtering most of them out after hitting the table.

So basing this on your example:

SQL> create table bowie as select * from dba_objects order by object_name ;

The index is retrieving a whole heap of rows based on the date predicate but you filter (throwaway) most of them away because of the filer condition on object_id.

Ideally, you would have an index on either both columns or in this case just the objkect_id column.

However, getting this back to the point I make in the post, if you look at the trace file of such a query, you would notice most of the associated I/Os come from getting the data out of the table, not from reading the index:

Thanks for your response and example.
Apologies if my questions have diverted your original topic but that was not my intention. I was under impression that the “Throw-away” case can also occur if the index grows larger and eventually may reach a stage where index access will need to visit more number of blocks than the corresponding table access. I was wrong as I got confused between “number of blocks” and “number of rows”. The “Throw-Away” example talks about number of rows which will be influenced by the index column selectivity.
Your example clearly shows that even in “throw-away” case, the consistent gets to access index will be less than those to access corresponding table.

p.s. BTW, as I was writing this, I was wondering whether it is possible that index growing in size (i.e. more number of leaf blocks) will result in more number of consistent gets.

You won’t get a 50% improvement with most indexes, especially less so with some pctfree to spare, so most indexes won’t have these types of characteristics.

Next point is if caching issues are impacting performance, the far easier option is to simply increase memory allocation as necessary, rather than rebuilding indexes all the time, assuming you just pick those indexes that yield an actual benefit and reduce in size.

As many I/Os are associated with a table, why isn’t equal consideration given for tables considering they also “waste” space after (say) deletions and with pctused commonly set at 40% on most tables in non-ASSM tablespaces. With most tables, the tables themselves generate most of the I/O unless the table is heavily and almost exclusively accessed with the PK.

Finally, you can always check these things and see if excessive PIOs are causing a problem. v$segment_statistics has useful data in determining which indexes (or tables) have PIOs, v$sql gives you data on which sql is causing PIOs. One can benchmark the impact of index rebuilds by looking at the PIOs of any segment of interest (all if necessary) and any sql statements of interest and see if total I/Os significantly reduce and/or if total times for sql statements reduce.

So it can be quantified to a reasonable extent and it can be determined whether index rebuilds really are beneficial.

Are statements really running faster, key sql that are important to your business after rebulding that index or those indexes or all your indexes.

In my experience, rebuilding most indexes makes no measurable difference. However, rebuilding that specific index might make a measurable difference for that sql in that application. So I strongly suggest not rebuilding all your indexes when it’s only those handful that make a difference.

Basically, there’s no need to guess these things when one can actually know as Oracle is so well instrumented. If you know it’s made a difference, great. What was the difference, why the difference, you know. If you don’t know then you just don’t know.

However, you may get the same reduction in PIOs by simply increasing the buffer cache …

Yes, an index “growing” in size can result in additional LIOs if it means Oracle now has to visit more leaf blocks (and in an extreme case, an additional branch block) than it did previously.

In my last example in the blog piece, the index scan accessing 10,000 rows had to read an additional 50 leaf blocks before the index was rebuilt.

However, this represented only 1% of all the LIOs performed during the scan and so these additional I/Os were relatively trivial.

However, if the LIOs on the index consisted of a far greater percentage of the total LIOs, then perhaps possible savings would be more significant.

So like most things it depends but as you don’t generally save (say) the 50% during an index rebuild/coalesce and as index scans don’t generally consist of a high percentage of the overall I/Os unless it’s a reasonably large scan of a good CF index or a index only scan of some description or a tiny index scan, then the overall benefits of such a rebuild/coalesce are likely to be minimal.

Firstly, although they’re kinda equivalent, they’re not actually the same and are costed differently by the CBO.

The first statement without the equality condition has a selectivity costed by the CBO as being appromiately:

(2147483647 – lowest value) / (highest value – lowest value)

So the selectivity is calculated as being the % of rows in the range 2147483647 to the lowest value out of the total range of values.

However, with an equality condition, it’s costed as being:

(2147483646 – lowest value) / (highest value – lowest value)

PLUS

the selectivity of one additional value (the value of 2147483646 itself). It’s treated as if the query was:

col1 < 2147483646 or col1 = 2147483646

So this may not end up with the same cost (depending on how the data is distributed within the ranges) and so the plan might be different.

But you say the plans are the same …

So the question I have is as you have a significant number col1 = 2147483647, did you likewise have many values of 2147483646 and may many of them have been deleted ? It might be Oracle is forced to read through a whole bunch of leaf blocks that are actually empty until it finally gets to the first occurance of 2147483647 ? As it's reading each of the 19 partitions, it might only be in a few of them where this might be happening ?

I would recommend tracing a session that runs slowly (dbms_monitor) and see within the trace file what all the additional blocks are that are being hit. If they are index blocks, dump a few of them and see what the contents might be.

In answer to your question, no a <= should not run faster but it depends on how it's actually costed by the CBO and what possible values might be in the boundary above just the <.

“did you likewise have many values of 2147483646 and
may many of them have been deleted ? It might be Oracle is forced to
read through a whole bunch of leaf blocks that are actually empty until it
finally gets to the first occurance of 2147483647 ?”

I did not get your point here,

should not be for predicate “col1 < 2147483646 or col1 = 2147483646" it has to go
through the empty leaf block to check col1 = 2147483646

Our first initial thought was the empty leaf blocks as well since we
update the 2147483646 value a lot and move it to 2147483647.

So we did a CTAS of the original table, put on the same index
of col1+col2 and ran the queries. Same result – big diff in gets and performance – and explain plans. Note that the copied table was idle and no dml was happening.

We did run some traces for both queries.
Let me find them and paste them here.

Oracle’s stance was that the extra gets was due to accounting for
fractions/decimals (inspite of the column being number(10,0) ).
After a lot of back and forth with Oracle, they have raised an
enhancement request for this.

Yes, if you perform a massive deletion and don’t re-insert data back, then the deleted space doesn’t get re-used and a rebuild/coalesce of the index and just as importantly a reorg of the table might be warranted.

This is one of the “classic” use cases for a possible index rebuild, as I’ve discussed previously such as here: