Nick Sieger: RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-fu tag:blog.nicksieger.com,2005:Typo Typo 2010-11-22T18:47:22+00:00 Chris urn:uuid:d3904d11-c577-42d4-9fe5-7cd910e90efc 2007-05-21T22:30:36+00:00 2010-11-22T18:47:22+00:00 Comment on RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-fu by Chris <p>Awesome notes! Great job, man&#46;</p> Ravenii urn:uuid:9f3b2ec5-684a-45cb-9c62-6b001b93ce96 2007-05-20T05:05:35+00:00 2010-11-22T18:47:23+00:00 Comment 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; &#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&#46; He did write a lot of very good material, I spent a lot of time reading his blog already!&#8221;</p> Nick Sieger urn:uuid:40171862-80d2-460f-9ad6-ead9fe29fd91 2007-05-19T20:34:00+00:00 2010-11-22T18:23:55+00:00 RailsConf 2007: Chris Wanstrath: Kickin' Ass with Cache-fu <p>Chris is here to talk about games, since he used to work for Gamespot&#46; He coded PHP, which is like training wheels without the bike&#46; He had to sit in a glass cube and help keep the site running during E3 last year&#46; There were 100 gajillion teenage boys during their lunch break hitting refresh, and it all blew up&#46; Couldn&#8217;t even gzip the responses, because the servers heated up to much&#46; They served 50M pages in a day, without downtime&#46; They did it with Memcache&#46;</p> <p>Memcache is a distributed hash &#45;&#45; multiple daemons running on different servers&#46; Developed by Livejournal for their infrastructure, you just put up the servers, and they just work&#46;</p> <p>Should you use Memcache? No&#46; <a href="http://c2.com/xp/YouArentGonnaNeedIt.html">YAGNI</a>, UYRDNI (unless you really do need it)&#46;</p> <h2>Rails and Memcache</h2> <p>Fragments, Actions, Sessions, Objects, cache it all&#46; You can use:</p> <ul> <li><code>memcache-client</code> (by Robot&#45;coop guys/Eric Hodel)&#46; Marshal&#46;unload is 40 times faster than Object&#46;new/loading from the database&#46;</li> <li>CachedModel &#45;&#45; integration with ActiveRecord</li> <li>Fragment Cache Store</li> <li>Memcache session store</li> </ul> <p>&#46;&#46;&#46;or&#46;&#46;&#46;</p> <h2><code>cache_fu</code></h2> <p>Or, <code>acts_as_cached</code>&#46; It knows about all the aforementioned objects, with a single YAML config file (<code>config/memcached.yml</code>)&#46; Word to the wise: don&#8217;t use names in your server config file&#46; Use IPs, avoid BIND and connections to the servers with every connection&#46; Don&#8217;t let DNS outages bring down your servers&#46;</p> <ul> <li><code>get_cache</code></li> <li><code>expire_cache</code></li> </ul> <p>This is all you need &#45;&#45; if you&#8217;re using <code>set_cache</code>, you probably don&#8217;t understand how the plugin works&#46; Expire cache on the &#8220;after save&#8221; hook, which allows you to cache ID misses as well&#46;</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&#45;scoped&#45;finders (if somebody thinks of a good name, let Chris know)&#46; The idea is to move custom finder logic to a method on your model, and then wrap a cache&#45;scoping thingy around it&#46; <code>cache_fu</code> ties this up nicely by giving you a <code>cached</code> method on AR::Base&#46;</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&#46;</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&#46;</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&#46; Instead, issue cache resets on completion of a request, rather than expiring beforehand&#46; That way, requests that continue to pile up will still use the cached copy until the rebuild is complete&#46;</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&#46;</p> <p><code>libketama</code>: consistent hashing that gives you the ability to redeploy Memcache servers without invalidating all the keys&#46;</p> <p>Q: Page caching? A: Nginx with native Memcache page caching, but outside of Rails domains&#46;</p> <p>Lots of other questions, but dude, Chris talks too fast!</p>