<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
	<channel>
		<title>stdout.heyzap - heyzap engineering blog</title>
		<description>Heyzap's engineering blog, sharing the lessons we learn while building Heyzap</description>
		<link>http://stdout.heyzap.com</link>
		
			<item>
				<title>Surfacing Interesting Content</title>
				<description>&lt;p&gt;Heyzap is a social network for mobile gamers, and as such, we get lots of user generated content. This is a collection algorithms we&amp;#8217;ve been using to surface the most interesting content.&lt;/p&gt;

&lt;h2 id='currently_popular'&gt;Currently Popular&lt;/h2&gt;

&lt;p&gt;Do you need to know which songs your users are listening to? Which tags are trending on Twitter? No need to break out a cron job, this algorithm will keep you up to date in real-time.&lt;/p&gt;
&lt;canvas id='exponential' height='200px' width='400px'&gt;
&lt;/canvas&gt;&lt;br /&gt;&lt;div class='clearfix'&gt;
  &lt;div style='float:left; margin-right:10px;'&gt; Half-life:&lt;/div&gt;
  &lt;div id='pop-halflife-slider' style='width:200px; margin:2px; float:left'&gt;

  &lt;/div&gt;

  &lt;div style='clear:both; float:left; margin-right:10px;'&gt; Vote Rate:&lt;/div&gt;
  &lt;div id='pop-comment-rate-slider' style='width:200px; margin:2px; float:left;'&gt;

  &lt;/div&gt;

  &lt;div style='clear:both; float:left; margin-right:10px;'&gt; Vote Distribution:&lt;/div&gt;
  &lt;div id='pop-vote-distribution-slider' style='width:200px; margin:2px; float:left;'&gt;

  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id='what_it_does'&gt;What it Does&lt;/h3&gt;

&lt;p&gt;We use this algorithm to find popular games trending on our network. Each time a user plays a game, we cast a &amp;#8220;vote&amp;#8221; for that game. Each vote has a &amp;#8220;score&amp;#8221;, which decays with age. For our popular games, votes decay at a rate of 50% per week. To display the most popular games, add up the all scores for each of the votes. Using this algorithm, a game played 20 times this week will be ranked higher than a game played 30 times last week, and less than a game played 50 times last week.&lt;/p&gt;

&lt;p&gt;Similarly, if you wanted to track trending hashtags, you would cast a vote each time a tag appears. You could also use this algorithm to track word frequencies in news articles, or which countries are visiting your site.&lt;/p&gt;

&lt;p&gt;In the visualization above, votes are cast randomly at a set of items. The orange bars indicate the current &amp;#8220;popularity score&amp;#8221; of each item, and the red bars indicate the probabilistic rate at which each item should accrue new votes.&lt;/p&gt;

&lt;p&gt;The longer the half-life, the slower the algorithm will respond to new votes. At the extreme ends, a half-life of zero would answer &amp;#8220;Which post was most recently voted on?&amp;#8221;, whereas a half-life of infinity would answer &amp;#8220;Which post has the most votes?&amp;#8221;.&lt;/p&gt;

&lt;h3 id='how_it_works'&gt;How it works&lt;/h3&gt;

&lt;p&gt;A straightforward implementation using a cron job might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each time a vote is cast, add 1 to the popularity score of the corresponding item.&lt;/li&gt;

&lt;li&gt;Once per day, divide all popularity scores by two.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You would probably actually want to divide the scores by 2^(1/24) each hour to minimize transients and make the decay more continuous.&lt;/p&gt;

&lt;p&gt;However, I loathe cron jobs, so here is an easier method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each time a vote is cast, add 2^((now - epoch) / half_life) to the corresponding item.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we only care about the rank of the popular items, the only difference between the outputs of the two implementations is that this one is perfectly continuous, as opposed to the stuttering decay of the cron variation.&lt;/p&gt;

&lt;p&gt;One drawback to the continious implementation is float overflow. With a carefully chosen epoch, we can make use of a double-precision float&amp;#8217;s 11 exponent bits to allow the algorithm to run for 2048 half-lives. If your half-life is one day, you can run the algorithm for five years before needing to migrate the epoch.&lt;/p&gt;

&lt;h3 id='using_redis_as_an_external_index'&gt;Using Redis as an External Index&lt;/h3&gt;

&lt;p&gt;In all my examples, I&amp;#8217;m using Redis as an external index. You could add a column and an index to your posts table, but it&amp;#8217;s probably huge, which presents its own limitations. Additionally, since we only care about the most popular items, we can save memory by only indexing the top few thousand items.&lt;/p&gt;

&lt;p&gt;If you&amp;#8217;re not familiar with Redis, I&amp;#8217;m using ZSETs. ZSETs are sorted sets. Half-array, half-dictionary. The value in the dictionary corresponds to the key&amp;#8217;s relative &amp;#8220;index&amp;#8221; in the array. They have O(Log(N)) inserts, O(Log(N)) slices, and are indexed by double-preciesion foats, which make them perfect for this implementation.&lt;/p&gt;

&lt;h3 id='implementation'&gt;Implementation&lt;/h3&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;PopularStream&lt;/span&gt;
  &lt;span class='no'&gt;STREAM_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;popular_stream&amp;quot;&lt;/span&gt;
  &lt;span class='no'&gt;HALF_LIFE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;day&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt;

  &lt;span class='c1'&gt;# 2.5 \* half_life (in days) years from now&lt;/span&gt;
  &lt;span class='no'&gt;EPOCH&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Date&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2015&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt;
 
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;onVote&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='c1'&gt;# dict[post.id] += value&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zincrby&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt; &lt;span class='no'&gt;EPOCH&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='no'&gt;HALF_LIFE&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
    &lt;span class='n'&gt;trim&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;10000&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;limit&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='mi'&gt;20&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='c1'&gt;# arr.sort.reverse[0, limit]&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zrevrange&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;limit&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;map&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='ss'&gt;:to_i&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;trim&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;key&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;n&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='c1'&gt;# arr = arr[-n, n]&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zremrangebyrank&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;key&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='n'&gt;n&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='nb'&gt;rand&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='n'&gt;n&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='c1'&gt;# run this in five years&lt;/span&gt;
  &lt;span class='c1'&gt;# you could make EPOCH and STREAM_KEY dynamic&lt;/span&gt;
  &lt;span class='c1'&gt;# to make this process easier. Otherwise migrate and deploy the new values&lt;/span&gt;
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;migrate&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;new_key&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;new_epoch&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zunionstore&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;new_key&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:weights&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='n'&gt;new_epoch&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt; &lt;span class='no'&gt;EPOCH&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='n'&gt;half_life&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='hot_stream'&gt;Hot Stream&lt;/h2&gt;

&lt;p&gt;If the age of the post is more relevant than the age of the votes, we can simplify things considerably by treating all votes as though they were cast at the time the post was created. This is the algorithm used by Reddit&amp;#8217;s front page.&lt;/p&gt;

&lt;h3 id='what_it_does'&gt;What it does&lt;/h3&gt;

&lt;p&gt;If we start the decay for all votes on a post at the same time, we can simplify the formula for a posts score to:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;&lt;span class='n'&gt;post_creation_time&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='n'&gt;half_life&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='n'&gt;log2&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;votes&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In the visualization below, votes are cast randomly on a series of posts. Each column represents the &amp;#8220;hot&amp;#8221; score of each post. The tallest column would be the #1 post on the &amp;#8220;hot&amp;#8221; page, the second tallest #2, and so on.&lt;/p&gt;
&lt;canvas id='hot' height='200px' width='400px'&gt;
&lt;/canvas&gt;&lt;br /&gt;&lt;div class='clearfix'&gt;
  &lt;div style='float:left; margin-right:10px;'&gt; Half Life:&lt;/div&gt;
  &lt;div id='hot-percolate-slider' style='width:200px; margin:2px; float:left'&gt;
  &lt;/div&gt;

  &lt;div style='clear:both; float:left; margin-right:10px;'&gt; Vote Rate:&lt;/div&gt;
  &lt;div id='hot-comment-rate-slider' style='width:200px; margin:2px; float:left;'&gt;
  &lt;/div&gt;

  &lt;div style='clear:both; float:left; margin-right:10px;'&gt; Vote Distribution:&lt;/div&gt;
  &lt;div id='hot-vote-distribution-slider' style='width:200px; margin:2px; float:left;'&gt;
  &lt;/div&gt;

&lt;/div&gt;
&lt;h3 id='how_it_works'&gt;How it works&lt;/h3&gt;
&lt;img src='/img/logvotes.png' /&gt;
&lt;p&gt;As I&amp;#8217;ve tried to show in the picture above, adding a constant to log(votes) is the same as multiplying votes by a constant. log(c) + log(n) = log(n*c). So, each half-life we add to log(votes) doubles those votes power, giving us the same decay we had in the previous algorithm.&lt;/p&gt;

&lt;p&gt;This means we don&amp;#8217;t have to worry about overflows anymore!&lt;/p&gt;

&lt;h3 id='implementation'&gt;Implementation&lt;/h3&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;HotStream&lt;/span&gt;
  &lt;span class='no'&gt;STREAM_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;hot_stream&amp;quot;&lt;/span&gt;

  &lt;span class='c1'&gt;# How long until a post with 100 votes is less interesting than one with 10 votes?&lt;/span&gt;
  &lt;span class='c1'&gt;# Reddit uses 12 hours&lt;/span&gt;
  &lt;span class='no'&gt;TENTH_LIFE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;12&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;hours&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt;
  
  &lt;span class='c1'&gt;# just to make it clear it&amp;#39;s still the same algorithm&lt;/span&gt;
  &lt;span class='no'&gt;HALF_LIFE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;TENTH_LIFE&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='no'&gt;Math&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='no'&gt;Math&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; 

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;onVote&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='c1'&gt;# dict[post.id] = value&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zadd&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;created_at&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='no'&gt;TENTH_LIFE&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='no'&gt;Math&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;log10&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;votes&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
    &lt;span class='n'&gt;trim&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;10000&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
 
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;limit&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;20&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='c1'&gt;# arr.sort.reverse[0, limit]&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zrevrange&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;limit&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='drip_stream'&gt;Drip Stream&lt;/h2&gt;

&lt;p&gt;This algorithm uses the same decay used in the hot steam, plus a threshold to create a Digg-like, rate-limited, append-only stream.&lt;/p&gt;

&lt;h3 id='what_it_does'&gt;What it does&lt;/h3&gt;

&lt;p&gt;Whenever a new post crosses the threshold, the threshold is incremented by the &amp;#8220;drip period&amp;#8221;, and the post is added to the drip stream. Since we&amp;#8217;re constantly increasing the base score of each new post, a new post should be added to the stream once per drip period.&lt;/p&gt;

&lt;p&gt;In the visualization below, votes are cast randomly on a series of posts. Each column represents the &amp;#8220;hot&amp;#8221; score of one post. The threshold is marked with a horizontal red line. As posts cross the threshold and are added to the drip stream, they are marked red.&lt;/p&gt;
&lt;canvas id='drip' height='200px' width='400px'&gt;
&lt;/canvas&gt;&lt;br /&gt;&lt;div class='clearfix'&gt;
  &lt;div style='float:left; margin-right:10px;'&gt; Half Life:&lt;/div&gt;
  &lt;div id='drip-percolate-slider' style='width:200px; margin:2px; float:left'&gt;
  &lt;/div&gt;

  &lt;div style='clear:both; float:left; margin-right:10px;'&gt; Drip Rate:&lt;/div&gt;
  &lt;div id='drip-drip-rate-slider' style='width:200px; margin:2px; float:left;'&gt;
  &lt;/div&gt;

  &lt;div style='clear:both; float:left; margin-right:10px;'&gt; Vote Rate:&lt;/div&gt;
  &lt;div id='drip-comment-rate-slider' style='width:200px; margin:2px; float:left;'&gt;
  &lt;/div&gt;

  &lt;div style='clear:both; float:left; margin-right:10px;'&gt; Vote Distribution:&lt;/div&gt;
  &lt;div id='drip-vote-distribution-slider' style='width:200px; margin:2px; float:left;'&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id='implementation'&gt;Implementation&lt;/h3&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;DripStream&lt;/span&gt;
  &lt;span class='no'&gt;STREAM_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;drip_stream&amp;quot;&lt;/span&gt;
  &lt;span class='no'&gt;THRESHOLD_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;drip_stream_threshold&amp;quot;&lt;/span&gt;

  &lt;span class='c1'&gt;# How long until a post with 100 votes is less interesting than one with 10 votes?&lt;/span&gt;
  &lt;span class='c1'&gt;# Reddit uses 12 hours&lt;/span&gt;
  &lt;span class='no'&gt;TENTH_LIFE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;12&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;hours&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt;

  &lt;span class='c1'&gt;# How often should a new story be pushed to the stream?&lt;/span&gt;
  &lt;span class='no'&gt;DRIP_PERIOD&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;hour&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;newVote&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zscore&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

    &lt;span class='n'&gt;score&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;created_at&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='no'&gt;TENTH_LIFE&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='no'&gt;Math&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;log10&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;points&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; 
    &lt;span class='n'&gt;threshold&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;THRESHOLD_KEY&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;||&lt;/span&gt;&lt;span class='n'&gt;score&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='no'&gt;DRIP_PERIOD&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='no'&gt;TENTH_LIFE&lt;/span&gt;

    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;score&lt;/span&gt; &lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;threshold&lt;/span&gt;
      &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;set&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;THRESHOLD_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;threshold&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='no'&gt;DRIP_PERIOD&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='no'&gt;TENTH_LIFE&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

      &lt;span class='c1'&gt;# dict[post.id] = value&lt;/span&gt;
      &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zadd&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

      &lt;span class='n'&gt;trim&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;10000&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
 
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;limit&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;20&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='c1'&gt;# arr.sort.reverse[0, limit]&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zrevrange&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;limit&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;map&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='ss'&gt;:to_i&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='friends_stream'&gt;Friends Stream&lt;/h2&gt;

&lt;p&gt;This creates a Twitter-like stream of people/places/things you are following.&lt;/p&gt;

&lt;h3 id='isnt_that_trivial'&gt;Isn&amp;#8217;t that trivial?&lt;/h3&gt;

&lt;p&gt;Sure, usually. That&amp;#8217;s why it&amp;#8217;s at the end.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='sql'&gt;&lt;span class='k'&gt;SELECT&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='k'&gt;FROM&lt;/span&gt; &lt;span class='n'&gt;posts&lt;/span&gt; &lt;span class='k'&gt;WHERE&lt;/span&gt; &lt;span class='n'&gt;user_id&lt;/span&gt; &lt;span class='k'&gt;IN&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;7&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;23&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;42&lt;/span&gt;&lt;span class='p'&gt;,...)&lt;/span&gt; &lt;span class='k'&gt;ORDER&lt;/span&gt; &lt;span class='k'&gt;BY&lt;/span&gt; &lt;span class='n'&gt;created_at&lt;/span&gt; &lt;span class='k'&gt;LIMIT&lt;/span&gt; &lt;span class='mi'&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Unfortunately, as you scale, IN queries get slow. Mongo pulls down 20 posts from each user, sorts them all by hand, then crops. When users follow thousands of other users, that gets slow. The SQL databases I tried at the time didn&amp;#8217;t cut it either.&lt;/p&gt;

&lt;p&gt;However, don&amp;#8217;t take my word for it. Just remember this is here if you start seeing thousand-entry IN queries in your slow log.&lt;/p&gt;

&lt;h3 id='how_it_works'&gt;How it works&lt;/h3&gt;

&lt;p&gt;The active ingredient is a ZSET of all users and their most recent post. That ZSET can be quickly intersected with the set of followed users, then sliced to create a list of recently active people you follow.&lt;/p&gt;

&lt;p&gt;In this implementation, I&amp;#8217;m using the actives list to union ZSETs containing each user&amp;#8217;s stream. You could just as easily use the list to pair down the arguments to your IN query.&lt;/p&gt;

&lt;h3 id='implementation'&gt;Implementation&lt;/h3&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;FriendsStream&lt;/span&gt;
  &lt;span class='no'&gt;USER_STREAM_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;user_stream_&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;
  &lt;span class='no'&gt;USER_FRIENDS_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;user_friends_&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;
  &lt;span class='no'&gt;USER_ACTIVE_FRIENDS_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;user_active_friends_&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;
  &lt;span class='no'&gt;FRIENDS_STREAM_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;friend_stream_&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;
  &lt;span class='no'&gt;ACTIVE_USERS_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;active_users&amp;quot;&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;follow&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;to_follow&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sadd&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;USER_FRIENDS_KEY&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;to_follow&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;push&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zadd&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;USER_STREAM_KEY&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;created_at&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;trim&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;USER_STREAM_KEY&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;40&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zadd&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;ACTIVE_USERS_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;user_id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;created_at&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_i&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;trim&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;ACTIVE_USERS_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;10000&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;limit&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='mi'&gt;20&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zinterstore&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;USER_ACTIVE_FRIENDS_KEY&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='no'&gt;ACTIVE_USERS_KEY&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;USER_FRIENDS_KEY&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;]]&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;active_friends&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zrevrangebyscore&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;USER_ACTIVE_FRIENDS_KEY&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;limit&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zunionstore&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;FRIENDS_STREAM_KEY&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;active_friends&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;map&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='no'&gt;USER_STREAM_KEY&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;zrevrange&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;FRIENDS_STREAM_KEY&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;limit&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;map&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='ss'&gt;:to_i&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='hiring_plug'&gt;Hiring Plug&lt;/h2&gt;

&lt;p&gt;Heyzap is always hiring great engineers. If you found this interesting, or better yet obvious, drop us an email. Make sure to mention you read this article (I think I get a bonus).&lt;/p&gt;

&lt;p&gt;Email: &lt;a href='mailto:jobs@heyzap.com'&gt;jobs@heyzap.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;About Us : &lt;a href='http://www.heyzap.com/about'&gt;heyzap.com/about&lt;/a&gt;&lt;/p&gt;
&lt;script src='/js/streams.js' type='text/javascript'&gt;
&lt;/script&gt;&lt;script type='text/javascript'&gt;
  $(document).ready(function(){
    var hotPlot = new Streams.Hot({canvas : $(&quot;#hot&quot;)[0]});
    hotPlot.generator.start();
    
    new Streams.GhettoSlider({
        container : $(&quot;#hot-percolate-slider&quot;)
      , min : 0
      , max : 50
    }).on(&quot;update&quot;, function(value){
      hotPlot.percolate = value;
    }).set(hotPlot.percolate);

    new Streams.GhettoSlider({
        container : $(&quot;#hot-comment-rate-slider&quot;)
      , min : 0
      , max : 60
    }).on(&quot;update&quot;, function(value){
      hotPlot.generator.newVoteRate = 1000 / value;
    }).set(1000 / hotPlot.generator.newVoteRate);

    new Streams.GhettoSlider({
        container : $(&quot;#hot-vote-distribution-slider&quot;)
      , min : 200
      , max : 0
    }).on(&quot;update&quot;, function(value){
      hotPlot.generator.voteDistribution = value;
    }).set(hotPlot.generator.voteDistribution);
    


    var dripPlot = new Streams.Drip({canvas : $(&quot;#drip&quot;)[0]});
    dripPlot.generator.start();
    
    new Streams.GhettoSlider({
        container : $(&quot;#drip-percolate-slider&quot;)
      , min : 0
      , max : 50
    }).on(&quot;update&quot;, function(value){
      dripPlot.percolate = value;
    }).set(dripPlot.percolate);

    new Streams.GhettoSlider({
        container : $(&quot;#drip-drip-rate-slider&quot;)
      , min : 10000
      , max : 0
    }).on(&quot;update&quot;, function(value){
      dripPlot.dripRate = value;
    }).set(dripPlot.dripRate);

    new Streams.GhettoSlider({
        container : $(&quot;#drip-comment-rate-slider&quot;)
      , min : 0
      , max : 60
    }).on(&quot;update&quot;, function(value){
      dripPlot.generator.newVoteRate = 1000 / value;
    }).set(1000 / dripPlot.generator.newVoteRate);

    new Streams.GhettoSlider({
        container : $(&quot;#drip-vote-distribution-slider&quot;)
      , min : 200
      , max : 0
    }).on(&quot;update&quot;, function(value){
      dripPlot.generator.voteDistribution = value;
    }).set(dripPlot.generator.voteDistribution);
    



    var popPlot = new Streams.Exponential({canvas : $(&quot;#exponential&quot;)[0]});
    popPlot.generator.start();

    new Streams.GhettoSlider({
        container : $(&quot;#pop-halflife-slider&quot;)
      , min : 100
      , max : 20000 
    }).on(&quot;update&quot;, function(value){
      popPlot.halfLife = value;
    }).set(popPlot.halfLife);
    
    new Streams.GhettoSlider({
        container : $(&quot;#pop-comment-rate-slider&quot;)
      , min : 0
      , max : 60
    }).on(&quot;update&quot;, function(value){
      popPlot.generator.newVoteRate = 1000 / value;
    }).set(1000 / popPlot.generator.newVoteRate);

    new Streams.GhettoSlider({
        container : $(&quot;#pop-vote-distribution-slider&quot;)
      , min : 200
      , max : 0
    }).on(&quot;update&quot;, function(value){
      popPlot.generator.voteDistribution = value;
    }).set(popPlot.generator.voteDistribution);
  });

&lt;/script&gt;&lt;style&gt;
    
    .clearfix:after {
    	content: &quot;.&quot;;
    	display: block;
    	clear: both;
    	visibility: hidden;
    	line-height: 0;
    	height: 0;
    }
     
    .clearfix {
    	display: inline-block;
    }

    html[xmlns] .clearfix {
    	display: block;
    }
     
    * html .clearfix {
    	height: 1%;
    }
    
    canvas {
      background: #023e82; /* Old browsers */
      background: -moz-linear-gradient(top, #023e82 0%, #146dac 100%); /* FF3.6+ */
      background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#023e82), color-stop(100%,#146dac)); /* Chrome,Safari4+ */
      background: -webkit-linear-gradient(top, #023e82 0%,#146dac 100%); /* Chrome10+,Safari5.1+ */
      background: -o-linear-gradient(top, #023e82 0%,#146dac 100%); /* Opera 11.10+ */
      background: -ms-linear-gradient(top, #023e82 0%,#146dac 100%); /* IE10+ */
      background: linear-gradient(to bottom, #023e82 0%,#146dac 100%); /* W3C */
      filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#023e82', endColorstr='#146dac',GradientType=0 ); /* IE6-9 */
    }
  &lt;/style&gt;</description>
				<published>Mon Apr 08 00:00:00 +0000 2013</published>
				<link>http://stdout.heyzap.com/2013/04/08/surfacing-interesting-content/</link>
			</item>
		
			<item>
				<title>How to Ace a Startup Engineering Interview Part 1</title>
				<description>&lt;p&gt;In the last 5 years I have interviewed hundreds of engineering candidates and I thought it would be valuable to put my thoughts on how people could interview better. This will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Help people be prepared for their next interview&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;Help connect engineers to the correct jobs.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;Hopefully lead to better engineers in the world :)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are two main categories that we judge engineers on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Technical skills: This spans practical and theoretical skills as well as experience that backs these up.&lt;/li&gt;

&lt;li&gt;Non-technical skills: Passion for the space, communication skills, cultural fit all fit here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Part 1, I will focus on technical skills.&lt;/p&gt;

&lt;h2 id='technical_skills_for_an_engineer_at_a_startup'&gt;Technical Skills for an Engineer at a Startup&lt;/h2&gt;

&lt;p&gt;I think about practical skills across 3 main principles:&lt;/p&gt;

&lt;p&gt;&lt;img src='/img/triangle.png' alt='3 Main Principles of Technical Skills' /&gt;&lt;/p&gt;

&lt;p&gt;These are all important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Coding&lt;/em&gt;: Your ability to produce high quality code in a timely manner and avoid rabbit holes.&lt;/li&gt;

&lt;li&gt;&lt;em&gt;Experience&lt;/em&gt;: Relevant experience shows off both your passion and what you can bring to the table from the start.&lt;/li&gt;

&lt;li&gt;&lt;em&gt;Theory&lt;/em&gt;: Theoretical knowledge Is the basis of your engineering ability and means you can quickly attack new problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Preparing for a job interview is the perfect opportunity to take yourself to the next level as a programmer and improve all these abilities.&lt;/p&gt;

&lt;h2 id='improve_your_theoretical_knowledge'&gt;Improve your Theoretical knowledge&lt;/h2&gt;

&lt;h3 id='learn_c'&gt;Learn C&lt;/h3&gt;

&lt;p&gt;C is a fundamental building block of programming. As such, knowing C gives you a strong base of understanding the higher level concepts you will likely need to know as a programmer in a startup.&lt;/p&gt;

&lt;p&gt;Having a basic working knowledge of C does not require too much work and can be picked up in a couple of weeks. C does not have much abstraction above assembly, this means by definition it is relatively simple. Like many others I learned most of C by reading the &lt;a href='http://www.amazon.com/C-Programming-Language-2nd-Edition/dp/0131103628/ref=sr_1_1?ie=UTF8&amp;amp;qid=1343162093&amp;amp;sr=8-1&amp;amp;keywords=C+programming'&gt;C Programming Language&lt;/a&gt; book.&lt;/p&gt;

&lt;p&gt;Once you get your head around pointers and memory management, C is a fun language as it takes you close to the metal of what a processor does and has principles like pointer management that do not exist in most other higher level languages. Understanding these concepts will also help you understand how higher level languages work.&lt;/p&gt;

&lt;h3 id='learn_about_data_structures_and_algorithms'&gt;Learn about Data Structures and Algorithms&lt;/h3&gt;

&lt;p&gt;Although it is relatively rare in web/app development to code up complex algorithms and data structures, data structures and algorithms has had more effect on my thought processes around building complex systems than anything else. Even if you are not making your own data structures, you will be making choices about how to use data structures every day of your programming career, so understanding the basics is crucial.&lt;/p&gt;

&lt;p&gt;I recommend the book, &lt;a href='http://www.amazon.com/Introduction-Algorithms-Thomas-H-Cormen/dp/0262033844/ref=sr_1_6?s=books&amp;amp;ie=UTF8&amp;amp;qid=1343162947&amp;amp;sr=1-6&amp;amp;keywords=Data+Structure'&gt;Introduction to Algorithms&lt;/a&gt;. Since the book is quite long, if you don’t have time to read it all, the basics of sorting, hash tables, binary trees, and string matching sections are highly recommended.&lt;/p&gt;

&lt;p&gt;Once you have a good grasp of data structures and algorithms, you’ll find these concepts put just about everything you do as an engineer into context.&lt;/p&gt;

&lt;h3 id='comparative_programming_languages'&gt;Comparative Programming Languages&lt;/h3&gt;

&lt;p&gt;In a startup, you will often be touching many languages across the technology stack. You may also be required to learn new languages and concepts quickly. To speed up learning, it helps to have a good understanding of various language concepts so you can quickly see their similarities. The best approach is to learn one language in every major style:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Low level&lt;/em&gt;: C.&lt;/li&gt;

&lt;li&gt;&lt;em&gt;Object Oriented&lt;/em&gt;: Java, C++ etc&lt;/li&gt;

&lt;li&gt;&lt;em&gt;Dynamic, high level language&lt;/em&gt;: python, ruby etc&lt;/li&gt;

&lt;li&gt;&lt;em&gt;Functional&lt;/em&gt;: Lisp, Erlang, Haskell etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you haven’t touched a language in one of these categories, it can feel as if you are learning to program all over again when you try it for the first time, which can be a fun experience.&lt;/p&gt;

&lt;h3 id='have_an_expert_level_knowledge_in_at_least_one_language_and_framework'&gt;Have an Expert Level Knowledge in at least one Language and Framework&lt;/h3&gt;

&lt;p&gt;Expert knowledge in a specific language or framework demonstrates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your ability to become an expert and there is no reason why you would be unable to become an expert in other areas as well.&lt;/li&gt;

&lt;li&gt;You understand some of the nuances involved with languages/frameworks and can make decisions on pros and cons of different tech.&lt;/li&gt;

&lt;li&gt;You have the passion to go deep on subjects and get to the heart of a language.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It takes time and energy to get to an expert level. Here are some tips to help&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try to keep doing different projects that push your understanding and knowledge of the language.&lt;/li&gt;

&lt;li&gt;Read blog posts on people doing interesting things in the space. &lt;a href='http://news.ycombinator.com'&gt;Hacker News&lt;/a&gt; is a good source.&lt;/li&gt;

&lt;li&gt;Read and contribute to open source projects in the space. If you go to Github and search for &lt;a href='https://github.com/languages'&gt;projects in your language&lt;/a&gt; you can see the top ones very easily.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='improve_your_practical_coding_ability'&gt;Improve your Practical Coding Ability&lt;/h2&gt;

&lt;p&gt;There are three ways we look at an engineer&amp;#8217;s practical coding ability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code examples from their contributions to open source and other projects (for example, on Github)&lt;/li&gt;

&lt;li&gt;On the spot coding questions on a whiteboard or remotely over &lt;a href='http://piratepad.net/front-page/'&gt;an Etherpad clone&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;A 2 hour long coding challenge&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The quality and speed of an engineer’s coding ability is important and can only come with practice.&lt;/p&gt;

&lt;p&gt;It is also helpful to use the appropriate language or framework for the job. Scripting languages like Python and Ruby can be quicker to program in than static languages like Java/C++ for many situations. Since we understand that an interviewee might not have had much experience in a dynamic language, we try to factor out its importance, but it still has influence.&lt;/p&gt;

&lt;p&gt;Doing coding challenges can also help hone your skills. You can find a number of resources online and can often get a benchmark on how fast you are so that you can further optimize your speed. &lt;a href='http://code.google.com/codejam/'&gt;Google Code Jam&lt;/a&gt; is a good source&lt;/p&gt;

&lt;h2 id='improve_your_relevant_tech_experience'&gt;Improve your Relevant Tech Experience&lt;/h2&gt;

&lt;p&gt;Having relevant experience is important in multiple ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It shows you have a passion for the technology and subject&lt;/li&gt;

&lt;li&gt;Your experience can be used to teach others at the company&lt;/li&gt;

&lt;li&gt;When you start you will hit the ground running.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can get this experience from your previous employer or from side projects.&lt;/p&gt;

&lt;p&gt;Side projects are really good signals for us. They show us:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You have a passion for technology and for building things&lt;/li&gt;

&lt;li&gt;You can generally explore more modern technologies and have a more well-rounded perspective&lt;/li&gt;

&lt;li&gt;You practice building things fast in a new space, which is exactly what you will be doing at a startup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These don’t have to be complex, massive projects to make an impact. Some types of side projects that are quick and show off your experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single purpose websites. e.g: downforeveryoneorjustme.com&lt;/li&gt;

&lt;li&gt;Simple iPhone app that solves a personal itch&lt;/li&gt;

&lt;li&gt;New open source libraries or contribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is fun to do a side project that explores some new technology/frameworks, like node.js, backbone, bootstrap etc. If a candidate comes in with a good repertoire of side projects and can talk intelligently about what they learnt while doing them , it is a great positive signal.&lt;/p&gt;

&lt;h2 id='conclusions'&gt;Conclusions&lt;/h2&gt;

&lt;p&gt;I am looking forward to talking to candidates who can ace all of these areas. Of course the technical side is just one half of the coin. I will explore the non-technical side in part 2.&lt;/p&gt;

&lt;p&gt;Heyzap is hiring great engineers. If you have some Android, iOS or Rails skills, even better. Get in touch with us at &lt;a href='mailto:jobs@heyzap.com'&gt;jobs@heyzap.com&lt;/a&gt; and check us out at &lt;a href='http://www.heyzap.com/about'&gt;heyzap.com/about&lt;/a&gt;.&lt;/p&gt;</description>
				<published>Wed Jul 25 00:00:00 +0000 2012</published>
				<link>http://stdout.heyzap.com/2012/07/25/how-to-ace-a-startup-engineering-interview-part-1/</link>
			</item>
		
			<item>
				<title>Continuous Cache Warming for Rails</title>
				<description>&lt;p&gt;&lt;strong&gt;Warning: what you are about to see will offend your sensibilities as an engineer. It should never be used in production or for user-facing or critical client purposes. It is terrible, no-good very-bad hackiness, and also kinda works. I just hope there&amp;#8217;s not some simple way to solve this that I totally missed. Now that you&amp;#8217;ve been warned, read on!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There&amp;#8217;s a problem with Rails page caching &amp;#8211; the page has to fully load once in somebody&amp;#8217;s browser before the cache kicks in. That means somebody&amp;#8217;s request is really slow, or worse, times out. Not so good. Wouldn&amp;#8217;t it be nice if something in the background could do all of a page&amp;#8217;s data operations, render the view, and then update the cache for that page, without users ever noticing? Well, check this out.&lt;/p&gt;

&lt;p&gt;You can render a page in irb via this method:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;  &lt;span class='n'&gt;app&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;https://www.mywebsite.com/slow/endpoint&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt; &lt;span class='kp'&gt;true&lt;/span&gt;
  &lt;span class='n'&gt;response&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;app&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;response&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt; &lt;span class='kp'&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I use &lt;code&gt;; true&lt;/code&gt; to prevent irb from dumping huge amounts of request / response data onto the console.&lt;/p&gt;

&lt;p&gt;Unfortunately, I couldn&amp;#8217;t find any way of doing this outside irb that wasn&amp;#8217;t very complicated. Our slow endpoint was on a back-end administrative page only; faking the session data in curl would have been annoying. Also, it was exceeding the timeout limits of our production server. So I had to add an ugly line in: &lt;code&gt;Admin::SlowController.set_timeout 60000&lt;/code&gt;, which has to be run in the rails environment. So, irb it is! Or script/console in this case.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the rundown. I set up a shell script &lt;code&gt;cache_generator.sh&lt;/code&gt; to run irb with a ruby file, like so:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;    &lt;span class='c'&gt;#!/bin/bash&lt;/span&gt;
    script/console production &amp;lt; worker/cache_page.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then set up a ruby script &lt;code&gt;cache_page.rb&lt;/code&gt; to do some crazy duck-typing to prepare our environment, render the page, and stuff it in Redis (our cache of choice):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;    &lt;span class='ss'&gt;Admin&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='ss'&gt;:SlowController&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;skip_before_filter&lt;/span&gt; &lt;span class='ss'&gt;:admin_required&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:only&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:long&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
    &lt;span class='ss'&gt;Admin&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='ss'&gt;:SlowController&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;set_timeout&lt;/span&gt; &lt;span class='mi'&gt;60000&lt;/span&gt;
    &lt;span class='n'&gt;app&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;https://www.heyzap.com/admin/slow/long?ignore_cache=true&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt; &lt;span class='kp'&gt;true&lt;/span&gt;
    &lt;span class='n'&gt;response&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;app&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;response&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt; &lt;span class='kp'&gt;true&lt;/span&gt;
    &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;hset&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;page_caches&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;long&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;response&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;body&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This ignores the required filters, runs as long as it needs to, renders the page and stores it. Now all that&amp;#8217;s left is to display it. In our controller method, I just added the following at the top:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;long&lt;/span&gt;
      &lt;span class='c1'&gt;# don&amp;#39;t use cache&lt;/span&gt;
      &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;page&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;REDIS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;hget&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;installs_cache&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;main&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt;&lt;span class='n'&gt;params&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:ignore_cache&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
        &lt;span class='n'&gt;render&lt;/span&gt; &lt;span class='ss'&gt;:inline&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;page&lt;/span&gt; &lt;span class='ow'&gt;and&lt;/span&gt; &lt;span class='k'&gt;return&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;
      &lt;span class='c1'&gt;# ...&lt;/span&gt;
      &lt;span class='n'&gt;render&lt;/span&gt; &lt;span class='ss'&gt;:action&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:long&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That &lt;code&gt;render :action =&amp;gt; :long&lt;/code&gt; at the end is important - that&amp;#8217;s necessary for the script/console code to work correctly. You might also want to skip the cache if there&amp;#8217;s a fancy query string or something - maybe &lt;code&gt;if params.count &amp;gt; 2&lt;/code&gt;, so if there are any crazy options the option-less cache won&amp;#8217;t be used.&lt;/p&gt;

&lt;p&gt;Last step, set up a cron job to run your script every now and then. I set ours for every five minutes, so we won&amp;#8217;t be too far off real data.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;    */5 * * * * &lt;span class='nb'&gt;cd&lt;/span&gt; /home/deploy/myapp/current &lt;span class='o'&gt;&amp;amp;&amp;amp;&lt;/span&gt; worker/cache_generator.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And boom! Your page now re-generates regularly, and is cached whenever users actually hit it. Wacky, hacky, and fun!&lt;/p&gt;</description>
				<published>Wed Mar 21 00:00:00 +0000 2012</published>
				<link>http://stdout.heyzap.com/2012/03/21/continuous-cache-warming-for-rails/</link>
			</item>
		
			<item>
				<title>How to Write a Self-Documenting API</title>
				<description>&lt;p&gt;Since the launch of Heyzap, we have been collecting a ton of interesting social data around mobile games on Android and iPhone. We think there is a lot of potential in using this data to discover interesting things around games and we wanted to expose that to the world through our API. Even though our mobile apps connect to our internal API, making a new API was not as easy as exposing the internal mobile API, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app and API requirements have been evolving iteratively. This has meant that it would not be intuitive if it were exposed and almost impossible to document.&lt;/li&gt;

&lt;li&gt;Some parts of the app hit API end points that would not scale if hit on mass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, when our internal hack day was approaching, I thought it was a perfect idea to try to launch an API and invest some time in learning how best to build it.&lt;/p&gt;

&lt;p&gt;You can check out the first version &lt;a href='http://www.heyzap.com/docs/api'&gt;API documentation&lt;/a&gt; and go to the &lt;a href='http://www.heyzap.com/api/v1/'&gt;root of the API&lt;/a&gt; to start exploring.&lt;/p&gt;

&lt;p&gt;Here is a cool usage of the API that Chris did for our hack day: &lt;a href='http://heyzap.com/live_checkins'&gt;Live Check-in map&lt;/a&gt;&lt;/p&gt;

&lt;h2 id='the_inspiration'&gt;The Inspiration&lt;/h2&gt;

&lt;p&gt;Heyzap has previously had several internally and externally facing APIs so I was aware of the mistakes I had made. Here are some that were foremost in my mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs are hit in ways that you don&amp;#8217;t expect and so every single end point needs to be scalable.&lt;/li&gt;

&lt;li&gt;It is easy to keep adding new parameters to API responses, which crowds the API.&lt;/li&gt;

&lt;li&gt;Getting the URLs right requires a lot of thought.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before starting the Heyzap API, I spent several hours researching blog posts and look at how other APIs were written. Here are the resources that I drew most inspiration from&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are a lot of opinionated blog posts on REST APIs. This is where I first got the idea for making the API as self-documenting as possible.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://developer.github.com/v3/'&gt;Github&amp;#8217;s API v3&lt;/a&gt;: I especially liked the way Github did short form and long form objects and rate limiting&lt;/li&gt;

&lt;li&gt;&lt;a href='https://dev.twitter.com/docs/api'&gt;Twitter&amp;#8217;s REST API&lt;/a&gt;: Being a social API, it had a lot of concepts we have in our API. I liked the way they managed to put all the docs on one page. We also borrowed some of the pagination paradigms they use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='considerations_for_selfdocumenting_apis'&gt;Considerations for Self-Documenting APIs&lt;/h2&gt;

&lt;p&gt;There are several aspects to making an API self-documenting. Here are things we considered&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URLs and REST resources have to be intuitive&lt;/li&gt;

&lt;li&gt;The API should cross-link with URL between different resources. This is used very aggressively in the API&lt;/li&gt;

&lt;li&gt;I feel that JSON is better for self-documenting than XML and that the world is moving to JSON so the API is JSON only&lt;/li&gt;

&lt;li&gt;The objects returned have to be self-documenting in terms of how the keys are labeled&lt;/li&gt;

&lt;li&gt;One can&amp;#8217;t hide too much functionally in the request or response Headers. This is at times a contentious issue in REST API designs and I explore it further below&lt;/li&gt;

&lt;li&gt;The different types of resources should use the same keys as much as possible. In our cases games and users had a reasonable amount of overlap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are discussed more in depth below.&lt;/p&gt;

&lt;h2 id='urls'&gt;URLs&lt;/h2&gt;

&lt;p&gt;A lot of thought was put in to the URL structure, and invested a reasonable amount of time in to the routes file before writing a line of code. Here is what we ended up with:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;  &lt;span class='n'&gt;map&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;namespace&lt;/span&gt; &lt;span class='ss'&gt;:api&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;api&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
    &lt;span class='n'&gt;api&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;namespace&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;v1&amp;quot;&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;version&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
      &lt;span class='c1'&gt;# root&lt;/span&gt;
      &lt;span class='n'&gt;version&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;connect&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:controller&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;base&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:action&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:index&lt;/span&gt;

      &lt;span class='n'&gt;version&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;resources&lt;/span&gt; &lt;span class='ss'&gt;:games&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:only&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:index&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:show&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:member&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='ss'&gt;:players&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='ss'&gt;:controller&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;games&amp;quot;&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;games&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
        &lt;span class='n'&gt;games&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;resources&lt;/span&gt; &lt;span class='ss'&gt;:ios&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:only&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:index&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:show&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:collection&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='ss'&gt;:search&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:trending&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:popular&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;
        &lt;span class='n'&gt;games&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;resources&lt;/span&gt; &lt;span class='ss'&gt;:android&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:only&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:index&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:show&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:collection&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='ss'&gt;:search&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:trending&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:popular&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;

        &lt;span class='n'&gt;games&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;resources&lt;/span&gt; &lt;span class='ss'&gt;:activity&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:only&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:index&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:collection&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='ss'&gt;:checkins&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:questions&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:tips&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='ss'&gt;:controller&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;games/activity&amp;quot;&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;

      &lt;span class='n'&gt;version&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;connect&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;users/:id&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:controller&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;users&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:action&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:show&lt;/span&gt;
      &lt;span class='n'&gt;version&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;resources&lt;/span&gt; &lt;span class='ss'&gt;:users&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:only&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:index&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:show&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:collection&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;search&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;  &lt;span class='ss'&gt;:member&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='ss'&gt;:badges&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:followers&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:following&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                                                           &lt;span class='ss'&gt;:games&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:boss_of&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='ss'&gt;:controller&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;users&amp;quot;&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;users&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
        &lt;span class='n'&gt;users&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;resources&lt;/span&gt; &lt;span class='ss'&gt;:activity&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:only&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:index&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:collection&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='ss'&gt;:checkins&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:questions&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:tips&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='ss'&gt;:controller&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;users/activity&amp;quot;&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;
      &lt;span class='n'&gt;version&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;resources&lt;/span&gt; &lt;span class='ss'&gt;:activity&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:only&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:index&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:show&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:member&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='ss'&gt;:checkins&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:questions&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:tips&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:badges&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:bossings&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:get&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='ss'&gt;:controller&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;activity&amp;quot;&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;At least 80% of this was written before a line of code was written.&lt;/p&gt;

&lt;h2 id='the_resource_roots_and_cross_linking'&gt;The Resource Roots and Cross Linking&lt;/h2&gt;

&lt;p&gt;Normally, when I&amp;#8217;m designing an API, I don&amp;#8217;t put anything in the root URL or in places where I don&amp;#8217;t expect to give data. But to make it sufficiently self-document, this API returns URL to relevant API end points wherever possible. This is the root response:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;activity_api_url&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;http://www.heyzap.com/api/v1/activity&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nx'&gt;games_api_url&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;http://www.heyzap.com/api/v1/games&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nx'&gt;users_api_url&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;http://www.heyzap.com/api/v1/users&amp;quot;&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And if you happen to click on the &lt;a href='http://www.heyzap.com/api/v1/games'&gt;games_api_url&lt;/a&gt; you get:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;platforms_allowed&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='nx'&gt;android_url&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;http://www.heyzap.com/api/v1/games/android&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='nx'&gt;ios_url&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;http://www.heyzap.com/api/v1/games/ios&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this way, without reading any documentation, you can explore most of the API. You of course need a JSON viewer plugin to do this.&lt;/p&gt;

&lt;p&gt;Additionally, whenever a different object is references in the API there is a API URL to it so you can explore in that way, too.&lt;/p&gt;

&lt;h2 id='the_short_and_long_form_objects'&gt;The Short and Long Form Objects&lt;/h2&gt;

&lt;p&gt;To keep the API readable, all end points that return an array of objects always returns the objects in a short form. It includes a URL always labeled &amp;#8220;url&amp;#8221; to the full object resource.&lt;/p&gt;

&lt;p&gt;This is a practice that Github follows and I really liked how it gave the information necessary at the array level and gave URLs to the full information. Twitter tended to just give an array of raw object IDs which was less self-documenting.&lt;/p&gt;

&lt;h2 id='use_of_request_and_response_headers'&gt;Use of Request and Response Headers&lt;/h2&gt;

&lt;p&gt;There are pros and cons to the use of request/response headers to convey a lot of information. Strictly speaking one could do all of the following with appropriate headers&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version: Through request ACCEPT headers&lt;/li&gt;

&lt;li&gt;Requesting Response Format: Through request ACCEPT headers&lt;/li&gt;

&lt;li&gt;Rate limiting: Through response headers&lt;/li&gt;

&lt;li&gt;Pagination: Through response headers&lt;/li&gt;

&lt;li&gt;Resources not found: Through response status codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although REST gives this ability, I think at times it can be not very readable and self-documenting. The two that I did do through headers were using status codes and rate limiting, but in both cases the response body also repeated the information.&lt;/p&gt;

&lt;h2 id='some_compromises'&gt;Some Compromises&lt;/h2&gt;

&lt;p&gt;To get this done in one day while still being scalable, I only exposed data that has been hit at scale on our mobile apps. This meant that, for example, Activity streams come in iOS or Android but you can&amp;#8217;t get a combined activity feed currently. Obviously the alternatives could have been done but it wasn&amp;#8217;t within the scope to make and test new ways of accessing the data.&lt;/p&gt;

&lt;p&gt;I am not fully happy with the way we handle pagination. Our mobile app uses infinite scrolling everywhere so it only really needs next_page_url. I would have preferred to encourage all people to skip through real pages with readable page numbers.&lt;/p&gt;

&lt;p&gt;Initially the API is read only. Setting up write capabilities end-to-end was again not within the scope of a day.&lt;/p&gt;

&lt;h2 id='conclusion'&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The API is just launching today so I am sure there will be things that we learn in real world usage that we didn&amp;#8217;t anticipate. We will probably move over our mobile apps to use this API or something very similar as soon as we add write capabilities.&lt;/p&gt;

&lt;p&gt;To start using the API read the &lt;a href='http://www.heyzap.com/docs/api'&gt;API documentation&lt;/a&gt; and go to the &lt;a href='http://www.heyzap.com/api/v1/'&gt;root of the API&lt;/a&gt; to start exploring.&lt;/p&gt;</description>
				<published>Wed Mar 07 00:00:00 +0000 2012</published>
				<link>http://stdout.heyzap.com/2012/03/07/how-to-write-a-self-documenting-api/</link>
			</item>
		
			<item>
				<title>Sunspot-Resque Session Proxy</title>
				<description>&lt;p&gt;We use &lt;a href='http://outoftime.github.com/sunspot'&gt;Sunspot&lt;/a&gt; and &lt;a href='http://lucene.apache.org/solr/'&gt;Solr&lt;/a&gt; for search and indexing. Solr is an Apache Foundation project that makes an easy standalone interface to the &lt;a href='http://lucene.apache.org/java/docs/index.html'&gt;Lucene&lt;/a&gt; search engine. Sunspot and its partner gem sunspot_rails provide an easy interface to the Solr server in Ruby, and hooks into ActiveRecord and the rails request lifecycle to update the search index in Solr automatically. For most setups, following the &lt;a href='https://github.com/outoftime/sunspot/wiki/Adding-Sunspot-search-to-Rails-in-5-minutes-or-less'&gt;add sunspot to your app in 5 minutes&lt;/a&gt; tutorial should work just fine.&lt;/p&gt;

&lt;p&gt;But when you&amp;#8217;re dealing with millions of records over many different types of data, and rather specific searching needs, the 5-minute setup doesn&amp;#8217;t quite cut it. At our rate of throughput, Sunspot was generating way too many commits to the search index, making it impossible to search. Also, if there was any kind of error in Sunspot, it would raise an exception that halted the current request. These two problems compounded each other and search basically unusable.&lt;/p&gt;

&lt;p&gt;To deal with the errors halting the request, we decided to delay tasks like indexing and committing until after the request. That way the commit errors would disappear, and we&amp;#8217;d only see web request errors on things that actually interfere with the web request - actual searches. After all, if you change your username, it isn&amp;#8217;t critical that it be searchable in real-time; waiting a few seconds or minutes in that case is fine. While the 3rd-party &lt;a href='https://github.com/bdurand/sunspot_index_queue'&gt;sunspot_index_queue&lt;/a&gt; library does this, it didn&amp;#8217;t integrate with our existing worker processes, which all run through &lt;a href='https://github.com/defunkt/resque'&gt;Resque&lt;/a&gt;. So we had to write an integration ourselves.&lt;/p&gt;

&lt;p&gt;sunspot_rails uses a &amp;#8220;session&amp;#8221; object to talk to Solr. The standard built-in session object makes an HTTP request to Solr for each request that it receives. We wanted a session object that only made a request for data needed &lt;em&gt;right now&lt;/em&gt; (i.e. actual searches) and shipped everything else to a Resque worker class. sunspot_rails provides an example session object in &lt;a href='https://github.com/outoftime/sunspot/blob/master/sunspot_rails/lib/sunspot/rails/stub_session_proxy.rb'&gt;stub_session_proxy&lt;/a&gt;, so we started from there and wired up the methods to do what we wanted. Here&amp;#8217;s how it looks right now:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;hoptoad_notifier&amp;#39;&lt;/span&gt;
&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='no'&gt;RAILS_ROOT&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;/solr/lib/sunspot_worker.rb&amp;quot;&lt;/span&gt;

&lt;span class='k'&gt;module&lt;/span&gt; &lt;span class='nn'&gt;Sunspot&lt;/span&gt;
  &lt;span class='k'&gt;module&lt;/span&gt; &lt;span class='nn'&gt;SessionProxy&lt;/span&gt;
    &lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;ResqueSessionProxy&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&lt;/span&gt; &lt;span class='no'&gt;AbstractSessionProxy&lt;/span&gt;
      &lt;span class='kp'&gt;attr_reader&lt;/span&gt; &lt;span class='ss'&gt;:search_session&lt;/span&gt;

      &lt;span class='n'&gt;delegate&lt;/span&gt; &lt;span class='ss'&gt;:new_search&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:search&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:config&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                &lt;span class='ss'&gt;:new_more_like_this&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:more_like_this&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                &lt;span class='ss'&gt;:delete_dirty&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:delete_dirty?&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:dirty?&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                &lt;span class='ss'&gt;:to&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:search_session&lt;/span&gt;

      &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;initialize&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;search_session&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Sunspot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='vi'&gt;@search_session&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;search_session&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;

      &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;rescued_exception&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;method&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;exception&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='no'&gt;HoptoadNotifier&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;notify&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;exception&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='vg'&gt;$stderr&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;puts&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Exception in SunspotSessionProxy&lt;/span&gt;&lt;span class='se'&gt;\#&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='nb'&gt;method&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;: &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;exception&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;message&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;

      &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:index!&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:index&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:remove!&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:remove&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='nb'&gt;method&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
        &lt;span class='nb'&gt;module_eval&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class='no'&gt;RUBY&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='sh'&gt;          def #{method}(*objects)&lt;/span&gt;
&lt;span class='sh'&gt;            missed_objects = []&lt;/span&gt;
&lt;span class='sh'&gt;            objects.each do |object|&lt;/span&gt;
&lt;span class='sh'&gt;              if(object.is_a? ActiveRecord::Base)&lt;/span&gt;
&lt;span class='sh'&gt;                Resque.enqueue SunspotWorker, :#{method}, {:class =&amp;gt; object.class.name, :id =&amp;gt; object.id }&lt;/span&gt;
&lt;span class='sh'&gt;              else&lt;/span&gt;
&lt;span class='sh'&gt;                missed_objects &amp;lt;&amp;lt; object&lt;/span&gt;
&lt;span class='sh'&gt;              end&lt;/span&gt;
&lt;span class='sh'&gt;            end&lt;/span&gt;
&lt;span class='sh'&gt;            begin&lt;/span&gt;
&lt;span class='sh'&gt;              @search_session.#{method}(missed_objects) unless missed_objects.empty?&lt;/span&gt;
&lt;span class='sh'&gt;            rescue =&amp;gt; e&lt;/span&gt;
&lt;span class='sh'&gt;              self.rescued_exception(:#{method}, e)&lt;/span&gt;
&lt;span class='sh'&gt;            end&lt;/span&gt;
&lt;span class='sh'&gt;          end&lt;/span&gt;
&lt;span class='no'&gt;        RUBY&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;

      &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:remove_by_id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:remove_by_id!&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='nb'&gt;method&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
        &lt;span class='nb'&gt;module_eval&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class='no'&gt;RUBY&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='sh'&gt;          def #{method}(clazz, id)&lt;/span&gt;
&lt;span class='sh'&gt;            Resque.enqueue SunspotWorker, :remove, {:class =&amp;gt; clazz, :id =&amp;gt; id}&lt;/span&gt;
&lt;span class='sh'&gt;          end&lt;/span&gt;
&lt;span class='no'&gt;        RUBY&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;

      &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;remove_all&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;clazz&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kp'&gt;nil&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='no'&gt;Resque&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;enqueue&lt;/span&gt; &lt;span class='no'&gt;SunspotWorker&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:remove_all&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;clazz&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;

      &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;remove_all!&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;clazz&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kp'&gt;nil&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='no'&gt;Resque&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;enqueue&lt;/span&gt; &lt;span class='no'&gt;SunspotWorker&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:remove_all&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;clazz&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;

      &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:commit_if_dirty&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:commit_if_delete_dirty&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:commit&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='nb'&gt;method&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
        &lt;span class='nb'&gt;module_eval&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class='no'&gt;RUBY&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='sh'&gt;          def #{method}&lt;/span&gt;
&lt;span class='sh'&gt;            Resque.enqueue(SunspotWorker, :commit) unless ::Rails.env == &amp;#39;production&amp;#39;&lt;/span&gt;
&lt;span class='sh'&gt;          end&lt;/span&gt;
&lt;span class='no'&gt;        RUBY&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We ship every recognized action to the Resque worker, and only send http requests for unrecognized actions. You&amp;#8217;ll notice we also catch exceptions in a begin / rescue block and send them to our exception catching service, which has since been renamed to &lt;a href='http://airbrakeapp.com/pages/home'&gt;Airbrake&lt;/a&gt;. Now all we needed was a Resque worker that would handle the input and do the actual processing. Here&amp;#8217;s what that looks like:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='ruby'&gt;&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;resque-retry&amp;#39;&lt;/span&gt;

&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;SunspotWorker&lt;/span&gt;
  &lt;span class='kp'&gt;extend&lt;/span&gt; &lt;span class='ss'&gt;Resque&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='ss'&gt;:Plugins&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;ExponentialBackoff&lt;/span&gt;
  &lt;span class='vi'&gt;@queue&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='ss'&gt;:solr_index&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;perform&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sunspot_method&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;object&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kp'&gt;nil&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;sunspot_method&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;sunspot_method&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_sym&lt;/span&gt;
    &lt;span class='n'&gt;object&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;with_indifferent_access&lt;/span&gt; &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;is_a?&lt;/span&gt; &lt;span class='no'&gt;Hash&lt;/span&gt;

    &lt;span class='n'&gt;session&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Sunspot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;
    &lt;span class='no'&gt;Sunspot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='ss'&gt;Sunspot&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='ss'&gt;:Rails&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;build_session&lt;/span&gt;
    &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='n'&gt;sunspot_method&lt;/span&gt;
    &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='ss'&gt;:index&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;index&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:class&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;constantize&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='ss'&gt;:remove&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;remove_by_id&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:class&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='ss'&gt;:remove_all&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;remove_all&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='ss'&gt;:commit&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;commit&lt;/span&gt;
    &lt;span class='k'&gt;else&lt;/span&gt;
      &lt;span class='k'&gt;raise&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Error: undefined protocol for SunspotWorker: &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;sunspot_method&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt; (&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;objects&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;)&amp;quot;&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
    &lt;span class='no'&gt;Sunspot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;session&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;index&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='no'&gt;Sunspot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;index&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;object&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;remove_by_id&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;id&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='no'&gt;Sunspot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;remove_by_id&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;id&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;remove_all&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;klass&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kp'&gt;nil&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;klass&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;constantize&lt;/span&gt; &lt;span class='k'&gt;unless&lt;/span&gt; &lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;nil?&lt;/span&gt;
    &lt;span class='no'&gt;Sunspot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;remove_all&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;commit&lt;/span&gt;
    &lt;span class='c1'&gt;# on production, use autocommit in solrconfig.xml &lt;/span&gt;
    &lt;span class='c1'&gt;# or commitWithin whenever sunspot supports it&lt;/span&gt;
    &lt;span class='no'&gt;Sunspot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;commit&lt;/span&gt; &lt;span class='k'&gt;unless&lt;/span&gt; &lt;span class='no'&gt;Rails&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;env&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;production&amp;#39;&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Since our default Rails environment sets the sunspot session to our proxy-queuing object, we have to manually change the connection to a standard session object that actually talks to Solr. We do that using the sunspot_rails build_session method, and then put it back when we&amp;#8217;re done.&lt;/p&gt;

&lt;p&gt;You&amp;#8217;ll also notice we don&amp;#8217;t allow commits, either in the proxy or in the worker in our production environment. This handily takes care of the &amp;#8220;too many commits&amp;#8221; issue, which usually reports itself as &amp;#8220;MaxWarmingSearchers Exceeded&amp;#8221;. We use Solr&amp;#8217;s &lt;a href='http://wiki.apache.org/solr/SolrConfigXml#Update_Handler_Section'&gt;autocommit&lt;/a&gt;, so our app never has to worry about committing in general. While it would be nicer to use Solr&amp;#8217;s new and shiny &lt;a href='http://wiki.apache.org/solr/UpdateXmlMessages#Optional_attributes_for_.22add.22'&gt;commitWithin&lt;/a&gt;, Sunspot doesn&amp;#8217;t support it, and autocommit was sufficient for our purposes.&lt;/p&gt;

&lt;p&gt;That&amp;#8217;s our code for the day - we&amp;#8217;re debating whether to package this up as a Sunspot plugin,&lt;/p&gt;</description>
				<published>Wed Aug 17 00:00:00 +0000 2011</published>
				<link>http://stdout.heyzap.com/2011/08/17/sunspot-resque-session-proxy/</link>
			</item>
		
	</channel>
</rss>