Nick Sieger: RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-fu http://blog.nicksieger.com/articles/2007/05/19/railsconf-2007-chris-wanstrath-kickin-ass-with-cache-fu en-us 40 "RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-fu" by Chris <p>Awesome notes! Great job, man.</p> Mon, 21 May 2007 22:30:36 +0000 urn:uuid:d3904d11-c577-42d4-9fe5-7cd910e90efc http://blog.nicksieger.com/articles/2007/05/19/railsconf-2007-chris-wanstrath-kickin-ass-with-cache-fu#comment-252 "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; &#8220;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!&#8221;</p> Sun, 20 May 2007 05:05:35 +0000 urn:uuid:9f3b2ec5-684a-45cb-9c62-6b001b93ce96 http://blog.nicksieger.com/articles/2007/05/19/railsconf-2007-chris-wanstrath-kickin-ass-with-cache-fu#comment-249 RailsConf 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&#8217;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 &#8211; 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 &#8211; integration with ActiveRecord</li> <li>Fragment Cache Store</li> <li>Memcache session store</li> </ul> <p>&#8230;or&#8230;</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&#8217;t use names in your server config file. Use IPs, avoid BIND and connections to the servers with every connection. Don&#8217;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 &#8211; if you&#8217;re using <code>set_cache</code>, you probably don&#8217;t understand how the plugin works. Expire cache on the &#8220;after save&#8221; 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">&lt;</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">&lt;</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">=&gt;</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">&lt;</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">&lt;&lt;</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&#8217;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">(&amp;</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&#8217;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">&lt;</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">&lt;</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">&lt;</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">=&gt;</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> Sat, 19 May 2007 20:34:00 +0000 urn:uuid:40171862-80d2-460f-9ad6-ead9fe29fd91 Nick Sieger http://blog.nicksieger.com/articles/2007/05/19/railsconf-2007-chris-wanstrath-kickin-ass-with-cache-fu railsconf railsconf2007 http://blog.nicksieger.com/articles/trackback/247