<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Gojko Adzic &#187; caching</title>
	<atom:link href="http://gojko.net/tag/caching/feed/" rel="self" type="application/rss+xml" />
	<link>http://gojko.net</link>
	<description>Building software that matters</description>
	<lastBuildDate>Wed, 04 Aug 2010 11:38:56 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Two data streams for a happy website</title>
		<link>http://gojko.net/2008/03/03/two-data-streams-for-a-happy-website/</link>
		<comments>http://gojko.net/2008/03/03/two-data-streams-for-a-happy-website/#comments</comments>
		<pubDate>Mon, 03 Mar 2008 18:04:04 +0000</pubDate>
		<dc:creator>gojko</dc:creator>
				<category><![CDATA[articles]]></category>
		<category><![CDATA[.net]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[scalability]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://gojko.net/2008/03/03/two-data-streams-for-a-happy-website/</guid>
		<description><![CDATA[One of the most important architectural decisions that must be done early on in a scalable web site project is splitting the data flow into two streams: one that is user specific and one that is generic. If this is done properly, the system will be able to grow easily. On the other hand, if [...]]]></description>
			<content:encoded><![CDATA[<p><img src="/images/869848_roads_sign.jpg" align="left" />One of the most important architectural decisions that must be done early on in a scalable web site project is splitting the data flow into two streams: one that is user specific and one that is generic. If this is done properly, the system will be able to grow easily. On the other hand, if the data streams are not separated from the start, then the growth options will be severely limited. Trying to make such a web site scale will be just painting the corpse, and this change will cost a whole lot more when you need to introduce it later (and it is &#8220;when&#8221; in this case, not &#8220;if&#8221;). <span id="more-104"></span></p>
<p>In a classic online book-store example, book details, prices and shop categories are all generic. They do not depend on any particular user. Browsing the catalogue should produce the same results for any two users at the same moment. Actions like buying a book or reviewing orders and transactions are user specific and they cannot be shared between different users. Why is it so important to split these two streams right from the start? Because generic and user-specific data flows are affected by completely different caching and partitioning constraints.</p>
<h2>Different constraints</h2>
<p>Keeping session footprint low, caching and partitioning have been tried and tested as best practices for scaling web systems. If there is only a single data stream, because of different constraints, these practices will be hard to implement and even harder to improve later. </p>
<p>For example, most generic data flow is completely stateless, but user-specific actions are often stateful.  If these two flows are clearly separated, then we can process generic actions with stateless services and servers. Stateless services are much easier to manage and scale than stateful services, since they can be easily replaced without any effect on the system operation. You can just stack more cheap servers and throw requests at them using round-robin or simple load balancing when you need more power. Stateful services cannot be scaled so easy &mdash; they might rely on resource locking and load balancers have to send all requests for a single session to the same server. If a stateful server dies, that has a visible effect on the system operation, so these services have to be much more resilient than stateless ones.</p>
<p>Generic data can be cached and shared between users and even servers. User-specific data like account information or transaction statuses should never be cached. Similarly, there is most likely no authentication required to get the generic data, but in most cases authentication rules apply to user-specific data. For example, anyone can view book details, but a user has to be logged in to view an order status (and the requested order has to belong to that particular user). If the two data streams are mixed, then any caching mechanism will have to analyse and understand the context of requests, and decide what should be cached on case-by-case basis. If the two streams are split, the decision is easy: everything generic is retrieved from the cache, everything user-specific does not even try to use the cache. No error-prone analytics involved. The generic data flow also does not have to suffer from the authentication overhead &mdash; you can save a lot of processor power by skipping authentication when it is not needed. Remember the <a href="http://gojko.net/2007/11/29/golden-rule-of-web-caching/">Golden rule of web caching</a>? We can cache generic data into static files and avoid any dynamic processing on the web servers, getting much better performance. On e-commerce systems, user-specific pages typically have to go through HTTPS, which adds even more overhead.</p>
<p>Because it does not matter where the generic data flow comes from, it is also much easier to partition. Once the system grows over the capacity of a single database, we can just take the generic content out and split it into several databases. Splitting account data and transactions is never that easy, because those details are bound by a single context and often constrained by business rules and validations. </p>
<h2>How to split the flow</h2>
<p><img src="/images/315726_got_to_split.jpg" align="right" style="margin:5px 5px 5px 5px"/>In the classic two-tier architecture, where the web site plugs directly into the database, data flow splitting starts with using a different data source for those two streams. Even if, on the start, everything sits in the same database, use different connection pools to retrieve user-specific and generic data. The generic data flow does not even have to run in transactions, which can make a big difference with some databases. If an ORM mapping library is used, then caching should be turned on for this stream. User-specific data flow, as a general rule of thumb, should be transactional and should never be cached. It is a good idea to hide any user-specific tables from the generic data flow using database user privileges, so that any attempts to mix the two streams get caught quickly. Internal web site services should be designed to clearly fall into one of those two categories, and not mix methods that retrieve generic data and perform user-specific functions. This is all a preparation for the day when the two-tier system needs to be split into more levels. With a clear separation of responsibilities, this divorce should not be painful at all. </p>
<p>In three tier architectures, I like to split the middleware straight from the start into user-specific and generic servers. Web servers sit on top, get the generic data from the first middleware group and process transactions using the second middleware group. Generic data-flow servers can be clustered and scaled easily, and any load balancing system will work right out of the box. They can be restarted, taken out of the cluster or put back in without any effect on the rest of the system. Transparent caching can be applied to those servers easily. User-specific servers, on the other hand, are much more tricky in all those aspects and should absolutely never be transparently cached. This split is a preparation for further scaling and caching, since generic data servers can be split regionally, put under several layers of cache servers, divided vertically by product range or type. The functionality on user-specific servers is focused and isolated, so that we will have less to focus </p>
]]></content:encoded>
			<wfw:commentRss>http://gojko.net/2008/03/03/two-data-streams-for-a-happy-website/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Golden rule of web caching</title>
		<link>http://gojko.net/2007/11/29/golden-rule-of-web-caching/</link>
		<comments>http://gojko.net/2007/11/29/golden-rule-of-web-caching/#comments</comments>
		<pubDate>Thu, 29 Nov 2007 17:37:26 +0000</pubDate>
		<dc:creator>gojko</dc:creator>
				<category><![CDATA[articles]]></category>
		<category><![CDATA[best practices]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[iis]]></category>
		<category><![CDATA[scalability]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://gojko.net/2007/11/29/golden-rule-of-web-caching/</guid>
		<description><![CDATA[Effective content caching is one of the key features of scalable web sites. Although there are several out-of-the-box options for caching with modern web technologies, a custom built cache still provides the best performance. The primary aim of caching is to speed up processing and reduce load on critical resources. In case of web sites, [...]]]></description>
			<content:encoded><![CDATA[<p><img src='/images/869106_music.jpg' style='border:1px solid black;margin:5px 5px 5px 5px;' align='left'/>Effective content caching is one of the key features of scalable web sites. Although there are several out-of-the-box options for caching with modern web technologies, a custom built cache still provides the best performance. <span id="more-73"></span></p>
<p>The primary aim of caching is to speed up processing and reduce load on critical resources. In case of web sites, the most critical resource is probably going to be the database &#8211; so dataset caching is a common solution to speed up the web. It is relatively easy to implement, and it can be introduced almost transparently into an existing system. But that is just  painting the corpse. First of all, dataset caching requires the same source data to be re-processed over and over, and takes up unnecessary CPU cycles. Also, a lot of data from recordsets is often not displayed directly, so dataset caching can lead to a lot of wasted memory &#8211; in fact, I&#8217;ve seen sites where this approach caused serious problems by fragmenting memory under heavy load and leaving the application without enough memory to run. </p>
<p>Although the web site will, no doubt, benefit significantly even from a dataset cache, it will scale much more and better if the cached content is closer to the final product. How much closer, that depends on the application. The golden rule of web caching is: <i>For the caching to be most effective, cache as close to the final product as possible</i>.</p>
<h2> Ideally, cache content into static files</h2>
<p>The best option for caching, if possible, is to use static files on the disk and allow web servers to publish those files directly. All web servers will process static files very efficiently, and that will also leave more resources for stuff that needs to be processed dynamically. Content on the disk does not take up valuable memory space available for the web application, and it absolutely minimises CPU requirements per request.</p>
<p>Here are the results of a stress test I recently did on a fairly good web server machine running IIS6 over a gigabit network. I measured the number of requests per second during a two-minute stress load for serving the a couple of files using several techniques. </p>
<table style="text-align:right; border:1px solid black;" align="center">
<tr style="background-color:#dddddd">
<td style="text-align:left">Requests/s <b>\</b> file (KB)</td>
<td>64</td>
<td>32</td>
<td>16</td>
<td>8</td>
<td>4</td>
<td>2</td>
<td>1</td>
<td>0.5</td>
</tr>
<tr style="background-color:#dddddd">
<td style="text-align:left">theoretic network capacity</td>
<td>2,048.00</td>
<td>4,096.00</td>
<td>8,192.00</td>
<td>16,384.00</td>
<td>25,600.00</td>
<td>51,200.00</td>
<td>102,400.00</td>
<td>204,800.00</td>
</tr>
<tr style="background-color:#eeeeee">
<td style="text-align:left">Static</td>
<td>1,786.16</td>
<td>3,531.10</td>
<td>7,013.06</td>
<td>11,545.83</td>
<td>13,154.12</td>
<td>14,278.29</td>
<td>14,336.17</td>
<td>14,371.84</td>
</tr>
<tr>
<td style="text-align:left">ASP-SSI</td>
<td>1,752.93</td>
<td>3,514.08</td>
<td>6,823.75</td>
<td>7,599.36</td>
<td>8,057.98</td>
<td>7,874.10</td>
<td>8,246.19</td>
<td>8,358.91</td>
</tr>
<tr style="background-color:#eeeeee">
<td style="text-align:left">ASP-Include</td>
<td>1,781.35</td>
<td>3,527.30</td>
<td>5,686.10</td>
<td>6,053.17</td>
<td>6,309.94</td>
<td>6,372.38</td>
<td>6,449.43</td>
<td>6,463.96</td>
</tr>
<tr>
<td style="text-align:left">ASP-Nocache</td>
<td>233.93</td>
<td>493.50</td>
<td>925.55</td>
<td>1,562.07</td>
<td>2,622.32</td>
<td>3,507.78</td>
<td>4,001.26</td>
<td>4,309.78</td>
</tr>
<tr style="background-color:#eeeeee">
<td style="text-align:left">ASP-Cache</td>
<td>1,264.07</td>
<td>2,540.00</td>
<td>3,571.16</td>
<td>4,878.81</td>
<td>5,265.62</td>
<td>5,497.21</td>
<td>5,653.36</td>
<td>5,708.31</td>
</tr>
<tr>
<td style="text-align:left">ASPX-Nocache</td>
<td>1,560.03</td>
<td>2,000.51</td>
<td>3,641.76</td>
<td>4,500.16</td>
<td>5,390.68</td>
<td>5,770.47</td>
<td>6,561.65</td>
<td>6,150.49</td>
</tr>
<tr style="background-color:#eeeeee">
<td style="text-align:left">ASPX-Cache</td>
<td>1,782.68</td>
<td>3,507.56</td>
<td>6,612.81</td>
<td>9,301.24</td>
<td>9,542.29</td>
<td>9,100.19</td>
<td>11,591.58</td>
<td>11,375.98</td>
</tr>
<tr>
<td style="text-align:left">ASPX-Include</td>
<td>1,777.88</td>
<td>3,491.74</td>
<td>6,447.18</td>
<td>8,964.44</td>
<td>9,359.95</td>
<td>9,281.08</td>
<td>11,304.62</td>
<td>11,198.98</td>
</tr>
<tr style="background-color:#eeeeee">
<td style="text-align:left">ASPX-SSI</td>
<td>1,790.19</td>
<td>3,568.56</td>
<td>6,497.78</td>
<td>9,032.19</td>
<td>9,267.66</td>
<td>9,707.82</td>
<td>11,224.71</td>
<td>11,000.50</td>
</tr>
</table>
<ul>
<li>Static – file served directly by IIS from disk</li>
<li>ASP-SSI – file included using #include SSI directive in an ASP file (turning on the SSI engine)</li>
<li>ASP-Include – file included using #include SSI directive in an ASP file but with an ASP statement <i>if (true)</i> around the include (so turning on the ASP engine as well)</li>
<li>ASP-Cache – file read from disk using FSO, but cached in an Application object for up to 10 seconds.</li>
<li>ASP-Nocache – file read from disk on every request using FSO</li>
<li>ASPX-SSI – file included using #include SSI directive in an ASPX file</li>
<li>ASPX-Include – file included using #include SSI directive in an ASPX file, with an additional C# if (true) block around the include</li>
<li>ASPX-Cache – file read from disk but cached using ASP.NET page caching for 10 seconds</li>
<li>ASPX-NoCache – file read from disk on each request, page caching turned off</li>
</ul>
<p>The difference between “static” and “aspx-cache” is the pure overhead of using the ASP.NET engine. For a 4KB file, we get a 38% increase of performance from a file based cache over even the ASP.NET page caching mechanism. Compared to a simple cache based on the Application object in ASP, the performance increase is 150%. And this is just for managing completely static content. A typical web application would pull the content from a database or format it, leading to much bigger differences. Also interesting is the difference between ASP-Include and ASP-SSI, effectively the cost of having turning on the ASP engine (having <i>if(true)</i> statement in ASP code). The good news is that difference between those two cases in ASP.NET is negligible.</p>
<p>With larger files we hit the bandwidth bottleneck first, so the benefits of a file based cache are not that visible. However, with the growing number of Ajax-based sites, pages are getting split into smaller independent requests, and so the overhead becomes quite noticeable. Although ASP.NET page caching is a great utility given that it almost requires no work to implement, file based cache can also be split across the server farm, allowing multiple machines to use the same content. </p>
<p>Caching into static files also brings the benefit of automatic support for last modification timestamps. For browsers with HTTP 1.1 support, if the same files are downloaded often (live Ajax updates), the server can actually reply just with the “304 Not modified” header, without sending any file content at all. Cache that works directly from disk files also allows us to use lightweight HTTP servers such as <a href="http://www.lighttpd.net" target="_blank" >LightHTTPD</a> to get some extra performance from the same hardware. </p>
<h2>Not just for pre-publishing</h2>
<p>The file based cache is often used for pre-publishing content, but there is a simple trick to use this technique for on-demand publishing, even for content that must be generated often on the fly. Most web servers I have worked with, including IIS and Apache, will allow us to override the 404 &#8220;Not found&#8221; handler. This can be used to implement the cache-miss scenario: when users request a file that has not yet been cached, IIS will not find it on the disk and will call the 404 handler; we can then generate the file using dynamic processing (ASP/ASP.NET) and store it to the disk for the next request before sending the content back to the client. The same technique can be used with Apache and PHP. </p>
<p>An important note for this technique is that requests must be mapped completely to the file path of the URL, not to GET parameters. So, for example, instead of using <i>http://myserver/search.aspx?query=fitnesse</i>, we would have to use something like <i>http://myserver/search/fitnesse.query</i>. Then a 404 handler would be set up on the <i>search</i> folder to perform a search based on the active URL path and store it into the <i>fitnesse.query</i> file. You can generally chose any extension you want, but avoid ASP/ASPX and other standard extensions, to prevent IIS from turing on ASP/ASP.NET processing when serving those files. HTML and TXT extensions are also not a good choice. IE uses &#8220;smart&#8221; caching by default, so HTML files are downloaded just once per page. If you fire a background AJAX request twice for a HTML file, only the first will actually go to the server. TXT and HTML files may also be cached by transparent proxy systems, so it&#8217;s best to avoid using those extensions. </p>
<p><b>Image credits:</b> <a href="http://www.sxc.hu/profile/therysma" target="_blank">Akis Kolokotronis</a>/SXC</p>
]]></content:encoded>
			<wfw:commentRss>http://gojko.net/2007/11/29/golden-rule-of-web-caching/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Speed up database code with result caching</title>
		<link>http://gojko.net/2007/11/02/speed-up-database-code-with-result-caching/</link>
		<comments>http://gojko.net/2007/11/02/speed-up-database-code-with-result-caching/#comments</comments>
		<pubDate>Fri, 02 Nov 2007 07:55:27 +0000</pubDate>
		<dc:creator>gojko</dc:creator>
				<category><![CDATA[articles]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[oracle]]></category>
		<category><![CDATA[scalability]]></category>

		<guid isPermaLink="false">http://gojko.net/2007/11/02/speed-up-database-code-with-result-caching/</guid>
		<description><![CDATA[One of the most interesting new features of Oracle 11 for me is the new function result caching mechanism. Until now, making sure that a PL/SQL function gets executed only as many times as necessary was a black art. The new caching system makes that quite easy &#8211; here is how it works. Enabling the [...]]]></description>
			<content:encoded><![CDATA[<p>One of the most interesting new features of Oracle 11 for me is the new function result caching mechanism. Until now, making sure that a PL/SQL function gets executed only as many times as necessary was a black art. The new caching system makes that quite easy &#8211; here is how it works.<span id="more-68"></span></p>
<h2>Enabling the cache</h2>
<p>To turn on caching for a function, add RESULT_CACHE immediately before IS or AS in the function declaration. There is an optional RELIES_ON clause which can specify a database table or column dependency. If a dependency is specified, any change to the underlying object invalidates cached results. So, for example, we can write a function that does some on-the-fly statistics depending on a configuration parameter. If the parameter table changes, function results are automatically deleted from the cache. This mechanism is intended to replace all in-house caching systems developed over the years using triggers and PL/SQL package variables. </p>
<p>A new DBMS package dbms_result_cache can be used to get statistics and manipulate cache directly. Two most interesting methods in that package are memory_report and FLUSH. Flush clears the cache, and memory_report prints a nice memory usage report to serveroutput.</p>
<h2>A practical example</h2>
<p>So let&#8217;s try it out. We&#8217;ll do some statistics on a table, and run the query a couple of times without and with caching. I typically run tests like these on sys.all_objects, but for some reason result caching is disabled on SYS structures. For the test, we&#8217;ll first copy ALL_OBJECTS into a table in some user schema. You&#8217;ll probably need the DBA role to run this script because of the memory flushing, but result caching is available to normal user roles.</p>
<blockquote>
<pre>

spool c:\log.txt 

drop table BIGTABLE;

create table BIGTABLE as
select * from all_objects;

SET SERVEROUTPUT ON

exec dbms_result_cache.flush;

alter system flush shared_pool
/

exec dbms_result_cache.memory_report;

create or replace function COUNT_OBJECTS(pOBJECT_TYPE varchar2) return number as
lCount number;
begin
	select count(*) into lCOUNT from BIGTABLE where object_type=pOBJECT_TYPE;
	return lCOUNT;
end;
/

create or replace function COUNT_OBJECTS2(pOBJECT_TYPE varchar2) return number
<b>RESULT_CACHE relies_on (BIGTABLE) </b>
as
lCount number;
begin
	select count(*) into lCOUNT from BIGTABLE where object_type=pOBJECT_TYPE;
	return lCOUNT;
end;
/

set timing on;

set autotrace traceonly;

select object_type, COUNT_OBJECTS(object_type) from BIGTABLE group by object_type;

select object_type, COUNT_OBJECTS(object_type) from BIGTABLE group by object_type;

select object_type, COUNT_OBJECTS2(object_type) from BIGTABLE group by object_type;

exec dbms_result_cache.memory_report;

select object_type, COUNT_OBJECTS2(object_type) from BIGTABLE group by object_type;

exec dbms_result_cache.memory_report;

spool off;
</pre>
</blockquote>
<p>Methods COUNT_OBJECTS and COUNT_OBJECTS2 are absolutely the same, except for the result caching. When the script is executed, we see the difference in the trace and memory report. The first execution, without caching, comes to this:</p>
<blockquote>
<pre>

36 rows selected.

Elapsed: 00:00:00.42

Execution Plan
----------------------------------------------------------
Plan hash value: 2406321947                                                     

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          | 63870 |   686K|   284   (2)| 00:00:04 |
|   1 |  HASH GROUP BY     |          | 63870 |   686K|   284   (2)| 00:00:04 |
|   2 |   TABLE ACCESS FULL| BIGTABLE | 63870 |   686K|   281   (1)| 00:00:04 |
------------------------------------------------------------------------------- 

Note
-----
   - dynamic sampling used for this statement                                   

Statistics
----------------------------------------------------------
        248  recursive calls
          0  db block gets
      37423  consistent gets
       1006  physical reads
          0  redo size
       1179  bytes sent via SQL*Net to client
        356  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         36  rows processed                                                     
</pre>
</blockquote>
<p>The second one, calling the same function but without caching, leads to this:</p>
<blockquote>
<pre>
36 rows selected.

Elapsed: 00:00:00.37

Execution Plan
----------------------------------------------------------
Plan hash value: 2406321947                                                     

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          | 63870 |   686K|   284   (2)| 00:00:04 |
|   1 |  HASH GROUP BY     |          | 63870 |   686K|   284   (2)| 00:00:04 |
|   2 |   TABLE ACCESS FULL| BIGTABLE | 63870 |   686K|   281   (1)| 00:00:04 |
------------------------------------------------------------------------------- 

Note
-----
   - dynamic sampling used for this statement                                   

Statistics
----------------------------------------------------------
         38  recursive calls
          0  db block gets
      37222  consistent gets
          0  physical reads
          0  redo size
       1179  bytes sent via SQL*Net to client
        356  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         36  rows processed                                                     
</pre>
</blockquote>
<p>The number of physical reads and recursive calls is much lower, because of internal caching mechanisms. Execution time drops from 42 to 37 ms. Now, the third execution with caching turned on:</p>
<blockquote>
<pre>

36 rows selected.

Elapsed: 00:00:00.37

Execution Plan
----------------------------------------------------------
Plan hash value: 2406321947                                                     

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          | 63870 |   686K|   284   (2)| 00:00:04 |
|   1 |  HASH GROUP BY     |          | 63870 |   686K|   284   (2)| 00:00:04 |
|   2 |   TABLE ACCESS FULL| BIGTABLE | 63870 |   686K|   281   (1)| 00:00:04 |
------------------------------------------------------------------------------- 

Note
-----
   - dynamic sampling used for this statement                                   

Statistics
----------------------------------------------------------
         58  recursive calls
          0  db block gets
      37310  consistent gets
          0  physical reads
          0  redo size
       1180  bytes sent via SQL*Net to client
        356  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         36  rows processed
</pre>
</blockquote>
<p>Number of recursive calls and physical reads is still lower than in the first case, because table data is loaded into the memory. Execution took about 37 ms, more or less the same as in the second run. Consistent gets show that we are still processing the whole table, because they are basically the same as in the first run.</p>
<p>The memory_report procedure after this call shows that some data is in the cache:</p>
<blockquote>
<pre>
R e s u l t   C a c h e   M e m o r y   R e p o r t
[Parameters]
Block Size          = 1K bytes
Maximum Cache Size  = 4064K bytes (4064 blocks)
Maximum Result Size = 203K bytes (203 blocks)
[Memory]
Total Memory = 202184 bytes [0.057% of the Shared Pool]
... Fixed Memory = 5296 bytes [0.002% of the Shared Pool]
... Dynamic Memory = 196888 bytes [0.056% of the Shared Pool]
....... Overhead = 131352 bytes
....... Cache Memory = 64K bytes (64 blocks)
........... Unused Memory = 26 blocks
........... Used Memory = 38 blocks
............... Dependencies = 2 blocks (2 count)
............... Results = 36 blocks
................... PLSQL   = 36 blocks (36 count)
PL/SQL procedure successfully completed.
</pre>
</blockquote>
<p>Now the last execution &#8211; repeating the function with caching turned on:</p>
<blockquote>
<pre>
36 rows selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 2406321947                                                     

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          | 63870 |   686K|   284   (2)| 00:00:04 |
|   1 |  HASH GROUP BY     |          | 63870 |   686K|   284   (2)| 00:00:04 |
|   2 |   TABLE ACCESS FULL| BIGTABLE | 63870 |   686K|   281   (1)| 00:00:04 |
------------------------------------------------------------------------------- 

Note
-----
   - dynamic sampling used for this statement                                   

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       1006  consistent gets
          0  physical reads
          0  redo size
       1180  bytes sent via SQL*Net to client
        356  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         36  rows processed                                                     
</pre>
</blockquote>
<p>So between third and fourth call, number of consistent gets dropped significantly. There were no recursive calls and no function executions. All values were read straight from the cache. Notice how the execution time dropped from 36 ms to 1 ms.</p>
<p>The cache is in the system global area (SGA), so cached values are available across database sessions. So, to conclude, adding RESULT_CACHE is an easy way to optimise dynamic statistics and parametrisation. Here are some links for the end: </p>
<ul>
<li>
<a target='_blank'  href='http://www.oracle.com/technology/obe/11gr1_db/perform/rescache/res_cache.htm'>Improving Application Performance with Result Cache</a> article from OTN.
</li>
<li>
<a target='_blank' href='http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28419/d_result_cache.htm'>DMBS_RESULT_CACHE API docs</a></p>
</li>
<li>
<a target='_blank' href='http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/subprograms.htm#BABCDCFA'>Result caching syntax docs</a></p>
</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://gojko.net/2007/11/02/speed-up-database-code-with-result-caching/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
