Resin Documentationapp server |
server caching
Server caching can speed dynamic pages to near-static speeds. When pages created by database queries only change every 15 minutes, e.g. CNN or Wikipedia or Slashdot, Resin can cache the results and serve them like static pages. Because Resin's caching only depends on HTTP headers, it will work for any JSPs, servlet or PHP page.response. Resin's caching operates like a proxy cache, looking at HTTP headers to compare hash codes or digests or simply caching for a static amount of time. Since the proxy cache follows the HTTP standards, applications like Mediawiki will automatically see dramatic performance improvement with no extra work. You can even cache REST-style GET requests. Because the cache supports advanced headers like "Vary", it can cache different versions of the page depending on the browser's capabilities. Gzip-enabled browsers will get the cached compressed page while more primitive browsers will see the plan page. With "Vary: Cookie", you can return a cached page for anonymous users, and still return a custom page for logged-in users. For many applications, enabling the proxy cache can improve your application's performance dramatically. When Quercus runs Mediawiki with caching enabled, Resin can return results as fast as static pages. Without caching, the performance is significantly slower.
To enable caching, your application will need to set a few HTTP headers. While a simple application can just set a timed cache with , a more sophisticated application can generate page hash digests with and short-circuit repeated responses.If subsections of your pages are cacheable but the main page is not, you can cache servlet includes just like caching top-level pages. Resin's include processing will examine the headers set by your include servlet and treat them just like a top-level cache.
Cache-Control: max-ageSetting the header will cache the results for a specified time. For heavily loaded pages, even setting short expires times can significantly improve performance. Pages using sessions should set a "Vary: Cookie" header, so anonymous users will see the cached page, while logged-in users will see their private page.<%@ page session="false" %> <%! int counter; %> <% response.addHeader("Cache-Control", "max-age=15"); %> Count: <%= counter++ %> is useful for database generated pages which are continuously, but slowly updated. To cache with a fixed content, i.e. something which has a valid hash value like a file, you can use with . ETag and If-None-MatchThe header specifies a hash or digest code for the generated page to further improve caching. The browser or cache will send the as a value when it checks for any page updates. If the page is the same, the application will return a 304 NOT_MODIFIED response with an empty body. Resin's FileServlet automatically provides this capability for static pages. In general, the ETag is the most effective caching technique, although it requires a bit more work than .To handle clustered servers in a load-balanced configuration, the calculated should be a hash of the result value, not a timestamp or version. Since each server behind a load balancer will generate a different timestamp for the files, each server would produce a different tag, even though the generated content was identical. So either producing a hash or ensuring the value is the same is critical.servlets will often also use <cache-mapping> configuration to set a or . The browser and proxy cache will cache the page without revalidation until runs out. When the time expires, it will use to revalidate the page. When using , your application will need to look for the header on incoming requests. If the value is the same, your servlet can return 304 NOT-MODIFIED. If the value differs, you'll return the new content and hash.import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class MyServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) { String etag = getCurrentEtag(); String ifNoneMatch = req.getHeader("If-None-Match"); if (ifNoneMatch != null && ifNoneMatch.equals(etag)) { res.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } res.setHeader("ETag", etag); ... // generate response } } C: GET /test-servlet HTTP/1.1 S: HTTP/1.1 200 OK S: ETag: xm8vz29I S: Cache-Control: max-age=15s S: ... C: GET /test-servlet HTTP/1.1 C: If-None-Match: xm8vz29I S: HTTP/1.1 304 Not Modified S: Cache-Control: max-age=15s S: ... C: GET /test-servlet HTTP/1.1 C: If-None-Match: UXi456Ww S: HTTP/1.1 200 OK S: ETag: xM81x+3j S: Cache-Control: max-age=15s S: ... ExpiresAlthough max-age tends to be easier and more flexible, an application can also set the header to enable caching, when the expiration date is a specific time instead of an interval. For heavily loaded pages, even setting short expires times can significantly improve performance. Sessions should be disabled for caching.The following example sets expiration for 15 seconds. So the counter should update slowly. <%@ page session="false" %> <%! int counter; %> <% long now = System.currentTimeMillis(); response.setDateHeader("Expires", now + 15000); %> Count: <%= counter++ %> is useful for database generated pages which are continuously, but slowly updated. To cache with a fixed content, i.e. something which has a valid hash value like a file, you can use with . If-Modified-SinceThe headers let you cache based on an underlying change date. For example, the page may only change when an underlying source page changes. Resin lets you easily use by overriding methods in HttpServlet or in a JSP page.Because of the clustering issues mentioned in the ETag section, it's generally recommended to use ETag and If-None-Match and avoid If-Modified-Since. In a load balanced environment, each backend server would generally have a different Last-Modified value, while would effectively disable caching for a proxy cache or a browser that switched from one backend server to another. The following page only changes when the underlying 'test.xml' page changes. <%@ page session="false" %> <%! int counter; public long getLastModified(HttpServletRequest req) { String path = req.getRealPath("test.xml"); return new File(path).lastModified(); } %> Count: <%= counter++ %> pages are useful in combination with the configuration. VaryIn some cases, you'll want to have separate cached pages for the same URL depending on the capabilities of the browser. Using gzip compression is the most important example. Browsers which can understand gzip-compressed files receive the compressed page while simple browsers will see the uncompressed page. Using the "Vary" header, Resin can cache different versions of that page. <% response.addHeader("Cache-Control", "max-age=3600"); response.addHeader("Vary", "Accept-Encoding"); %> Accept-Encoding: <%= request.getHeader("Accept-Encoding") %> The "Vary" header can be particularly useful for caching anonymous pages, i.e. using "Vary: Cookie". Logged-in users will get their custom pages, while anonymous users will see the cached page. Resin can cache subpages even when the top page can't be cached. Sites allowing user personalization will often design pages with subpages. Some subpages are user-specific and can't be cached. Others are common to everybody and can be cached.Resin treats subpages as independent requests, so they can be cached independent of the top-level page. Try the following, use the first counter example as the included page. Create a top-level page that looks like:<% if (! session.isNew()) { %> <h1>Welcome back!</h1> <% } %> <jsp:include page="expires.jsp"/> <%@ page session="false" %> <%! int counter; %> <% response.setHeader("Cache-Control", "max-age=15"); %> Count: <%= counter++ %> The Vary header can be used to implement anonymous user caching. If a user is not logged in, he will get a cached page. If he's logged in, he'll get his own page. This feature will not work if anonymous users are assigned cookies for tracking purposes. To make anonymous caching work, you must set the If you omit the Vary header, Resin will use the to cache the same page for every user.<%@ page session="false" %> <%! int _counter; %> <% response.addHeader("Cache-Control", "max-age=15"); response.addHeader("Vary", "Cookie"); String user = request.getParameter("user"); %> User: <%= user %> <%= counter++ %> The top page must still set the or header, but Resin will take care of deciding if the page is cacheable or not. If the request has any cookies, Resin will not cache it and will not use the cached page. If it has no cookies, Resin will use the cached page.When using Vary: Cookie, user tracking cookies will make the page uncacheable even if the page is the same for all users. Resin chooses to cache or not based on the existence of any cookies in the request, whether they're used or not. cachechild of cluster<cache> configures the proxy cache (requires Resin Professional). The proxy cache improves performance by caching the output of servlets, jsp and php pages. For database-heavy pages, this caching can improve performance and reduce database load by several orders of magnitude. The proxy cache uses a combination of a memory cache and a disk-based cache to save large amounts of data with little overhead. Management of the proxy cache uses the ProxyCacheMXBean.
element cache { disk-size? & enable? & enable-range? & entries? & path? & max-entry-size? & memory-size? & rewrite-vary-as-private? } <resin xmlns="http://caucho.com/ns/resin"> <cluster id="web-tier"> <cache entries="16384" disk-size="2G" memory-size="256M"/> <server id="a" address="192.168.0.10"/> <host host-name="www.foo.com"> </cluster> </resin> rewrite-vary-as-privateBecause not all browsers understand the Vary header, Resin can rewrite Vary to a Cache-Control: private. This rewriting will cache the page with the Vary in Resin's proxy cache, and also cache the page in the browser. Any other proxy caches, however, will not be able to cache the page. The underlying issue is a limitation of browsers such as IE. When IE sees a Vary header it doesn't understand, it marks the page as uncacheable. Since IE only understands "Vary: User-Agent", this would mean IE would refuse to cache gzipped pages or "Vary: Cookie" pages. With the <rewrite-vary-as-private> tag, IE will cache the page since it's rewritten as "Cache-Control: private" with no Vary at all. Resin will continue to cache the page as normal. cache-mappingassigns a and to a cacheable page, i.e. a page with an or setting. It does not affect or cached pages. The FileServlet takes advantage of because it provides the ETag servlet. Often, you want a long Expires time for a page to a browser. For example, any gif will not change for 24 hours. That keeps browsers from asking for the same gif every five seconds; that's especially important for tiny formatting gifs. However, as soon as that page or gif changes, you want the change immediately available to any new browser or to a browser using reload. Here's how you would set the Expires to 24 hours for a gif, based on the default FileServlet. <web-app xmlns="http://caucho.com/ns/resin"> <cache-mapping url-pattern='*.gif' expires='24h'/> </web-app> The automatically generates the Expires header. It only works for cacheable pages setting If-Modified or ETag. It will not affect pages explicily setting Expires or non-cacheable pages. So it's safe to create a cache-mapping for even if only some are cacheable.When designing and testing your cached page, it's important to see how Resin is caching the page. To turn on logging for caching, you'll add the following to your resin.conf: <resin xmlns="http://caucho.com/ns/resin"> <logger name="com.caucho.server.cache" level="fine"/> ... </resin> The output will look something like the following: [10:18:11.369] caching: /images/caucho-white.jpg etag="AAAAPbkEyoA" length=6190 [10:18:11.377] caching: /images/logo.gif etag="AAAAOQ9zLeQ" length=571 [10:18:11.393] caching: /css/default.css etag="AAAANzMooDY" length=1665 [10:18:11.524] caching: /images/pixel.gif etag="AAAANpcE4pY" length=61 ... [10:18:49.303] using cache: /css/default.css [10:18:49.346] using cache: /images/pixel.gif [10:18:49.348] using cache: /images/caucho-white.jpg [10:18:49.362] using cache: /images/logo.gif /resin-adminblock cache miss ratioThe block cache miss ratio tells how often Resin needs to access the disk to read a cache entry. Most cache requests should come from memory to improve performance, but cache entries are paged out to disk when the cache gets larger. It's very important to keep the <memory-size> tag of the <cache> large enough so the block cache miss ratio is small. proxy cache miss ratioThe proxy cache miss ratio measures how often cacheable pages must go to their underlying servlet instead of being cached. The miss ratio does not measure the non-cacheable pages. invocation miss ratioThe invocation miss ratio measures how often Resin's invocation cache misses. The invocation cache is used for both cacheable and non-cacheable pages to save the servlet and filter chain construction. A miss of the invocation cache is expensive, since it will not only execute the servlet, but also force the servlet and filter chain to be rebuilt. The <entries> field of the <cache> controlls the invocation miss ratio. BlockManagerMXBeanBlockManagerMXBean returns statistics about the block cache. Since Resin's block cache is used for the proxy cache as well as clustered sessions and JMS messages, the performance of the block cache is very important. The block cache is a memory/paging cache on top of a file-based backing store. If the block cache misses, a request needs to go to disk, otherwise it can be served directly from memory. resin:type=BlockManager public interface BlockManagerMXBean { public long getBlockCapacity(); public long getHitCountTotal(); public long getMissCountTotal(); } ProxyCacheMXBeanThe ProxyCacheMXBean provides statistics about the proxy cache as well as operations to clear the cache. The hit and miss counts tell how effectively the cache is improving performance. resin:type=ProxyCache public interface ProxyCacheMXBean { public long getHitCountTotal(); public long getMissCountTotal(); public CacheItem []getCacheableEntries(int max); public CacheItem []getUncacheableEntries(int max); public void clearCache(); public void clearCacheByPattern(String hostRegexp, String urlRegexp); public void clearExpires(); }
|