<?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>Oliver's Place &#187; nginx</title>
	<atom:link href="http://weichhold.com/category/nginx/feed/" rel="self" type="application/rss+xml" />
	<link>http://weichhold.com</link>
	<description></description>
	<lastBuildDate>Thu, 12 Aug 2010 18:13:53 +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>High performance Grails with memcached</title>
		<link>http://weichhold.com/2010/03/04/high-performance-grails-with-memcached/</link>
		<comments>http://weichhold.com/2010/03/04/high-performance-grails-with-memcached/#comments</comments>
		<pubDate>Thu, 04 Mar 2010 21:28:24 +0000</pubDate>
		<dc:creator>oliver</dc:creator>
				<category><![CDATA[Grails]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[memcached]]></category>

		<guid isPermaLink="false">http://weichhold.com/?p=148</guid>
		<description><![CDATA[This article is the second one in my article series about fast loading web pages. The first article dealt with the Django Framework while this one is about Grails which I have recently elected as my preferred rapid web application framework. Ever since I switched from Django to Grails there was one issue that bothered [...]]]></description>
			<content:encoded><![CDATA[<p>This article is the second one in my article series about fast loading web pages. The first article dealt with the <a href="http://www.djangoproject.com/">Django Framework</a> while this one is about <a href="http://grails.org">Grails </a>which I have recently elected as my preferred rapid web application framework. Ever since I switched from Django to Grails there was one issue that bothered me and that was the loss of the ability to serve complete pre-rendered pages from a distributed cache to the enduser while bypassing the application server.</p>
<p>The solution described in this article is in use on the production servers powering <a href="http://mmogle.com">mmogle.com</a> where it ensures page response times below 100ms for cached content.</p>
<p>Let me summarize the concept for readers not familiar with my first article: The goal we want to achieve is to greatly decrease the load on the application server (container) by storing the raw HTML output of rendered pages in a (distributed-) cache to be picked up by a Frontend Web Server without even bothering the application server behind it for the current request. Since we would need stuff like Edge Side Includes (ESI) for dealing with personalized pages, we are going limit ourselves to anonymous users.</p>
<p><span id="more-148"></span></p>
<p>This picture below shows the server setup:</p>

<a href="http://weichhold.com/wp-content/gallery/misc/nginx-memcached.png" title="" class="thickbox" rel="singlepic8" >
	<img class="ngg-singlepic" src="http://weichhold.com/wp-content/plugins/nextgen-gallery/nggshow.php?pid=8&amp;width=420&amp;height=&amp;mode=" alt="nginx-memcached" title="nginx-memcached" />
</a>

<p><a href="http://wiki.nginx.org/Main">Nginx </a>will act as frontend web server. This is where all requests will initially arrive. Requests for static resources will always be handled by nginx. Dynamic requests will be scrutinized and once it is determined that a request could be cached, a cache key is computed based on request URI, cookies etc. Nginx will then proceed with querying memcached if one of the application servers has deposited content under that key. If the answer is positive the response from memcached is directly returned to the client – the application servers are bypassed. Perhaps it is worth noting that the application server (tomcat) is only responsible for storing the content, whereas nginx &#8211; acting as frontend server &#8211; is responsible for retrieving it. This decoupling is what sets this technique apart from the Grails Cache Filter Plugin which both stores and retrieves cached content.</p>
<p>Let’s continue with the actual implementation. When I decided to port the technique I described in the aforementioned article from Python/Django to Groovy/Grails, I thought it was going to be a very easy task. All that’s needed is to grab the rendered page contents within a Grails afterView Filter, put it into memcached and be done with it, right? Wrong! Imagine my surprise when I noticed the rendered output never had a layout applied. After lots of trying I gave up, realising that there is simply no way to get my hands on the <strong>complete </strong>HTML (emphasis on <em>complete</em>) for a rendered Grails page from within the application itself. The reason for this is Grails layouts are based on Sitemesh which is implemented as a ServletFilter. Grails applies the Layout to pages through its GrailsPageFilter ServletFilter which is executed further down in the pipeline after any Grails Filters.</p>
<p>That left me with only one option: a custom ServletFilter that executes before GrailsPageFilter. So I checked out the source of the <a href="http://www.grails.org/plugin/cachefilter">Grails Cache Filter Plugin</a>, stripped it off anything not necessary for our purpose and born was MemcachedFilter. You can download the source <a href="http://weichhold.com/MemcachedFilter.zip">here</a>.</p>
<p>MemcachedFilter is written in Groovy and it needs to be configured to execute before GrailsPageFilter. That means that it’s filter mapping element must appear before the filter mapping for GrailsPageFilter in web.xml. Here’s a sample web.xml snippet:</p>

<div class="wp_codebox_msgheader"><span class="right"><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples" target="_blank" title="WP-CodeBox HowTo?"><span style="color: #99cc00">?</span></a></sup></span><span class="left"><a href="javascript:;" onclick="javascript:showCodeTxt('p148code5'); return false;">View Code</a> XML</span><div class="codebox_clear"></div></div><div class="wp_codebox"><table><tr id="p1485"><td class="code" id="p148code5"><pre class="xml" style="font-family:monospace;"><span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter-name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>memcached<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter-name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter-class<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>com.banshee.servlet.MemcachedFilter<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter-class<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
&nbsp;
<span style="color: #808080; font-style: italic;">&lt;!-- memcached filter --&gt;</span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter-mapping<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter-name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>memcached<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter-name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;url-pattern<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>/*<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/url-pattern<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter-mapping<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
&nbsp;
&nbsp;
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter-mapping<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter-name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>charEncodingFilter<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter-name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;url-pattern<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>/*<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/url-pattern<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter-mapping<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
&nbsp;
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter-mapping<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;filter-name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>sitemesh<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter-name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
  <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;url-pattern<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>/*<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/url-pattern<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/filter-mapping<span style="color: #000000; font-weight: bold;">&gt;</span></span></span></pre></td></tr></table></div>

<p>MemcachedFilter has a dependency on a bean named “memcachedClient”. Please <a href="http://weichhold.com/2009/09/17/configuring-memcached-in-a-grails-application/">refer to this article</a> for configuration instructions.</p>
<p>Now that the filter is configured and active we actually want to put it to use – which is quite easy. The filter scans all Responses it encounters for the presence of two special headers: &#8220;X-Memcached-Filter-Cache-Key&#8221; and &#8220;X-Memcached-Filter-Cache-Timeout&#8221; (the latter is optional). If the &#8220;X-Memcached-Filter-Cache-Key&#8221; is detected the filter will store the response content in memcached using the header value as key. The expiration timeout will be either a default value or the value of the &#8220;X-Memcached-Filter-Cache-Timeout&#8221; header. It’s that simple. Here’s a usage example:</p>

<div class="wp_codebox_msgheader"><span class="right"><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples" target="_blank" title="WP-CodeBox HowTo?"><span style="color: #99cc00">?</span></a></sup></span><span class="left"><a href="javascript:;" onclick="javascript:showCodeTxt('p148code6'); return false;">View Code</a> GROOVY</span><div class="codebox_clear"></div></div><div class="wp_codebox"><table><tr id="p1486"><td class="code" id="p148code6"><pre class="groovy" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">class</span> FooController
<span style="color: #66cc66;">&#123;</span>
  <span style="color: #000000; font-weight: bold;">def</span> index <span style="color: #66cc66;">=</span>
  <span style="color: #66cc66;">&#123;</span>
    <span style="color: #b1b100;">if</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">!</span>SecurityUtils.<span style="color: #006600;">subject</span>.<span style="color: #006600;">principal</span><span style="color: #66cc66;">&#41;</span>      <span style="color: #808080; font-style: italic;">// store response only for anonymous users (using Shiro plugin)</span>
    <span style="color: #66cc66;">&#123;</span>
      response.<span style="color: #006600;">setHeader</span><span style="color: #66cc66;">&#40;</span>MemcachedFilter.<span style="color: #006600;">MEMCACHED_FILTER_X_CACHE_KEY</span>, ConfigurationHolder.<span style="color: #006600;">config</span>.<span style="color: #006600;">app_prefix</span> <span style="color: #66cc66;">+</span> <span style="color: #66cc66;">&#40;</span>request.<span style="color: #006600;">forwardURI</span> <span style="color: #66cc66;">-</span> request.<span style="color: #006600;">contextPath</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>
      response.<span style="color: #006600;">setHeader</span><span style="color: #66cc66;">&#40;</span>MemcachedFilter.<span style="color: #006600;">MEMCACHED_FILTER_X_CACHE_TIMEOUT</span>, <span style="color: #cc66cc;">120</span><span style="color: #66cc66;">&#41;</span>         <span style="color: #808080; font-style: italic;">// cache for two minutes</span>
    <span style="color: #66cc66;">&#125;</span>
  <span style="color: #66cc66;">&#125;</span>
<span style="color: #66cc66;">&#125;</span></pre></td></tr></table></div>

<p>This will store the response content for FooController’s ‘index’ action in memcached for two minutes using the context relative request URI as cache key as long as the requesting user is not logged in. It is of paramount importance that you never ever do this for authenticated users or bad things may happen. You don’t want John Doe to access information intended for the boss, only because the boss happened to be the first user accessing a certain page, do you? To make sure that our Frontend server will try fetch content from the cache for authenticated users we need a way for it to tell if a request is from an anonymous or authenticated user. To do this I wrote a simple Grails Filter that ensures that authenticated users are tagged with a cookie:</p>

<div class="wp_codebox_msgheader"><span class="right"><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples" target="_blank" title="WP-CodeBox HowTo?"><span style="color: #99cc00">?</span></a></sup></span><span class="left"><a href="javascript:;" onclick="javascript:showCodeTxt('p148code7'); return false;">View Code</a> GROOVY</span><div class="codebox_clear"></div></div><div class="wp_codebox"><table><tr id="p1487"><td class="code" id="p148code7"><pre class="groovy" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">class</span> CacheFilters
<span style="color: #66cc66;">&#123;</span>
  CacheService questionCacheService
&nbsp;
  <span style="color: #000000; font-weight: bold;">final</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #aaaadd; font-weight: bold;">String</span> MEMCACHED_USER_IS_AUTH_INDICATOR_COOKIE <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">'user_id'</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">def</span> filters <span style="color: #66cc66;">=</span>
  <span style="color: #66cc66;">&#123;</span>
    <span style="color: #808080; font-style: italic;">/** Sets or deletes a cookie that signals nginx that the current user is authenticated - not used for permission checks */</span>
    setNginxUserIsAuthenticatedIndicator<span style="color: #66cc66;">&#40;</span>controller: <span style="color: #ff0000;">'*'</span>, action: <span style="color: #ff0000;">'*'</span><span style="color: #66cc66;">&#41;</span>
    <span style="color: #66cc66;">&#123;</span>
      afterView <span style="color: #66cc66;">=</span>
      <span style="color: #66cc66;">&#123;</span>
        <span style="color: #000000; font-weight: bold;">def</span> cookie <span style="color: #66cc66;">=</span> request.<span style="color: #006600;">cookies</span>.<span style="color: #663399;">find</span> <span style="color: #66cc66;">&#123;</span> it.<span style="color: #006600;">name</span> <span style="color: #66cc66;">==</span> MEMCACHED_USER_IS_AUTH_INDICATOR_COOKIE <span style="color: #66cc66;">&#125;</span>
&nbsp;
        <span style="color: #808080; font-style: italic;">// is the user authenticated (by login or cookie?)</span>
        <span style="color: #b1b100;">if</span><span style="color: #66cc66;">&#40;</span>SecurityUtils.<span style="color: #006600;">subject</span>.<span style="color: #006600;">principal</span><span style="color: #66cc66;">&#41;</span>
        <span style="color: #66cc66;">&#123;</span>
          <span style="color: #808080; font-style: italic;">// yes, set the cookie if needed</span>
          <span style="color: #b1b100;">if</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">!</span>cookie<span style="color: #66cc66;">&#41;</span>
          <span style="color: #66cc66;">&#123;</span>
            cookie <span style="color: #66cc66;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Cookie<span style="color: #66cc66;">&#40;</span>MEMCACHED_USER_IS_AUTH_INDICATOR_COOKIE, User.<span style="color: #663399;">get</span><span style="color: #66cc66;">&#40;</span>SecurityUtils.<span style="color: #006600;">subject</span>.<span style="color: #006600;">principal</span><span style="color: #66cc66;">&#41;</span>.<span style="color: #006600;">id</span>.<span style="color: #006600;">toString</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>
            cookie.<span style="color: #006600;">setPath</span><span style="color: #66cc66;">&#40;</span>request.<span style="color: #006600;">getContextPath</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>
            response.<span style="color: #006600;">addCookie</span><span style="color: #66cc66;">&#40;</span>cookie<span style="color: #66cc66;">&#41;</span>
          <span style="color: #66cc66;">&#125;</span>
        <span style="color: #66cc66;">&#125;</span>
&nbsp;
        <span style="color: #b1b100;">else</span>
        <span style="color: #66cc66;">&#123;</span>
          <span style="color: #808080; font-style: italic;">// delete the cookie if necessary</span>
          <span style="color: #b1b100;">if</span><span style="color: #66cc66;">&#40;</span>cookie<span style="color: #66cc66;">&#41;</span>
          <span style="color: #66cc66;">&#123;</span>
            cookie.<span style="color: #006600;">setMaxAge</span><span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0</span><span style="color: #66cc66;">&#41;</span>
            cookie.<span style="color: #006600;">setPath</span><span style="color: #66cc66;">&#40;</span>request.<span style="color: #006600;">getContextPath</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>
            response.<span style="color: #006600;">addCookie</span><span style="color: #66cc66;">&#40;</span>cookie<span style="color: #66cc66;">&#41;</span>
          <span style="color: #66cc66;">&#125;</span>
        <span style="color: #66cc66;">&#125;</span>
      <span style="color: #66cc66;">&#125;</span>
    <span style="color: #66cc66;">&#125;</span>
  <span style="color: #66cc66;">&#125;</span>
<span style="color: #66cc66;">&#125;</span></pre></td></tr></table></div>

<p>Rest assured that the sole purpose of the cookie is to signal the frontend server that a user is authenticated in order to disable any cache retrieval attempts. No security checks are ever performed against the cookie.</p>
<p>Now that we’ve taken care of storing the content, someone has to retrieve it. As already mentioned we are using Nginx as Frontend server. I’m a big fan of it because of its small footprint, great performance and integrated support for memcached. Below is a Nginx virtual host configuration that implements the remaining bits and pieces:</p>

<div class="wp_codebox_msgheader"><span class="right"><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples" target="_blank" title="WP-CodeBox HowTo?"><span style="color: #99cc00">?</span></a></sup></span><span class="left"><a href="javascript:;" onclick="javascript:showCodeTxt('p148code8'); return false;">View Code</a> APACHE</span><div class="codebox_clear"></div></div><div class="wp_codebox"><table><tr id="p1488"><td class="code" id="p148code8"><pre class="apache" style="font-family:monospace;">upstream tomcats
{
  server 127.0.0.1:<span style="color: #ff0000;">8080</span> weight=<span style="color: #ff0000;">1</span>;
}
&nbsp;
server
{
  <span style="color: #00007f;">listen</span> <span style="color: #ff0000;">80</span>;
  server_name example.com;
  access_log /var/log/nginx/example.log;
&nbsp;
  <span style="color: #00007f;">include</span> ua_ban_list.conf;
&nbsp;
  proxy_set_header Host $http_host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
&nbsp;
  default_type text/html;
  charset utf-<span style="color: #ff0000;">8</span>;
&nbsp;
  proxy_redirect http://tomcats/ http://example.com/;
&nbsp;
  location /
  {
    <span style="color: #adadad; font-style: italic;"># only consider GET requests for caching</span>
    if ($request_method != GET)
    {
      proxy_pass http://tomcats;
      break;
    }
&nbsp;
    <span style="color: #adadad; font-style: italic;"># never cache auth requests</span>
    if ($request_uri ~* (^/auth/.*$))
    {
      proxy_pass http://tomcats;
      break;
    }
&nbsp;
    <span style="color: #adadad; font-style: italic;"># detect cookies that indicate an authenticated user</span>
    if ($http_cookie ~* <span style="color: #7f007f;">&quot;(rememberMe|user_id)&quot;</span>)
    {
      <span style="color: #adadad; font-style: italic;"># don't try cache lookup for authenticated user - NEVER</span>
      proxy_pass http://tomcats;
      break;
    }
&nbsp;
    <span style="color: #adadad; font-style: italic;"># compute cache key from app prefix + request_uri</span>
    <span style="color: #adadad; font-style: italic;"># if they computed key does not match the key computed by the application</span>
    <span style="color: #adadad; font-style: italic;"># server when storing content we will get nothing but memcached misses</span>
    <span style="color: #adadad; font-style: italic;"># and NO speedup</span>
    set $memcached_key example_$request_uri;
    memcached_pass memcached;
    error_page <span style="color: #ff0000;">404</span> = @cache_miss;
  }
&nbsp;
  location @cache_miss
  {
    internal;
    proxy_pass http://tomcats;
  }
}</pre></td></tr></table></div>

<p>Please pay special attention to the line where the cache key is computed. A key prefix &#8220;example_&#8221; is used. To get the example to work you would have to add a config variable &#8216;app_prefix&#8217; to Config.groovy which needs to have the value &#8220;example_&#8221;.</p>
<p>Finally I should point out that this approach isn&#8217;t tied to Grails applications. Basically any Java Web application could use it if someone would port the MemcachedFilter from Groovy to Java which should be a piece of cake considering that it was developed from Java Sources in the first place.</p>
<p>That&#8217;s it for now. I hope you enjoyed the ride.</p>
<img src="http://weichhold.com/?ak_action=api_record_view&id=148&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://weichhold.com/2010/03/04/high-performance-grails-with-memcached/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>django, nginx, memcached &#8211; the dynamic trio</title>
		<link>http://weichhold.com/2008/09/12/django-nginx-memcached-the-dynamic-trio/</link>
		<comments>http://weichhold.com/2008/09/12/django-nginx-memcached-the-dynamic-trio/#comments</comments>
		<pubDate>Fri, 12 Sep 2008 21:01:22 +0000</pubDate>
		<dc:creator>oliver</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[memcached]]></category>

		<guid isPermaLink="false">http://weichhold.com/?p=17</guid>
		<description><![CDATA[Inspired by this article I decided to find out if the same technique can be exploited in my current project which is developed in django. My first problem was to come up with a viable cache key scheme since simply using the full request URI as suggested in the article wouldn&#8217;t work for me because [...]]]></description>
			<content:encoded><![CDATA[<p>Inspired by <a href="http://www.igvita.com/2008/02/11/nginx-and-memcached-a-400-boost/" target="_blank">this article</a> I decided to find out if the same technique can be exploited in my current project which is developed in <a href="http://www.djangoproject.com" target="_blank">django</a>.</p>
<p>My first problem was to come up with a viable cache key scheme since simply using the full request URI as suggested in the article wouldn&#8217;t work for me because my site renders a different version of a navigation menu depending on the authentication state of current the user. After weighing in the advantages and disadvantages between the super clean variant of factoring the session cookie and all other cookies into the <a href="http://www.danga.com/memcached/" target="_blank">memcached</a> key and a less heavy weight method that would only append a server supplied abstract &#8220;page version&#8221; field to the request URI, I went for the latter. My resulting nginx virtual host config was looking like this:</p>
<p><span id="more-17"></span></p>

<div class="wp_codebox_msgheader"><span class="right"><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples" target="_blank" title="WP-CodeBox HowTo?"><span style="color: #99cc00">?</span></a></sup></span><span class="left"><a href="javascript:;" onclick="javascript:showCodeTxt('p17code12'); return false;">View Code</a> APACHE</span><div class="codebox_clear"></div></div><div class="wp_codebox"><table><tr id="p1712"><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
</pre></td><td class="code" id="p17code12"><pre class="apache" style="font-family:monospace;"><span style="color: #adadad; font-style: italic;"># define application servers</span>
upstream backend
{
  server 127.0.0.1:<span style="color: #ff0000;">8080</span> weight=<span style="color: #ff0000;">1</span>;
}
&nbsp;
server
{
  <span style="color: #00007f;">listen</span> <span style="color: #ff0000;">80</span>;
  server_name domain.com;
  access_log /var/log/nginx/domain.com.log;
&nbsp;
  location /
  {
    <span style="color: #adadad; font-style: italic;"># we never cache post requests</span>
    if ($request_method = POST)
    {
      proxy_pass http://backend;
      break;
    }
&nbsp;
    <span style="color: #adadad; font-style: italic;"># extract cache key args and compute cache key</span>
    if ($http_cookie ~* <span style="color: #7f007f;">&quot;pv=([^;]+)(?:;|$)&quot;</span>)
    {
      set $page_version $<span style="color: #ff0000;">1</span>;
    }
    set $memcached_key $request_uri&amp;amp;pv=$page_version;
&nbsp;
    <span style="color: #adadad; font-style: italic;"># Check if local memcached server can answer this request</span>
    default_type text/html;
    memcached_pass 127.0.0.1:<span style="color: #ff0000;">11211</span>;
&nbsp;
    <span style="color: #adadad; font-style: italic;"># Send to app. server if Memcached could not answer the request</span>
    error_page <span style="color: #ff0000;">404</span> = @cache_miss;
  }
&nbsp;
  location @cache_miss
  {
    proxy_pass http://backend;
  }
}</pre></td></tr></table></div>

<p>The important lines are line 23-27. Line 23 tests the cookies header of the current request for the presence of a cookie named &#8216;pv&#8217; using a regular expression and line 25 stores the extracted value of this cookie in a temporary variable. Later, in line 27 we combine the value of that variable with the request URI to form the final memcached key. For example if the request uri would be /foo and the headers would contain a cookie pv=acme123, then the cache key would be /foo&amp;pv=acme123.</p>
<p>Line 31 is where the actual memcached lookup happens. If the computed key is present in the cache, the cached page is returned immediately &#8211; completely bypassing the backend. If the page (or the specific version of the page) is not present in the cache, then the alternate branch at line 37 is taken which ultimately forwards the request to our load balanced cluster of application servers in line 39. The actual servers making up the cluster is defined at line 2. There&#8217;s currently only one lonely application server defined there and that&#8217;s a apache 2.2 listening on localhost port 8080 running our django application through mod_wsgi.</p>
<p>So far I have explained how the front line request processing in nginx works but how is invalidation handled? To tackle that problem I wrote a django middleware that can be applied to arbitrary django views using a decorator:<br />
<br/></p>

<div class="wp_codebox_msgheader"><span class="right"><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples" target="_blank" title="WP-CodeBox HowTo?"><span style="color: #99cc00">?</span></a></sup></span><span class="left"><a href="javascript:;" onclick="javascript:showCodeTxt('p17code13'); return false;">View Code</a> PYTHON</span><div class="codebox_clear"></div></div><div class="wp_codebox"><table><tr id="p1713"><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="code" id="p17code13"><pre class="python" style="font-family:monospace;">@cache_page_nginx<span style="color: black;">&#40;</span><span style="color: #ff4500;">3600</span><span style="color: #66cc66;">*</span><span style="color: #ff4500;">6</span>, compute_common_page_version<span style="color: black;">&#41;</span>
<span style="color: #ff7700;font-weight:bold;">def</span> index<span style="color: black;">&#40;</span>request<span style="color: black;">&#41;</span>:
  <span style="color: #ff7700;font-weight:bold;">return</span> render_to_response<span style="color: black;">&#40;</span><span style="color: #483d8b;">'index.html'</span><span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> compute_common_page_version<span style="color: black;">&#40;</span>request<span style="color: black;">&#41;</span>:
  <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'This method is called by the nginx_cache decorator
  The method is supposed to return some value that can be used to distinguish
  different cached versions of the same page. For now it is sufficient to differentiate
  between authenticated and anonymous users'</span><span style="color: #483d8b;">''</span>
  <span style="color: #ff7700;font-weight:bold;">if</span> request.<span style="color: #dc143c;">user</span> <span style="color: #ff7700;font-weight:bold;">and</span> request.<span style="color: #dc143c;">user</span>.<span style="color: black;">is_authenticated</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #ff4500;">1</span>
  <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #ff4500;">0</span></pre></td></tr></table></div>

<p>Some readers will notice that the decorator looks very similar to Django&#8217;s built-in cache_page decorator. And it actually works similar but with several important differences:</p>
<ul>
<li>It totally relies on nginx doing the actual cache lookup. The decorator is only responsible for storing the rendered content of page in the cache.</li>
<li>It bypasses the Django cache framework and talks to memcached directly using cmemcached or python-memcached. This was done because there&#8217;s reallly no point in supporting an additional level of abstraction if the consumer of the cached content won&#8217;t look for it anywhere but in memcached.</li>
</ul>
<p>A word about the compute_common_page_version function. What does it do? The purpose of this method is to ensure that the cache will contain at maximum two distinct versions of the home page. Once for authenticated users and one for anonymous users. For more complex scenarios, the complexity of this method would also increase, resulting on more distinct versions of the same page. Obviously the difficulty lies in maintaining data integrity for every user while not producing to many cached versions of a page, thus increasing memory pressure on the server and lowering cache hit ratio.<br />
<br/><br />
The decorator:<br />
<br/></p>

<div class="wp_codebox_msgheader"><span class="right"><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples" target="_blank" title="WP-CodeBox HowTo?"><span style="color: #99cc00">?</span></a></sup></span><span class="left"><a href="javascript:;" onclick="javascript:showCodeTxt('p17code14'); return false;">View Code</a> PYTHON</span><div class="codebox_clear"></div></div><div class="wp_codebox"><table><tr id="p1714"><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
</pre></td><td class="code" id="p17code14"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">from</span> django.<span style="color: black;">utils</span>.<span style="color: black;">decorators</span> <span style="color: #ff7700;font-weight:bold;">import</span> decorator_from_middleware
<span style="color: #ff7700;font-weight:bold;">from</span> django.<span style="color: black;">conf</span> <span style="color: #ff7700;font-weight:bold;">import</span> settings
<span style="color: #ff7700;font-weight:bold;">from</span> django.<span style="color: black;">core</span>.<span style="color: black;">cache</span> <span style="color: #ff7700;font-weight:bold;">import</span> cache
<span style="color: #ff7700;font-weight:bold;">from</span> django.<span style="color: black;">utils</span>.<span style="color: black;">encoding</span> <span style="color: #ff7700;font-weight:bold;">import</span> smart_unicode, smart_str
<span style="color: #ff7700;font-weight:bold;">from</span> utils <span style="color: #ff7700;font-weight:bold;">import</span> kdebug
&nbsp;
<span style="color: #ff7700;font-weight:bold;">try</span>:
    <span style="color: #ff7700;font-weight:bold;">import</span> cmemcache <span style="color: #ff7700;font-weight:bold;">as</span> memcache
<span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">ImportError</span>:
    <span style="color: #ff7700;font-weight:bold;">try</span>:
        <span style="color: #ff7700;font-weight:bold;">import</span> memcache
    <span style="color: #ff7700;font-weight:bold;">except</span>:
        <span style="color: #ff7700;font-weight:bold;">raise</span> InvalidCacheBackendError<span style="color: black;">&#40;</span><span style="color: #483d8b;">&quot;Memcached cache backend requires either the 'memcache' or 'cmemcache' library&quot;</span><span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">class</span> UpdateCacheMiddleware<span style="color: black;">&#40;</span><span style="color: #008000;">object</span><span style="color: black;">&#41;</span>:
  <span style="color: #483d8b;">&quot;&quot;&quot;
  Updates memcached with the response of the request. It is of _paramount_
  importance that the generated cache_key matches exactly the key generated
  by your web to lookup the page from the cache.
&nbsp;
  This class talks to memcached, bypassing Djangos cache backend because
  it is only meant to talk to memcached and nothing else.
&nbsp;
  Must the first piece of middleware in settings.MIDDLEWARE_CLASSES so
  that it'll get called last during the response phase.
  &quot;&quot;&quot;</span>
  <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, cache_timeout, page_version_method<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'timeout - is the timeout in seconds after which the cached response expires in memcached
    page_version_method - is called during response processing and must return an arbitrary value
    that can be used to distinguish different cached versions of the same page '</span><span style="color: #483d8b;">''</span>
    <span style="color: #008000;">self</span>.<span style="color: black;">cache_timeout</span> = cache_timeout
    <span style="color: #008000;">self</span>.<span style="color: black;">page_version_method</span> = page_version_method
    <span style="color: #008000;">self</span>.<span style="color: black;">cache</span> = memcache.<span style="color: black;">Client</span><span style="color: black;">&#40;</span>settings.<span style="color: black;">NGINX_MEMCACHED_SERVERS</span><span style="color: black;">&#41;</span>
    <span style="color: #008000;">self</span>.<span style="color: black;">cookie_name</span> = <span style="color: #483d8b;">'pv'</span>
&nbsp;
  <span style="color: #ff7700;font-weight:bold;">def</span> process_response<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, request, response<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;Sets the cache, if needed.&quot;&quot;&quot;</span>
    <span style="color: #ff7700;font-weight:bold;">if</span> <span style="color: #ff7700;font-weight:bold;">not</span> settings.<span style="color: black;">NGINX_MEMCACHED_ENABLE</span> <span style="color: #ff7700;font-weight:bold;">or</span> request.<span style="color: black;">method</span> <span style="color: #66cc66;">!</span>= <span style="color: #483d8b;">'GET'</span> <span style="color: #ff7700;font-weight:bold;">or</span> <span style="color: #ff7700;font-weight:bold;">not</span> response.<span style="color: black;">status_code</span> == <span style="color: #ff4500;">200</span>:
      <span style="color: #808080; font-style: italic;"># because of interactions between this middleware and the</span>
      <span style="color: #808080; font-style: italic;"># HTTPMiddleware, which throws the body of a HEAD-request</span>
      <span style="color: #808080; font-style: italic;"># away before this middleware gets a chance to cache it.</span>
      <span style="color: #ff7700;font-weight:bold;">return</span> response
&nbsp;
    <span style="color: #008000;">self</span>.<span style="color: black;">cache_response</span><span style="color: black;">&#40;</span>request, response<span style="color: black;">&#41;</span>
    <span style="color: #ff7700;font-weight:bold;">return</span> response
&nbsp;
  <span style="color: #ff7700;font-weight:bold;">def</span> cache_response<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, request, response<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'Manually insertion of cached version of page into the cache'</span><span style="color: #483d8b;">''</span>
    <span style="color: #808080; font-style: italic;"># retrieve page version</span>
    pv = <span style="color: #008000;">self</span>.<span style="color: black;">page_version_method</span><span style="color: black;">&#40;</span>request<span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># compute key that's follows the same naming convention as specified in nginx configuration:</span>
    <span style="color: #808080; font-style: italic;"># set $memcached_key $request_uri&amp;sid=$session_id;</span>
    cache_key = <span style="color: #008000;">self</span>.<span style="color: black;">compute_cache_key_from_request</span><span style="color: black;">&#40;</span>request, pv<span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># update cache</span>
    <span style="color: #008000;">self</span>.<span style="color: black;">cache</span>.<span style="color: #008000;">set</span><span style="color: black;">&#40;</span>cache_key, response._get_content<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>, <span style="color: #008000;">self</span>.<span style="color: black;">cache_timeout</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># communicate the page version to the browser using cookie</span>
    response.<span style="color: black;">set_cookie</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">cookie_name</span>, pv<span style="color: black;">&#41;</span>
&nbsp;
  <span style="color: #ff7700;font-weight:bold;">def</span> compute_cache_key_from_request<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, request, page_version<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'Computes the cache key for the specified page and version of the page'</span><span style="color: #483d8b;">''</span>
    <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">self</span>.<span style="color: black;">compute_cache_key</span><span style="color: black;">&#40;</span>request.<span style="color: black;">get_full_path</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>, page_version<span style="color: black;">&#41;</span>
&nbsp;
  <span style="color: #ff7700;font-weight:bold;">def</span> compute_cache_key<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, page, page_version<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'Computes the cache key for the specified page and version of the page'</span><span style="color: #483d8b;">''</span>
    <span style="color: #ff7700;font-weight:bold;">return</span> smart_str<span style="color: black;">&#40;</span><span style="color: #483d8b;">&quot;%s&amp;%s=%s&quot;</span> <span style="color: #66cc66;">%</span> <span style="color: black;">&#40;</span>page, <span style="color: #008000;">self</span>.<span style="color: black;">cookie_name</span>, page_version<span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
&nbsp;
  <span style="color: #ff7700;font-weight:bold;">def</span> invalidate_page_version_from_request<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, request, page_version<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'Removes the specified version of the page requested by '</span>request<span style="color: #483d8b;">' from the cache'</span><span style="color: #483d8b;">''</span>
    cache_key = <span style="color: #008000;">self</span>.<span style="color: black;">compute_cache_key_from_request</span><span style="color: black;">&#40;</span>request, page_version<span style="color: black;">&#41;</span>
    <span style="color: #008000;">self</span>.<span style="color: black;">cache</span>.<span style="color: black;">delete</span><span style="color: black;">&#40;</span>cache_key<span style="color: black;">&#41;</span>
&nbsp;
  <span style="color: #ff7700;font-weight:bold;">def</span> invalidate_page_version<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, page, page_version<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'Removes the specified version of the page requested by '</span>request<span style="color: #483d8b;">' from the cache'</span><span style="color: #483d8b;">''</span>
    cache_key = <span style="color: #008000;">self</span>.<span style="color: black;">compute_cache_key</span><span style="color: black;">&#40;</span>request, page_version<span style="color: black;">&#41;</span>
    <span style="color: #008000;">self</span>.<span style="color: black;">cache</span>.<span style="color: black;">delete</span><span style="color: black;">&#40;</span>cache_key<span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># decorator</span>
cache_page_nginx = decorator_from_middleware<span style="color: black;">&#40;</span>UpdateCacheMiddleware<span style="color: black;">&#41;</span></pre></td></tr></table></div>

<img src="http://weichhold.com/?ak_action=api_record_view&id=17&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://weichhold.com/2008/09/12/django-nginx-memcached-the-dynamic-trio/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
