Nick Sieger: RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-futag:blog.nicksieger.com,2005:TypoTypo2007-08-31T17:32:33+00:00Chrisurn:uuid:d3904d11-c577-42d4-9fe5-7cd910e90efc2007-05-21T22:30:36+00:002007-08-31T17:32:33+00:00Comment on RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-fu by Chris<p>Awesome notes! Great job, man.</p>Raveniiurn:uuid:9f3b2ec5-684a-45cb-9c62-6b001b93ce962007-05-20T05:05:35+00:002007-08-31T17:32:33+00:00Comment on RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-fu by Ravenii<p>Thanks Nick, I tried to trackback but I may have failed! here is a part I wrote;
“Once I found that Nick was blogging, and found that I can catch up with presentations, that I have missed!, I gave up even thinking about writing. He did write a lot of very good material, I spent a lot of time reading his blog already!”</p>Nick Siegerurn:uuid:40171862-80d2-460f-9ad6-ead9fe29fd912007-05-19T20:34:00+00:002007-07-13T09:45:27+00:00RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-fu<p>Chris is here to talk about games, since he used to work for Gamespot. He coded PHP, which is like training wheels without the bike. He had to sit in a glass cube and help keep the site running during E3 last year. There were 100 gajillion teenage boys during their lunch break hitting refresh, and it all blew up. Couldn’t even gzip the responses, because the servers heated up to much. They served 50M pages in a day, without downtime. They did it with Memcache.</p>
<p>Memcache is a distributed hash – multiple daemons running on different servers. Developed by Livejournal for their infrastructure, you just put up the servers, and they just work.</p>
<p>Should you use Memcache? No. <a href="http://c2.com/xp/YouArentGonnaNeedIt.html">YAGNI</a>, UYRDNI (unless you really do need it).</p>
<h2>Rails and Memcache</h2>
<p>Fragments, Actions, Sessions, Objects, cache it all. You can use:</p>
<ul>
<li><code>memcache-client</code> (by Robot-coop guys/Eric Hodel). Marshal.unload is 40 times faster than Object.new/loading from the database.</li>
<li>CachedModel – integration with ActiveRecord</li>
<li>Fragment Cache Store</li>
<li>Memcache session store</li>
</ul>
<p>…or…</p>
<h2><code>cache_fu</code></h2>
<p>Or, <code>acts_as_cached</code>. It knows about all the aforementioned objects, with a single YAML config file (<code>config/memcached.yml</code>). Word to the wise: don’t use names in your server config file. Use IPs, avoid BIND and connections to the servers with every connection. Don’t let DNS outages bring down your servers.</p>
<ul>
<li><code>get_cache</code></li>
<li><code>expire_cache</code></li>
</ul>
<p>This is all you need – if you’re using <code>set_cache</code>, you probably don’t understand how the plugin works. Expire cache on the “after save” hook, which allows you to cache ID misses as well.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">Presentation</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">acts_as_cached</span>
<span class="ident">after_save</span> <span class="symbol">:expire_cache</span>
<span class="keyword">end</span></code></pre></div>
<p>Example: only cache published items</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">Presentation</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">acts_as_cached</span> <span class="symbol">:conditions</span> <span class="punct">=></span> <span class="punct">'</span><span class="string">published = 1</span><span class="punct">'</span>
<span class="keyword">end</span></code></pre></div>
<p>Cached-scoped-finders (if somebody thinks of a good name, let Chris know). The idea is to move custom finder logic to a method on your model, and then wrap a cache-scoping thingy around it. <code>cache_fu</code> ties this up nicely by giving you a <code>cached</code> method on AR::Base.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">Topic</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="keyword">def </span><span class="method">self.weekly_popular</span>
<span class="constant">Topic</span><span class="punct">.</span><span class="ident">find</span> <span class="symbol">:all</span><span class="punct">,</span> <span class="punct">...</span>
<span class="keyword">end</span>
<span class="keyword">end</span>
<span class="constant">Topic</span><span class="punct">.</span><span class="ident">cached</span><span class="punct">(</span><span class="symbol">:weekly_popular</span><span class="punct">)</span></code></pre></div>
<p>Adding date to cache key with <code>alias_method_chain</code>:</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">def </span><span class="method">self.cache_key_with_date</span><span class="punct">(</span><span class="ident">id</span><span class="punct">)</span>
<span class="punct">...</span>
<span class="keyword">end</span>
<span class="keyword">class </span><span class="punct"><<</span> <span class="constant">self</span>
<span class="ident">alias_method_chain</span> <span class="symbol">:cache_key</span><span class="punct">,</span> <span class="symbol">:date</span>
<span class="keyword">end</span></code></pre></div>
<p>Cached loads by ID: <code>Topic.find(1, 2, 3)</code> moves to <code>Topic.get_cache(1, 2, 3)</code>, which can parallelize calls to memcached and bring them back as they’re ready.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="ident">user_ids</span> <span class="punct">=</span> <span class="attribute">@topic</span><span class="punct">.</span><span class="ident">posts</span><span class="punct">.</span><span class="ident">map</span><span class="punct">(&</span><span class="symbol">:user_id</span><span class="punct">).</span><span class="ident">uniq</span>
<span class="attribute">@users</span> <span class="punct">=</span> <span class="constant">User</span><span class="punct">.</span><span class="ident">get_cache</span><span class="punct">(</span><span class="ident">user_ids</span><span class="punct">)</span></code></pre></div>
<p>You can also cache associations, so that you’re navigating associations via Memcache.</p>
<p>Cache overrides</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">before_filter</span> <span class="symbol">:set_cache_override</span>
<span class="keyword">def </span><span class="method">set_cache_override</span>
<span class="constant">ActsAsCached</span><span class="punct">.</span><span class="ident">skip_cache_gets</span> <span class="punct">=</span> <span class="punct">!!</span><span class="ident">params</span><span class="punct">[</span><span class="symbol">:skip_cache</span><span class="punct">]</span>
<span class="keyword">end</span>
<span class="keyword">end</span></code></pre></div>
<p><code>reset_cache</code>: Slow, uncached operations can sometimes queue up and wedge a site. Instead, issue cache resets on completion of a request, rather than expiring beforehand. That way, requests that continue to pile up will still use the cached copy until the rebuild is complete.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">Presentation</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">after_save</span> <span class="symbol">:reset_cache</span>
<span class="keyword">end</span></code></pre></div>
<p>Versioning: a way to expire cache on new code releases</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">Presentation</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">acts_as_cached</span> <span class="symbol">:version</span> <span class="punct">=></span> <span class="number">1</span>
<span class="keyword">end</span></code></pre></div>
<p>Deployment: Chris recommends using Monit to ensure your Memcache servers are up.</p>
<p><code>libketama</code>: consistent hashing that gives you the ability to redeploy Memcache servers without invalidating all the keys.</p>
<p>Q: Page caching? A: Nginx with native Memcache page caching, but outside of Rails domains.</p>
<p>Lots of other questions, but dude, Chris talks too fast!</p>