RailsTips by John Nunemaker http://railstips.org/blog/ 2020-12-17T08:52:09-05:00 Flipper Preloading 584981a8edb2f305ce30df2c 2016-12-08T12:26:56-05:00 2016-12-08T11:00:00-05:00 <p>In which I describe a new optimization for flipper.</p> <p><a href="https://github.com/jnunemaker/flipper">Flipper</a> is already pretty optimized for production usage (flipper does billions of feature checks a day at GitHub), but the latest release (0.10.2) just received a couple new ones &mdash; all thanks to community contributions.</p> <p>In <a href="https://github.com/jnunemaker/flipper/issues/190">jnunemaker/flipper#190</a>, <a href="https://github.com/mscoutermarsh">@mscoutermarsh</a> said:</p> <blockquote> <p>Hi,<br /> <br>Would love if there&#8217;s a way to preload enabled features for an actor. For Product Hunt, we check several features in a single request for current_user. With activerecord this adds up to quite a few queries. Would love to get it down to one.<br /> <br>From browsing source I don&#8217;t believe this is currently available.</p> </blockquote> <p>I suggested using caching, as we do at GitHub, but also <a href="https://github.com/jnunemaker/flipper/issues/190#issuecomment-260132126">put some thoughts down</a> on how preloading features could work. <a href="https://github.com/gshutler">@gshutler</a> saw the conversation and put pen to paper on a <a href="https://github.com/jnunemaker/flipper/pull/198">great pull request</a> that made the idea concrete.</p> <h2>Some Background</h2> <p>Often times users of flipper do many feature checks per request. Normally, this would require a network call for each feature check, but flipper comes with a <a href="https://github.com/jnunemaker/flipper/blob/dfdaa8265fe8ffc29666493663678d1fbc5bab87/lib/flipper/middleware/memoizer.rb">Memoizer middleware</a> that stores the gate values for a feature in memory for the duration of a request. This makes it so the first feature check for a feature performs a network call, but subsequent ones just do in memory <code>Hash</code> fetches (aka dramatic hamster faster).</p> <h2><span class="caps">DSL</span>#preload and Adapter#get_multi</h2> <p>The addition by gshutler was an adapter method <code>get_multi</code>, which takes an array of features and allows the adapter to load all features in one network call. He then added <code>DSL#preload</code>, along with a <code>:preload</code> option for the <code>Memoizer</code> middleware, which use <code>get_multi</code> to load any provided features in one network call instead of N.</p> <p>By default, the Adapter implementation of <code>get_multi</code> performs one <code>get</code> per feature. Each adapter can then override this functionality with a more efficient means of fetching the data. For example, the active record adapter uses an <code>IN</code> query to select all gate values for all features.</p> <pre class="ruby"><code class="ruby">def get_multi(features) db_gates = @gate_class.where(feature_key: features.map(&amp;:key)) grouped_db_gates = db_gates.group_by { |gate| gate.feature_key } result = {} features.each do |feature| result[feature.key] = result_for_feature(feature, grouped_db_gates[feature.key]) end result end</code></pre> <p>gshutler provided the redis implementation of <code>get_multi</code> and then I, as the maintainer of flipper, added <code>get_multi</code> for the other core adapters, along with updates to the <a href="https://github.com/jnunemaker/flipper/blob/dfdaa8265fe8ffc29666493663678d1fbc5bab87/docs/Adapters.md">Adapters</a> and <a href="https://github.com/jnunemaker/flipper/blob/dfdaa8265fe8ffc29666493663678d1fbc5bab87/docs/Optimization.md">Optimizations</a> documentation.</p> <h2>The Result</h2> <p>mscoutermarsh was kind enough to <a href="https://github.com/jnunemaker/flipper/issues/206#issuecomment-265628778">drop a graph of the result</a> for an endpoint of <a href="https://www.producthunt.com/">Product Hunt</a>.</p> <p><img src="/assets/5849875eedb2f305ce30e3a5/article_full/flipper_preload.png" alt="" /></p> <p>I will leave it up to the reader to determine when he deployed the preloading. :) Looks like ~50% improvement on that endpoint.</p> <p>I feel like this is a great example of the power of open source and how important it is to efficiently load data for requests, so I thought I would take the time to write it up. Hope you enjoyed it. Happy bulk loading!</p> John Nunemaker Flipping ActiveRecord 5679fb67a0b5dd535b15d753 2016-07-05T09:50:40-04:00 2015-12-22T21:05:00-05:00 <p>In which I release an official ActiveRecord adapter for Flipper.</p> <p>Originally, I did not like the idea of an ActiveRecord adapter for <a href="http://www.railstips.org/blog/archives/2015/08/03/flipper-insanely-easy-feature-flipping/">Flipper</a>. I work on <a href="https://github.com">GitHub.com</a> day to day, so everything I do has to be extremely performant. Using ActiveRecord for something like this felt like way too much overhead.</p> <p>In fact, at GitHub, we use a custom adapter for Flipper built on good old raw <span class="caps">SQL</span>. Not only that, but we also use a memcache adapter which wraps the pure <span class="caps">SQL</span> adapter to avoid hitting MySQL most of the time. The memcache wrapper (at the time of this writing) works similar to the memoizing adapter that is included with Flipper (for those that are curious).</p> <p>Over time, a few good options came out for using Flipper with ActiveRecord and they changed my mind. I realized that not every application is GitHub.com. Some applications value ease of integration over performance. I even wrote my own ActiveRecord adapter for <a href="https://speakerdeck.com">SpeakerDeck</a>, which is what I am now including in the core <a href="https://github.com/jnunemaker/flipper">flipper repo</a> (but available as a separate gem).</p> <h2>Installation</h2> <p>Drop the gem in your Gemfile:</p> <pre><code>gem "flipper-active_record"</code></pre> <p>Generate the migration:</p> <pre><code>rails g flipper:active_record</code></pre> <h2>Usage</h2> <pre class="ruby"><code class="ruby">require 'flipper/adapters/active_record' adapter = Flipper::Adapters::ActiveRecord.new flipper = Flipper.new(adapter) # profit...</code></pre> <p>From there, you use flipper the same as you would with any of the previously supported adapters. Internally, all features are stored in a <code>flipper_features</code> table and all gate related values are stored in a <code>flipper_gates</code> table. You can see more about the <a href="https://github.com/jnunemaker/flipper/blob/e4b43c55dac3243aef79c8c9600ad9f15e3f2d95/examples/active_record/internals.rb">internals in the examples</a>.</p> <h2>Conclusion</h2> <p>As of Flipper 0.7.3, you can now flip features with the easy and comfort of ActiveRecord and the peace of mind that as new AR versions are released, your flipper adapter will be updated and ready to go.</p> <p>Happy flipping and happy holidays!</p> John Nunemaker Flipper: Insanely Easy Feature Flipping 55bf5b8ad4c96106840498fa 2015-12-22T20:51:27-05:00 2015-08-03T08:00:00-04:00 <p>In which I ramble about turning features on and off in a really easy way.</p> <p>Cross posted from <a href="http://johnnunemaker.com/flipper/">JohnNunemaker.com</a> as it seems relevant here too.</p> <pre><code> __ _.-~ ) _..--~~~~,' ,-/ _ .-'. . . .' ,-',' ,' ) ,'. . . _ ,--~,-'__..-' ,' ,'. . . (@)' ---~~~~ ,' /. . . . '~~ ,-' /. . . . . ,-' ; . . . . - . ,' : . . . . _ / . . . . . `-.: . . . ./ - . ) . . . | _____..---.._/ _____ ~---~~~~----~~~~ ~~ </code></pre> <p>Nearly <a href="https://github.com/jnunemaker/flipper/commit/8257cc68a9a2ff6fb6b3ae6c497b15309c4d0d7b">three years ago</a>, I started work on Flipper. Even though there were other feature flipping libraries out there at the time, most notably <a href="https://github.com/FetLife/rollout">rollout</a>, I decided to whip up my own. <a href="https://speakerdeck.com/jnunemaker/dont-repeat-yourself-repeat-others">Repeating others</a> is, after all, one of the better ways to level up your game.</p> <p>My main issue with rollout was that it was inflexible. You couldn&#8217;t change the ways in which a feature was enabled (ie: adding percentage of time rollout). You had to use redis. The list goes on. I poked around and couldn&#8217;t find anything like what I was looking for and I was in the mood to create, so I started flipper.</p> <p>Most of the work was done off and on over the course of a few weeks. At the time, I was working on traffic graphs for GitHub and I wanted a way to turn features on/off in a flexible way.</p> <h2 id="naming-is-hard">Naming is hard</h2> <p>Flipper started as a simple ripoff of rollout with the primary difference being the use of adapters for storage instead of forcing redis. I struggled through awkward terminology and messy code for a while, until a great conversation with <a href="http://opensoul.org">Brandon Keepers</a> led me to the lingo flipper uses today: Actor, Feature and Gate (thanks Brandon!)</p> <p>An <strong>actor</strong> is the thing trying to do something. It can be anything. On <a href="https://github.com">GitHub</a>, the actor can be a user, organization or even a repository. Actors must respond to <code>flipper_id</code>. If you plan on using multiple types of actors, you can namespace the flipper_id with the type (ie: &#8220;User:6&#8221;, &#8220;Organization:12&#8221;, or &#8220;Repository: 2&#8221;).</p> <p>A <strong>feature</strong> is something that you want to control enabled-ness for. On <a href="https://speakerdeck.com">SpeakerDeck</a>, I have a feature for search. With the click of a button, I can disable search if it is causing issues. On GitHub, we do thousands of feature checks per second across nearly 30 features (at the time of this writing) in different states of enabled-ness. If I told you what they were for I would have to kill you.</p> <p>A <strong>gate</strong> determines if a feature is enabled for an actor. There are currently five gates &#8212; boolean, actor, group, % of actors and % of time. Amongst these you can rollout a new feature or control an existing one in whatever way you desire.</p> <h3 id="the-gates">The Gates</h3> <p>The <strong>boolean gate</strong> allows completely enabling or disabling a feature. Think of it as a short cut to turning a feature fully on or fully off quickly. Enabling the boolean gate means the feature is on all the time for everyone. Disabling the boolean gate clears all enabled gates so the feature is completely off. Think of disable like a reset.</p> <pre class="ruby"><code class="ruby">flipper = Flipper.new(adapter) flipper[:search].enable # turn on flipper[:search].disable # turn off flipper[:search].enabled? # check</code></pre> <p>The <strong>actor gate</strong> allows enabling a feature for one or more specific actors. If you wanted to enable a new feature for one of your friends, you could use this gate.</p> <pre><code class="ruby"> flipper = Flipper.new(adapter) flipper[:search].enable_actor user # turn on for actor flipper[:search].enabled? user # true flipper[:search].disable_actor user # turn off for actor flipper[:search].enabled? user # false </code></pre> <p>The <strong>group gate</strong> allows enabling a feature for one or more groups. A group is a named block of code that returns true or false for a given actor. You could have a group for everyone in your company, or only engineering, or perhaps all users in the US or Europe. Anything your heart can imagine can be converted to a group and the entire group can be enabled at once.</p> <pre><code class="ruby"> Flipper.register(:admins) do |actor| actor.respond_to?(:admin?) &amp;amp;&amp;amp; actor.admin? end flipper = Flipper.new(adapter) flipper[:search].enable_group :admins # turn on for admins flipper[:search].disable_group :admins # turn off for admins person = Person.find(params[:id]) flipper[:search].enabled? person # check if enabled, returns true if person.admin? is true </code></pre> <p>The <strong>percentage of actors gate</strong> allows slowly enabling a feature for a percentage of actors. As long as you continue to increase the percentage, an actor will consistently remain enabled. This allows for careful rollouts of a feature to everyone without overwhelming the system as a whole.</p> <pre><code class="ruby"> flipper = Flipper.new(adapter) # turn search on for 10 percent of users in the system flipper[:search].enable_percentage_of_actors 10 # checks if actor's flipper_id is in the enabled percentage by hashing # user.flipper_id.to_s to ensure enabled distribution is smooth flipper[:search].enabled? user # turn search off for percentage of actors, other gates could retur true still flipper[:search].disable_percentage_of_actors # sets to 0 </code></pre> <p>The <strong>percentage of time gate</strong> allows enabling a feature for a random percentage of time. This is great for dark shipping and load testing. We actually used somehing similar to this to launch traffic graphs. We wanted to be positive that we could stand up to real traffic, so we performed ajax requests behind the scenes based on a percentage of time to the new feature. This allowed us to crank up the traffic, hit a bottleneck, kill the traffic, fix the bottlneck and repeat.</p> <pre><code class="ruby"> flipper = Flipper.new(adapter) # turn on logging for 5 percent of the time # could be on during one request and off the next # could even be on first time in request and off second time flipper[:logging].enable_percentage_of_time 5 # turn off logging for percentage of time flipper[:logging].disable_percentage_of_time # sets to 0 </code></pre> <p>All <a href="https://github.com/jnunemaker/flipper/blob/master/docs/Gates.md">the gates are fully documented in the flipper repo</a> as well.</p> <h2 id="adapters">Adapters</h2> <p>The adapter pattern is used to store which gates gates are enabled for a given feature. This means you can store flipper&#8217;s information however you desire. At the time of this writing, <a href="https://github.com/jnunemaker/flipper/blob/master/docs/Adapters.md">several adapters already exist</a>, such as in memory, pstore, mongo, redis, cassandra, and active record. If one of those doesn&#8217;t tickle your fancy, creating a new adapter is really easy. The <span class="caps">API</span> for an adapter is this:</p> <ul> <li><code>features</code> &#8211; Get the set of known features.</li> <li><code>add(feature)</code> &#8211; Add a feature to the set of known features.</li> <li><code>remove(feature)</code> &#8211; Remove a feature from the set of known features.</li> <li><code>clear(feature)</code> &#8211; Clear all gate values for a feature.</li> <li><code>get(feature)</code> &#8211; Get all gate values for a feature.</li> <li><code>enable(feature, gate, thing)</code> &#8211; Enable a gate for a thing.</li> <li><code>disable(feature, gate, thing)</code> &#8211; Disable a gate for a thing.</li> </ul> <p>At GitHub, we actually use a <span class="caps">SQL</span> adapter fronted by memcache for performance reasons.</p> <h2 id="instrumentation">Instrumentation</h2> <p>Flipper is wired to be <a href="https://github.com/jnunemaker/flipper/blob/master/docs/Instrumentation.md">instrumented out of the box</a>, using ActiveSupport::Notifications <span class="caps">API</span> (though AS::Notifs are not specifically required). I even included automatic statsd instrumentation for those that are already using statsd.</p> <pre class="ruby"><code class="ruby">require "flipper/instrumentation/statsd" statsd = Statsd.new # or whatever your statsd instance is Flipper::Instrumentation::StatsdSubscriber.client = statsd</code></pre> <p>If statsd doesn&#8217;t work for you, <a href="https://github.com/jnunemaker/flipper/blob/master/examples/instrumentation.rb">you can easily customize</a> wherever you want to instrument to (ie: InfluxDB, New Relic, etc.).</p> <h2 id="performance">Performance</h2> <p>Flipper was built based on my time working on Words with Friends and to be used at GitHub, so you can rest easy that it was built with performance in mind. The adapter <span class="caps">API</span> is intentionally made to allow for fetching all gate values for a feature in one network call and there is even (optional) built in memoization of adapter calls, <a href="https://github.com/jnunemaker/flipper/blob/master/docs/Optimization.md">including a Rack middleware</a> which enables memoizing the fetching a feature for the duration of a request.</p> <p>I&#8217;ve also thought about making it easy to allow for batch loading of features, though I haven&#8217;t needed this yet on any site I&#8217;ve worked on, so for now it remains a thought rather than an implementation.</p> <h2 id="web-ui">Web UI</h2> <p>As a cherry on top, I&#8217;ve also created a <a href="https://github.com/jnunemaker/flipper/tree/master/docs/ui">rack middleware web UI</a> for controlling flipper, which can be protected by any authentication you need. Below are a couple screenshots (at the time of this writing).</p> <h3 id="list-of-features">List of features</h3> <p><img src="/assets/55bf5bbbd4c9610665045cd0/article_full/features.png" alt="" /></p> <h3 id="viewing-individual-feature">Viewing individual feature</h3> <p><img src="/assets/55bf5bbbedb2f361bb042aa8/article_full/feature.png" alt="" /></p> <p>All the gates can be manipulated to enable features however you would like through the click of a button or the clack of a keyboard.</p> <h2 id="conclusion">Conclusion</h2> <p>Flipper is ready for the prime time. As I said earlier, we are now using it on GitHub.com for thousands of feature checks every second. The <span class="caps">API</span> changed a bit in 0.7, but is pretty stable now. Drop it in your next project and give it a try. If you do, please let me know (email or issue on the repo) as I love to know how people are using things I&#8217;ve worked on.</p> John Nunemaker Of Late 530b9aabf002ff02ea0001c1 2014-02-24T14:18:22-05:00 2014-02-24T14:00:00-05:00 <p>In which I link to a new place where I&#8217;ll be writing.</p> <p>A lot has changed over the years. I now do a lot more than just rails and having railstips as my domain seems to mentally put me in a corner.</p> <p>As such, I have revived <a href="http://johnnunemaker.com">johnnunemaker.com</a>. While I may still post a rails topic here once in a while, I&#8217;ll be posting a lot more varied topics over there.</p> <p>In fact, I just published my first post of any length, titled <a href="http://johnnunemaker.com/analytics-at-github/">Analytics at GitHub</a>. Head on over and give it a read.</p> John Nunemaker Let Nunes Do It 5170477f7a5072364c0026b6 2013-04-18T16:00:51-04:00 2013-04-18T15:20:00-04:00 <p>In which I release Nunes to a soon-to-be-more-instrumented world.</p> <p>In a moment of either genius or delirium I decided to name my newest project after myself. Why? Well, here is the story whether you want to know or not.</p> <h2>Why Nunes?</h2> <p>Naming is always the hardest part of a project. Originally, it was named Railsd. The idea of the gem is automatically subscribe to all of the valuable Rails instrumentation events and send them to statsd in a sane way, thus Railsd was born.</p> <p>After working on it a bit, I realized that the project was just an easy way to send Rails instrumentation events to any service that supports counters and timers. With a few tweaks, I made Railsd support <a href="http://instrumentalapp.com">InstrumentalApp</a>, a favorite service of mine, in addition to Statsd.</p> <p>Thus came the dilemma. No longer did the (already terrible) name Railsd make sense. As I sat and thought about what to name it, I remembered joking one time about naming a project after myself, so that every time anyone used it they had no choice but to think about me. <strong>Thus <a href="https://github.com/jnunemaker/nunes">Nunes</a> was born</strong>.</p> <p>Lest you think that I just wanted to name it Nunes only so that you think of me, here is a bit more detail. Personally, I attempt to instrument everything I can. Be it code, the steps I take, or the calories I consume, I want to know what is going on. I have also noticed that which is automatically instrumented is the easiest to instrument.</p> <p><strong>I love tracking data so deeply that I want to instrument your code. Really, I do</strong>. I want to clone your repo, inject a whole bunch of instrumentation and deploy it to production, so you can know exactly what is going on. I want to sit over your shoulder and look at the graphs with you. Ooooooh, aren&#8217;t those some pretty graphs!</p> <p><strong>But I don&#8217;t work for you, or with you, so that would be weird</strong>.</p> <p>Instead, I give you Nunes. I give you Nunes as a reminder that I want to instrument everything and you should too. I give you Nunes so that instrumenting is so easy that you will feel foolish not using it, at least a start. Go ahead, the first metric is free! Yep, I want you to have that first hit and get addicted, like me.</p> <h2>Using Nunes</h2> <p>I love instrumenting things. Nunes loves instrumenting things. To get started, just add Nunes to your gemfile:</p> <pre><code class="ruby"># be sure to think of me when you do :) gem "nunes"</code></pre> <p>Once you have nunes in your bundle (be sure to think of bundling me up with a big hug), you just need to tell nunes to subscribe to all the fancy events and provide him with somewhere to send all the glorious metrics:</p> <pre><code class="ruby"># yep, think of me here too require 'nunes' # for statsd statsd = Statsd.new(...) Nunes.subscribe(statsd) # ooh, ooh, think of me! # for instrumental I = Instrument::Agent.new(...) Nunes.subscribe(I) # one moooore tiiiime!</code></pre> <p>With just those couple of lines, you get a whole lot of goodness. Out of the box, Nunes will subscribe to the following Rails instrumentation events:</p> <ul> <li><code>process_action.action_controller</code></li> <li><code>render_template.action_view</code></li> <li><code>render_partial.action_view</code></li> <li><code>deliver.action_mailer</code></li> <li><code>receive.action_mailer</code></li> <li><code>sql.active_record</code></li> <li><code>cache_read.active_support</code></li> <li><code>cache_generate.active_support</code></li> <li><code>cache_fetch_hit.active_support</code></li> <li><code>cache_write.active_support</code></li> <li><code>cache_delete.active_support</code></li> <li><code>cache_exist?.active_support</code></li> </ul> <p>Thanks to all the wonderful information those events provide, you will instantly get some of these counter metrics:</p> <ul> <li><code>action_controller.status.200</code></li> <li><code>action_controller.format.html</code></li> <li><code>action_controller.exception.RuntimeError</code> &#8211; where RuntimeError is the class of any exceptions that occur while processing a controller&#8217;s action.</li> <li><code>active_support.cache_hit</code></li> <li><code>active_support.cache_miss</code></li> </ul> <p>And these timer metrics:</p> <ul> <li><code>action_controller.runtime</code></li> <li><code>action_controller.view_runtime</code></li> <li><code>action_controller.db_runtime</code></li> <li><code>action_controller.posts.index.runtime</code> &#8211; where <code>posts</code> is the controller and <code>index</code> is the action</li> <li><code>action_view.app.views.posts.index.html.erb</code> &#8211; where <code>app.views.posts.index.html.erb</code> is the path of the view file</li> <li><code>action_view.app.views.posts._post.html.erb</code> &#8211; I can even do partials! woot woot!</li> <li><code>action_mailer.deliver.post_mailer</code> &#8211; where <code>post_mailer</code> is the name of the mailer</li> <li><code>action_mailer.receive.post_mailer</code> &#8211; where <code>post_mailer</code> is the name of the mailer</li> <li><code>active_record.sql</code></li> <li><code>active_record.sql.select</code> &#8211; also supported are insert, update, delete, transaction_begin and transaction_commit</li> <li><code>active_support.cache_read</code></li> <li><code>active_support.cache_generate</code></li> <li><code>active_support.cache_fetch</code></li> <li><code>active_support.cache_fetch_hit</code></li> <li><code>active_support.cache_write</code></li> <li><code>active_support.cache_delete</code></li> <li><code>active_support.cache_exist</code></li> </ul> <h2>But Wait, There is More!</h2> <p>In addition to doing all that work for you out of the box, Nunes will also help you wrap your own code with instrumentation. I know, I know, sounds too good to be true.</p> <pre><code class="ruby"> class User &lt; ActiveRecord::Base extend Nunes::Instrumentable # OH HAI IT IS ME, NUNES # wrap save and instrument the timing of it instrument_method_time :save end </code></pre> <p>This will instrument the timing of the User instance method save. What that means is when you do this:</p> <pre><code class="ruby"># the nerve of me to name a user nunes user = User.new(name: "NUNES!") user.save </code></pre> <p>An event named <code>instrument_method_time.nunes</code> will be generated, which in turn is subscribed to and sent to whatever you used to send instrumentation to (statsd, instrumental, etc.). The metric name will default to &#8220;class.method&#8221;. For the example above, the metric name would be <code>user.save</code>. No fear, you can customize this.</p> <pre><code class="ruby">class User &lt; ActiveRecord::Base extend Nunes::Instrumentable # never # wrap save and instrument the timing of it instrument_method_time :save, 'crazy_town.save' end </code></pre> <p>Passing a string as the second argument sets the name of the metric. You can also customize the name using a Hash as the second argument.</p> <pre><code class="ruby">class User &lt; ActiveRecord::Base extend Nunes::Instrumentable # gonna # wrap save and instrument the timing of it instrument_method_time :save, name: 'crazy_town.save' end </code></pre> <p>In addition to name, you can also pass a payload that will get sent along with the generated event.</p> <pre><code class="ruby"> class User &lt; ActiveRecord::Base extend Nunes::Instrumentable # give nunes up # wrap save and instrument the timing of it instrument_method_time :save, payload: {pay: "loading"} end </code></pre> <p>If you subscribe to the event on your own, say to log some things, you&#8217;ll get a key named <code>:pay</code> with a value of <code>"loading"</code> in the event&#8217;s payload. Pretty neat, eh?</p> <h2>Conclusion</h2> <p>I hope you find Nunes useful and that each time you use it, you think of me and how much I want to instrument your code for you, but am not able to. Go forth and instrument!</p> <p>P.S. If you have ideas for Nunes, create an issue and start some chatter. Let&#8217;s make Nunes even better!</p> John Nunemaker An Instrumented Library in ~30 Lines 510006ed7a507277eb000ac0 2013-01-23T15:35:26-05:00 2013-01-23T15:00:00-05:00 <p>lmao if you don&#8217;t make it easy for users of your library to log, measure and graph everything.</p> <h2>The Full ~30 Lines</h2> <p>For the first time ever, I am going to lead with the end of the story. Here is the full ~30 lines that I will break down in detail during the rest of this post.</p> <pre><code class="ruby">require 'forwardable' module Foo module Instrumenters class Noop def self.instrument(name, payload = {}) yield payload if block_given? end end end class Client extend Forwardable def_delegator :@instrumenter, :instrument def initialize(options = {}) # some other setup for the client ... @instrumenter = options[:instrumenter] || Instrumenters::Noop end def execute(args = {}) instrument('client_execute.foo', args: args) { |payload| result = # do some work... payload[:result] = result result } end end end client = Foo::Client.new({ instrumenter: ActiveSupport::Notifications, }) client.execute(...) # I AM INSTRUMENTED!!!</code></pre> <h2>The Dark Side</h2> <p>A while back, <a href="http://www.railstips.org/blog/archives/2011/03/21/hi-my-name-is-john/">statsd grabbed a hold of the universe</a>. It swept in like an elf on a unicorn and we all started keeping track of stuff that previously was a pain to keep track of.</p> <p>Like any wave of awesomeness, it came with a dark side that was felt, but mostly overlooked. Dark side? Statsd? Graphite? You must be crazy! Nope, not me, definitely not crazy this one. Not. At. All.</p> <p>What did we all start doing in order to inject our measuring? Yep, <strong>we started opening up classes in horrible ways</strong> and creating hooks into libraries that sometimes change rapidly. Many times, updating a library would cause a break in the stats reporting and require effort to update the hooks.</p> <h2>The Ideal</h2> <p>Now that the wild west is settling a bit, I think some have started to reflect on that wave of awesomeness and realized something.</p> <blockquote> <p>I no longer want to inject my own instrumentation into your library. Instead, I want to tell your library where it should send the instrumentation.</p> </blockquote> <p>The great thing is that <a href="http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html">ActiveSupport::Notifications</a> is pretty spiffy in this regard. By simply allowing your library to talk to an &#8220;instrumenter&#8221; that responds to <code>instrument</code> with an event name, optional payload, and optional block, you can make all your library&#8217;s users <strong>really</strong> happy.</p> <p>The great part is:</p> <ol> <li>You do not have to <strong>force your users to use active support</strong>. They simply need some kind of instrumenter that responds in similar fashion.</li> <li>They <strong>no longer have to monkey patch</strong> to get metrics.</li> <li>You can <strong>point them in the right direction as to what is valuable to instrument</strong> in your library, since really you know it best.</li> </ol> <p>There are a few good examples of libraries (faraday, excon, etc.) doing this, but I haven&#8217;t seen a great post yet, so here is my attempt to point you in what I feel is the right direction.</p> <h2>The Interface</h2> <p>First, like I said above, we do not want to force requiring active support. Rather than require a library, <strong>it is always better to require an interface</strong>.</p> <p>The interface that we will require is the one used by active support, but an adapter interface could be created for any instrumenter that we want to support. Here is what it looks like:</p> <pre><code class="ruby">instrumenter.instrument(name, payload) { |payload| # do some code here that should be instrumented # we expect payload to be yielded so that additional # payload entries can be included during the # computation inside the block }</code></pre> <p>Second, we have two options.</p> <ol> <li>Either have an instrumenter or not. If so, then call <code>instrument</code> on the instrumenter. If not, then do not call <code>instrument</code>.</li> <li>The option, which I prefer, is to <strong>have a default instrumenter that does nothing</strong>. Aptly, I call this the noop instrumenter.</li> </ol> <h2>The Implementation</h2> <p>Let&#8217;s pretend our library is named foo, therefore it will be namespaced with the module Foo. I typically namespace the instrumenters in a module as well. Knowing this, our noop instrumenter would look like this:</p> <pre><code class="ruby">module Foo module Instrumenters class Noop def self.instrument(name, payload = {}) yield payload if block_given? end end end end</code></pre> <p>As you can see, all this instrumenter does is yield the payload if a block is given. As I mentioned before, <strong>we yield payload so that the computation inside the block can add entries to the payload</strong>, such as the result.</p> <p>Now that we have a default instrumenter, how can we use it? Well, let&#8217;s imagine that we have a Client class in foo that is the main entry point for the gem.</p> <pre><code class="ruby">module Foo class Client def initialize(options = {}) # some other setup for the client ... @instrumenter = options[:instrumenter] || Instrumenters::Noop end end end</code></pre> <p>This code simply allows people to pass in the instrumenter that they would like to use through the initialization options. Also, by default if no instrumenter is provided, we use are noop version that just yields the block and moves on.</p> <p>Note: the use of || instead of #fetch is intentional. It prevents a nil instrumenter from being passed in. There are other ways around this, but I have found using the noop instrumenter in place of nil, better than complaining about nil.</p> <p>Now that we have an <code>:instrumenter</code> option, someone can quite easily pass in the instrumenter that they would like to use.</p> <pre><code class="ruby">client = Foo::Client.new({ :instrumenter =&gt; ActiveSupport::Notifications, })</code></pre> <p>Boom! Just like that we&#8217;ve allowed people to inject active support notifications, or whatever instrumenter they want into our library. Anyone else getting excited?</p> <p>Once we have that, we can start instrumenting the valuable parts. Typically what I do is I setup delegation of the <code>instrument</code> to the instrumenter using ruby&#8217;s forwardable library:</p> <pre><code class="ruby">require 'forwardable' module Foo class Client extend Forwardable # forward instrument in this class to @instrumenter, for those unfamilier # with forwardable. def_delegator :@instrumenter, :instrument def initialize(options = {}) # some other setup for the client ... @instrumenter = options[:instrumenter] || Instrumenters::Noop end end end</code></pre> <p>Now we can use the <code>instrument</code> method directly anywhere in our client instance. For example, let&#8217;s say that client has a method named <code>execute</code> that we would like to instrument.</p> <pre><code class="ruby">module Foo class Client def execute(args = {}) instrument('client_execute.foo', args: args) { |payload| result = # do some work... payload[:result] = result result } end end end</code></pre> <p>With just a tiny wrap of the instrument method, the users of our library can do a ridiculous amount of instrumentation. For one, note that we pass the args and the result along with the payload. This means our users can create a log subscriber and log each method call with timing, argument, and result information. Incredibly valuable!</p> <p>They can also create a metrics subscriber that sends the timing information to <a href="http://instrumentalapp.com">instrumental</a>, <a href="https://github.com/eric/metriks">metriks</a>, statsd, or whatever.</p> <h2>The Bonus</h2> <p>You can even provide log subscribers and metric subscribers in your library, which means instrumentation for your users is simply a require away. For example, here is the <a href="https://github.com/jnunemaker/cassanity/blob/master/lib/cassanity/instrumentation/log_subscriber.rb">log subscriber</a> I added to <a href="https://github.com/jnunemaker/cassanity">cassanity</a>.</p> <pre><code class="ruby">require 'securerandom' require 'active_support/notifications' require 'active_support/log_subscriber' module Cassanity module Instrumentation class LogSubscriber &lt; ::ActiveSupport::LogSubscriber def cql(event) return unless logger.debug? name = '%s (%.1fms)' % ["CQL Query", event.duration] # execute arguments are always an array where the first element is the # cql string and the rest are the bound variables. cql, *args = event.payload[:execute_arguments] arguments = args.map { |arg| arg.inspect }.join(', ') query = "#{cql}" query += " (#{arguments})" unless arguments.empty? debug " #{color(name, CYAN, true)} [ #{query} ]" end end end end Cassanity::Instrumentation::LogSubscriber.attach_to :cassanity</code></pre> <p>All the users of cassanity need to do to get logging of the <span class="caps">CQL</span> queries they are performing and their timing is require a file (and have activesupport in their gemfile):</p> <pre><code class="ruby">require 'cassanity/instrumentation/log_subscriber'</code></pre> <p>And they get logging goodness like this in their terminal:</p> <p><img src="/assets/51003fd97a507223410006a6/article_full/cassanity_instrumentation.png" class="full image" alt="" /></p> <h2>The Accuracy</h2> <p>But! <span class="caps">BUT</span>, you say. What about the tests? Well, my friend, I have that all wrapped up for you as well. Since it is so easy to pass through an instrumenter to our library, we should probably also have an in memory instrumenter that keeps track of the events instrumented, so you can test thoroughly, and ensure you don&#8217;t hose your users with incorrect instrumentation.</p> <p>The previous sentence was quite a mouthful, so my next one will be short and sweet. For testing, I created an in-memory instrumenter that simply stores each instrumented event with name, payload, and the computed block result for later comparison. Check it:</p> <pre><code class="ruby">module Foo module Instrumenters class Memory Event = Struct.new(:name, :payload, :result) attr_reader :events def initialize @events = [] end def instrument(name, payload = {}) result = if block_given? yield payload else nil end @events &lt;&lt; Event.new(name, payload, result) result end end end end</code></pre> <p>Now in your tests, you can do something like this when you want to check that your library is correctly instrumenting:</p> <pre><code class="ruby">instrumenter = Foo::Instrumenters::Memory.new client = Foo::Client.new({ instrumenter: instrumenter, }) client.execute(...) payload = {... something .. } event = instrumenter.events.last assert_not_nil event assert_equal 'client_execute.foo', event.name assert_equal payload, event.payload </code></pre> <h2>The End Result</h2> <p>With two instrumenters (noop, memory) and a belief in interfaces, we have created immense value.</p> <p><img src="/assets/510046327a50722361001157/article_full/freakin_sweet.jpg" class="full image" alt="" /></p> <h2>Further Reading</h2> <p>Without any further ado, here are a few of the articles and decks that I read recently related to this.</p> <ul> <li><a href="http://railscasts.com/episodes/249-notifications-in-rails-3">RailsCasts: Notifications in Rails 3</a></li> <li><a href="https://speakerdeck.com/nextmat/digging-deep-with-activesupportnotifications">Digging Deep with ActiveSupport Notifications</a></li> <li><a href="https://speakerdeck.com/tenderlove/code-charcuterie">Code Charcuterie</a></li> <li><a href="https://gist.github.com/566725">Instrument Anything in Rails 3</a></li> <li><a href="http://www.paperplanes.de/2012/3/14/on-notifications-logsubscribers-and-bringing-sanity-to-rails-logging.html">On Notifications, Log Subscribers and Bringing Sanity to Rails Logs</a></li> </ul> <h2>Fin</h2> <p>Go forth and instrument all the things!</p> John Nunemaker Booleans are Baaaaaaaaaad 50759b74dabe9d400600aa8a 2017-12-11T17:03:37-05:00 2012-10-10T13:10:00-04:00 <p>In which I encourage the use of state machines because they rock.</p> <p>First off, did you pronounce the title of this article like a sheep? That was definitely the intent. Anyway, onward to the purpose of this here text.</p> <p>One of the things I have learned the hard way is that booleans are bad. Just to be clear, I do not mean that true/false is bad, but rather that using true/false for state is bad. Rather than rant, lets look at a concrete example.</p> <h2>An Example</h2> <p>The first example that comes to mind is the ever present user model. On signup, most apps force you to confirm your email address.</p> <p>To do this there might be a temptation to add a boolean, lets say &#8220;active&#8221;. Active defaults to false and upon confirmation of the email is changed to true. This means your app needs to make sure you are always dealing with active users. Cool. Problem solved.</p> <p>It might look something like this:</p> <pre><code class="ruby">class User include MongoMapper::Document scope :active, where(:active =&gt; true) key :active, Boolean end</code></pre> <p>To prevent inactive users from using the app, you add a before filter that checks if the current_user is inactive. If they are, you redirect them to a page asking them to confirm there email or resend the email confirmation. Life is grand!</p> <h2>The Requirements Change</h2> <p>Then, out of nowhere comes an abusive user, let&#8217;s name him John. John is a real jerk. He starts harassing your other users by leaving mean comments about their moms.</p> <p>In order to combat John, you add another boolean, lets say &#8220;abusive&#8221;, which defaults to false. You then add code to allow marking a user as abusive. Doing so sets &#8220;abusive&#8221; to true. You then add code that disallows users who have abusive set to true from adding comments.</p> <h2>The Problem</h2> <p>You now have split state. Should an abusive user really be active? Then a new idea pops into your head. When a user is marked as abusive, lets also set active to false, so they just can&#8217;t use the system. Oh, and when a user is marked as active, let&#8217;s make sure that abusive is set to false. Problem solved? Right? <span class="caps">RIGHT</span>? Wrong.</p> <p><strong>You are now maintaining one state with two switches</strong>. As requirements change, you end up with more and more situations like this and weird edge cases start to sneak in.</p> <h2>The Solution</h2> <p>How can we improve the situation? Two words: state machine. State machines are awesome. Lets rework our user model to use the <a href="https://github.com/pluginaweek/state_machine">state_machine</a> gem.</p> <pre><code class="ruby">class User include MongoMapper::Document key :state, String state_machine :state, :initial =&gt; :inactive do state :inactive state :active state :abusive event :activate do transition all =&gt; :active end event :mark_abusive do transition all =&gt; :abusive end end end</code></pre> <p>With just the code above, we can now do all of this:</p> <pre><code class="ruby">user = User.create user.active? # false because initial is set to inactive user.activate! user.active? # true because we activated user.mark_abusive! user.active? # false user.inactive? # false user.abusive? # true User.with_state(:active) # scope to return active User.with_state(:inactive) # another scope User.with_state(:abusive) # driving the example home</code></pre> <p>Pretty cool, eh? You get a lot of bang for the buck. I am just showing the beginning of what you can do, head on over to the <a href="https://github.com/pluginaweek/state_machine">readme</a> to see more. You can add guards and all kinds of neat things. Problem solved. Right? <span class="caps">RIGHT</span>? Wrong.</p> <h3>Requirements Change Again</h3> <p>Uh oh! Requirements just changed again. Mr. <span class="caps">CEO</span> decided that instead of calling people abusive, we want to refer to them as &#8220;spammy&#8221;.</p> <p>The app has been wildly successful and you now have millions of users. You have two options:</p> <ol> <li>Leave the code as it is and just change the language in the views. This sucks because then you are constantly translating between the two.</li> <li>Put up the maintenance page and accept downtime, since you have to push out new code and migrate the data. This sucks, because your app is down, simply because you did not think ahead.</li> </ol> <h3>A Better State Machine</h3> <p>Good news. With just a few tweaks, you could have built in the flexibility to handle changing your code without needing to change your data. The state machine gem supports changing the value that is stored in the database.</p> <p>Instead of hardcoding strings in your database, use integers. Integers allow you to change terminology willy nilly in your app and only change app code. Let&#8217;s take a look at how it could work:</p> <pre><code class="ruby">class User include MongoMapper::Document States = { :inactive =&gt; 0, :active =&gt; 1, :abusive =&gt; 2, } key :state, Integer state_machine :state, :initial =&gt; :inactive do # create states based on our States constant States.each do |name, value| state name, :value =&gt; value end event :activate do transition all =&gt; :active end event :mark_abusive do transition all =&gt; :abusive end end end</code></pre> <p>With just that slight change, we now are storing state as an integer in our database. This means changing from &#8220;abusive&#8221; to &#8220;spammy&#8221; is just a code change like this:</p> <pre><code class="ruby">class User include MongoMapper::Document States = { :inactive =&gt; 0, :active =&gt; 1, :spammy =&gt; 2, } key :state, Integer state_machine :state, :initial =&gt; :inactive do States.each do |name, value| state name, :value =&gt; value end event :activate do transition all =&gt; :active end event :mark_spammy do transition all =&gt; :spammy end end end</code></pre> <p>Update the language in the views, deploy your changes and you are good to go. <strong>No downtime. No data migration. Copious amounts of flexibility for little to no more work.</strong></p> <p>Next time you reach for a boolean in your database, think again. Please! Whip out the state machine gem and wow your friends with your wisdom and foresight.</p> John Nunemaker Four Guidelines That I Feel Have Improved My Code 4ff5abc2dabe9d4a7201446c 2012-07-05T15:51:28-04:00 2012-07-05T15:00:00-04:00 <p>In which I share some tips based on recent trial and error.</p> <p>I have been thinking a lot about isolation, dependencies and clean code of late. I know there is a lot of disagreement with people vehemently standing in both camps.</p> <p>I certainly will not say either side is right or wrong, but what follows is what I feel has improved my code. I post it here to formalize some recent thoughts and, if I am lucky, get some good feedback.</p> <p>Before I rush into the gory details, I feel I should mention that I went down this path, not as an architecture astronout, but out of genuine pain in what I was working on.</p> <p>My models were growing large. My tests were getting slow. Things did not feel &#8220;right&#8221;.</p> <p>I started watching Gary Bernhardt&#8217;s <a href="http://destroyallsoftware.com">Destroy All Software</a> screencasts. He is a big proponent of testing in isolation. Definitely go get a subscription and take a day to get caught up.</p> <p>On top of <span class="caps">DAS</span>, I started reading everything I could on the subject of growing software, clean code and refactoring. When I say reading, I really should say devouring.</p> <p>I was literally prowling about like a lion, looking for the next book I could devour. Several times my wife asked me to get off my hands and knees and to kindly stop roaring about <span class="caps">SRP</span>.</p> <p>Over the past few months as I have tried to write better code, I have definitely learned a lot. <strong>Learning without reflection and writing is not true learning for me</strong>.</p> <p>Reflecting on why something feels better and then writing about it <strong>formalizes it in my head</strong> and has the added benefit of being available for anyone else who is struggling with the same.</p> <p>Here are a few guidelines that have jumped out at me over the past few days as I reflected on what I have been practicing the past few months.</p> <h2>Guideline #1. One responsibility to rule them all</h2> <p>Single responsibility principle (<span class="caps">SRP</span>) is really hard. I think a lot of us are frustrated and feeling the pain of our chubby &lt;insert your favorite <span class="caps">ORM</span>&gt; classes. Something does not feel right. Working on them is hard.</p> <p>The problem is context. You have to load a lot of context in your brain when you crack open that <strong><span class="caps">INFAMOUS</span></strong> user model. That context takes up the space where we would normally create and come up with new solutions.</p> <h3>Create More Classes</h3> <p>So what are we to do? <strong>Create more classes</strong>. Your models do not need to inherit from ActiveRecord::Base, or include MongoMapper::Document, or whatever.</p> <p>A model is something that has business logic. Start breaking up your huge models that have persistence bolted on into plain old Ruby classes.</p> <p>I am not going to lie to you. If you have not been doing this, <strong>it will not be easy</strong>. Everything will seem like it should just be tucked as another method in a model that also happens to persist data in a store.</p> <h3>Naming is Hard</h3> <p>Another pain point will be naming. Naming is fracking hard. You are welcome for the <span class="caps">BSG</span> reference there. I would like to take that statement a step further though.</p> <p><strong>Naming is hard because our classes and methods are doing too much</strong>. The fewer responsibilities your class has, the easier it will be to name, especially after a few months of practice.</p> <h3>An Example</h3> <p>Enough talk, lets see some code. In our track processors, which pop tracks off a queue and store reports in a database, we query for the gauge being tracked before storing reports. The purpose of this query is to ensure that the gauge is in good standing and that we should, in fact, store reports in the database for it.</p> <p>A lot of people throw the tracking code on their site and never remove it or sign up for a paying account. We do this find to make sure those people noop, instead of creating tons of data that no one is paying for.</p> <p>This query happens for each track and it is pulling information that rarely if ever changes. It seemed like a prime spot for a wee bit of caching.</p> <p>First, I created a tiny service around the memcached client I decided to use. This only took an hour and it means that my application now has an interface for caching (<code>get</code>, <code>set</code>, <code>delete</code>, and <code>fetch</code>). I&#8217;ll talk more about this in guideline #3.</p> <p>Once I had defined the interface Gauges would use for caching, I began to integrate it. After much battling and rewriting of the caching code, each piece felt like it was doing too much and things were getting messy.</p> <p>I stepped back and thought through my plans. I wanted to cache only the attributes, so I threw everything away and started with that. First, I wanted to be able to read attributes from the data store.</p> <pre><code class="ruby">class GaugeAttributeService def get(id) criteria = {:_id =&gt; Plucky.to_object_id(id)} if (attrs = gauge_collection.find_one(criteria)) attrs.delete('_id') attrs end end end</code></pre> <p>Given an id, this class returns a hash of attributes. That is pretty much one responsibility. Sweet action. Let&#8217;s move on.</p> <p>Second, I knew that I wanted to add read-through caching for this. Typically read-through caching uses some sort of fetch pattern. Fetch is basically a shortcut for look first in the cache and if it is not there, compute the block, store the computed result in the cache and return the computed result.</p> <p>If I would have added caching in the <code>GaugeAttributeService</code> class, I would have violated <span class="caps">SRP</span>. Describing the class would have been &quot;checks the cache and if not there it fetches from database&quot;. Note the use of &quot;and&quot;.</p> <p>As <a href="http://www.amazon.com/dp/B002TIOYVW/">Growing Object Oriented Software</a> states:</p> <blockquote> <p>Our heuristic is that we should be able to describe what an object does without using any conjunctions (“and,” “or”).</p> </blockquote> <p>Instead, I created a new class to wrap (or decorate) my original service.</p> <pre><code class="ruby">class GaugeAttributeServiceWithCaching def initialize(attribute_service = GaugeAttributeService.new) @attribute_service = attribute_service end def get(id) cache_service.fetch(cache_key(id)) { @attribute_service.get(id) } end end</code></pre> <p>I left a few bits out of this class so we can focus on the important part, which is that all we do with this class is wrap the original one with a cache fetch.</p> <p>As you can see, naming is pretty easy for this class. It is a gauge attribute service with caching and is named as such. It initializes with an object that must respond to <code>get</code>. Note also that it defaults to an instance of <code>GaugeAttributeService</code>.</p> <p>Unit testing this class is easy as well. We can isolate the dependencies (<code>attribute_service</code> and <code>cache_service</code>) in the unit test and make sure that they do what we expect (<code>fetch</code> and <code>get</code>).</p> <p><small><strong>Note</strong>: There definitely could a point made that &quot;with&quot; is the same as &quot;and&quot; and therefore means that we are breaking <span class="caps">SRP</span>. Naming is hard, really hard. Rather than get mired forever in naming, I rolled with this convention and, at this point, it does not bother me. I am definitely open to suggestions. Another name I played with was CachedGaugeAttributeService.</small></p> <p>Below is an example setup with new dependencies inject in the test that help us verify this classes behavior in isolation.</p> <pre><code class="ruby">attributes = {'title' =&gt; 'GitHub'} attribute_service = Class.new do def get(id) attributes end end.new cache_service = Class.new do def fetch(key) get(key) || yield end def get(key) end end.new service = GaugeAttributeServiceWithCaching.new(attribute_service) service.cache_service = cache_service </code></pre> <p>Above I used dynamic classes. Instead of dynamic classes, one could use stubbing or whatever. I&#8217;ll talk more about <code>cache_service=</code> later.</p> <p>Decorating in this manner means we can easily find without caching by using GaugeAttributeService or with caching by using GaugeAttributeServiceWithCaching.</p> <p>The important thing to note is that we added new functionality to our application by extending existing parts instead of changing them. I read recently, but cannot find the quote, that if you can add a new feature purely by extending existing classes and creating new classes, you are winning.</p> <h2>Guideline #2. Use accessors for collaborators</h2> <p>In the example above, you probably noticed that when testing <code>GaugeAttributeServiceWithCaching</code>, I changed the cache service used by assigning a new one. What I often see is others using some top level config, or even worse they actually use a <code>$</code> global.</p> <pre><code class="ruby"># bad Gauges.cache = Memcached.new class GaugeAttributeServiceWithCaching def get(id) Gauges.cache.fetch(cache_key(id)) { … } end end # worse $cache = Memcached.new class GaugeAttributeServiceWithCaching def get(id) $cache.fetch(cache_key(id)) { … } end end</code></pre> <p>What sucks about this is you are coupling this class to a global and coupling leads to pain. Instead, what I have started doing is using accessors to setup collaborators. Here is the example from above, but now with the cache service accessors included.</p> <pre><code class="ruby"> class GaugeAttributeServiceWithCaching attr_writer :cache_service def cache_service @cache_service ||= CacheService.new end end</code></pre> <p>By doing this, we get a sane, memoized default for our cache service (<code>CacheService.new</code>) and the ability to change that default (<code>cache_service=</code>), either in our application or when unit testing.</p> <p>Finding ourselves doing this quite often, we created a library, aptly named <a href="https://github.com/bkeepers/morphine">Morphine</a>. Right now it does little more than what I just showed (memoized default and writer method to change).</p> <p>As I have started to use this gem, I am getting more ideas for things that would be helpful. Here is the same code as above, but using Morphine. What I like about it, over a memoized method and an attr&#95;writer is that it feels a little more declarative and creates a standard way of declaring collaborators for classes.</p> <pre><code class="ruby"> class GaugeAttributeServiceWithCaching include Morphine register :cache_service do CacheService.new end end</code></pre> <p>Note also that I am not passing these dependencies in through initialize. At first I started with that and it looked something like this:</p> <pre><code class="ruby">class GaugeAttributeServiceWithCaching def initialize(attribute_service = GaugeAttributeService.new, cache_service = CacheService.new) @attribute_service = attribute_service @cache_service = cache_service end end</code></pre> <p>Personally, over time I found this method tedious. My general guideline is <strong>pass a dependency through initialize when you are going to decorate it, otherwise use accessors</strong>. Let&#8217;s look at the attribute service with caching again.</p> <pre><code class="ruby">class GaugeAttributeServiceWithCaching include Morphine register :cache_service do CacheService.new end def initialize(attribute_service = GaugeAttributeService.new) @attribute_service = attribute_service end end </code></pre> <p>Since this class is decorating an attribute service with caching, I pass in the service we want to decorate through initialize. I do not, however, pass in the cache service through initialize. Instead, the cache service uses Morphine (or accessors).</p> <p>First, I think this <strong>makes the intent more obvious</strong>. The intent of this class is to wrap another object, so that object should be provided to initialize. Defaulting the service to wrap is merely a convenience.</p> <p>Second, the cache service is a dependency, but not one that is being wrapped. It purely <strong>needs a sane default and a way to be replaced</strong>, therefore it uses Morphine (or accessors).</p> <p>I cannot say this is a hard and fast rule that everyone should follow and that you are wrong if you do not. I can say that through trial and error, <strong>following this guideline has led to the least amount of friction</strong> while maintaining flexibility and isolation.</p> <h2>Guideline #3. Create real interfaces</h2> <p>As I mentioned above, the first thing I started with when working on the caching code was an interface for caching for the application, rather than just using a client directly. Occasionally what I see people do is create an interface, but wholesale pass arguments through to a client like so:</p> <pre><code class="ruby"># bad idea class CacheService def initialize(driver) @driver = driver end def get(*args) @driver.get(*args) end def set(*args) @driver.set(*args) end def delete(*args) @driver.delete(*args) end end </code></pre> <p>In my opinion, this is abstracting at the wrong level. All you are doing is adding a layer of indirection on top of a driver. It makes it harder to follow and any exceptions that the driver raises will be raised in your application. Also, any parameters that the driver works with, your interface will work with. There is no point in doing this.</p> <p>Instead, create a real interface. Define the methods and parameters you want your application to be able to use and make that work with whatever driver you end up choosing or changing to down the road.</p> <h3>Handling Exceptions</h3> <p>First, I created the exceptions that would be raised if anything goes wrong.</p> <pre><code class="ruby">class CacheService class Error &lt; StandardError attr_reader :original def initialize(original = $!) if original.nil? super else super(original.message) end @original = original end end class NotFound &lt; Error; end class NotStored &lt; Error; end end</code></pre> <p>CacheService::Error is the base that all other errors inherit from. It wraps whatever the original error was, instead of discarding it, and defaults to the last exception that was raised <code>$!</code>. I will show how these are used in a bit.</p> <h3>Portability and serialization</h3> <p>I knew that I wanted the cache to be portable, so instead of just defaulting to Marshal&#8217;ing, I used only raw operations and ensured that I wrapped all raw operations with serialize and deserialize, where appropriate.</p> <p>In order to allow this cache service class to work with multiple serialization methods, I registered a serializer dependency, instead of just using MultiJson&#8217;s <code>dump</code> and <code>load</code> directly. I then wrapped convenience methods (<code>serialize</code> and <code>deserialize</code>) that handle a few oddities induced by the driver I am wrapping.</p> <pre><code class="ruby">class CacheService include Morphine register :serializer do Serializers::Json.new end private def serialize(value) serializer.serialize(value) end def deserialize(value) if value.is_a?(Hash) # get with multiple keys value.each { |k, v| value[k] = deserialize(v) } value else serializer.deserialize(value) end end end</code></pre> <h3>Handling exceptions (continued)</h3> <p>I then created a few private methods that hit the driver and wrap exceptions. These private methods are what the public methods use to ensure that exceptions are properly handled and such.</p> <pre><code class="ruby">class CacheService private def driver_read(keys) deserialize(@driver.get(keys, false)) rescue Memcached::NotFound raise NotFound rescue Memcached::Error raise Error end def driver_write(method, key, value) @driver.send method, key, serialize(value), DefaultTTL.call, false rescue Memcached::NotStored raise NotStored rescue Memcached::Error raise Error end def driver_delete(key) @driver.delete(key) rescue Memcached::NotFound raise NotFound end end</code></pre> <p>At this point, no driver specific exceptions should ever bubble outside of the cache service. When using the cache service in the application, I need only worry about handling the cache service exceptions and not the specific driver exceptions.</p> <p><strong>If I change to a different driver, only this class changes</strong>. The rest of my application stays the same. Big win. How many times have you upgraded a gem and then had to update pieces all over your application because they willy-nilly changed their interface.</p> <h3>The public interface</h3> <p>All that is left is to define the public methods and parameters that can be used in the application.</p> <pre><code class="ruby">class CacheService def get(keys) driver_read(keys) rescue NotFound nil end def set(key, value) driver_write :set, key, value end def delete(key) driver_delete key rescue NotFound nil end end</code></pre> <p>At this point, the application has a defined interface that it can work with for caching and for the most part does not need to worry about exceptions as they are wrapped and, in some cases, even handled (ie: nil for NotFound).</p> <p>Creating real interfaces ensures that expectations are set and upgrades are easy. Defined interfaces give other developers on the project confidence that if they follow the rules, things will work as expected.</p> <h2>Guideline #4. Test the whole way through</h2> <p>Whatever you want to call them, you need tests that prove all your components are wired together and working as expected, in the same manor as they will be used in production.</p> <p>The reason a lot of developers have felt pain with pure unit testing and isolation is because they forget to add that secondary layer of tests on top that ensure that the way things are wired together works too.</p> <p>Unit tests are there to drive our design. Acceptance tests are there to make sure that things are actually working the whole way through. Each of these are essential and not to be skipped over.</p> <p>If you are having problems testing, it may be your design. If you are getting burned by isolation, you are probably missing higher level tests. You should be able to kill your unit tests and still have reasonable confidence that your system is working.</p> <p>Nowadays, I often start with a high level test and then work my way in unit testing the pieces as I make them. I&#8217;ve found this keeps me focused on the value I am adding and ensures that my coverage is good.</p> <h2>Conclusion</h2> <p>While it has definitely taken a lot of trial and error, I am starting to find the right balance between flexibility, isolation and overkill.</p> <ol> <li>Stick to single responsibilities.</li> <li>Inject decorated dependencies through initialization and use accessors for other dependencies.</li> <li>Create real interfaces.</li> <li>Test in isolation <strong>and</strong> the whole way through.</li> </ol> <p>Follow these guidelines and I believe you will start to feel better about the code you are writing, as I have over the past few months.</p> <p>I would love to hear what others of you are doing and see examples. Comment below with gists, github urls, and other thoughts. Thanks!</p> John Nunemaker Misleading Title About Queueing 4f54f0ecdabe9d1bb400cf11 2012-03-05T13:37:12-05:00 2012-03-05T11:00:00-05:00 <p>In which I discuss queueing Gauges track requests in Kestrel.</p> <p>I don&#8217;t know about you, but I find it super frustrating when people blog about cool stuff at the beginning of a project, but then as it grows, <strong>they either don&#8217;t take the time to teach or they get all protective about what they are doing</strong>.</p> <p>I am going to do my best to <strong>continue to discuss the strategies we are using</strong> to grow <a href="http://gaug.es">Gauges</a>. I hope you find them useful and, by all means, if you have tips or ideas, hit me. Without any further ado&#8230;</p> <p>March 1st of last year (2011), we <a href="http://orderedlist.com/blog/articles/gauges/">launched Gauges</a>. March 1st of this year (a few days ago), we finally switched to a queue for track requests. Yes, for one full year, we did all report generation in the track request.</p> <h2>1. In the Beginning</h2> <p>My goal for Gauges in the beginning was realtime. I wanted data to be so freakin&#8217; up-to-date that it blew people&#8217;s minds. What I&#8217;ve realized over the past year of talking to customers is that sometimes Gauges is so realtime, it is too realtime.</p> <p>That is definitely not to say that we are going to work on slowing Gauges down. More what it means, is that my priorities are shifting. As more and more websites use Gauges to track, availability moves more and more to the front of my mind.</p> <h3>Gut Detects Issue</h3> <p>A few weeks back, with much help from friends (<a href="http://twitter.com/#!/bkeepers">Brandon Keepers</a>, <a href="http://twitter.com/#!/jnewland">Jesse Newland</a>, <a href="http://twitter.com/#!/hwaet">Kyle Banker</a>, <a href="http://twitter.com/#!/lindvall">Eric Lindvall</a>, and the top notch dudes at <a href="http://twitter.com/#!/fastestforward">Fastest Forward</a>), I started digging into some performance issues that were getting increasingly worse. They weren&#8217;t bad yet, but I had this gut feeling they would be soon.</p> <p>My gut was right. Our disk io utilization on our primary database doubled from January to February, which was also our biggest growth in terms of number of track requests. If we doubled again from February to March, <strong>it was not going to be pretty</strong>.</p> <h3>Back to the Beginning</h3> <p>From the beginning, Gauges built all tracking reports on the fly in the track request. When a track came in, Gauges did a few queries and then performed around 5-10 updates.</p> <p>When you are small, this is fine, but as growth happens, updating live during a track request can become an issue. I had no way to throttle traffic to the database. This meant if we had enough large sites start tracking at once, most likely our primary database would say uncle.</p> <p>As you can guess, if your primary says uncle, you start losing tracking data. In my mind, <strong>priority number one is now to never lose tracking data</strong>. In order to do this effectively, I felt we were finally at the point where we needed to separate tracking from reporting.</p> <h2>2. Availability Takes Front Seat</h2> <p>My goal is for tracking to never be down. If, occasionally, you can&#8217;t get to your reporting data, or if, occasionally, your data gets behind for a few minutes, I will survive. If, however, tracking requests start getting tossed to the wayside while the primary screams for help, I will not.</p> <p>I talked with some friends and found Kestrel to be very highly recommended, particularly by Eric (linked above). He swore by it, and was pushing it harder than we needed to, so I decided to give it a try.</p> <p>A few hours later, my lacking <span class="caps">JVM</span> skills (Kestrel is Scala) were bearing their head big time. I still had not figured out how to build or run the darn thing. I posted to the mailing list, where someone quickly pointed out that Kestrel defaults to /var for logging, data, etc. and, unfortunately, spits out no error on startup about lacking permissions on <span class="caps">OSX</span>. One sudo !! later and I was in business.</p> <h2>3. Kestrel</h2> <p>Before I get too far a long with this fairy tail, let&#8217;s talk about Kestrel &#8212; what is it and why did I pick it?</p> <p><a href="https://github.com/robey/kestrel">Kestrel</a> is a simple, distributed message queue, based on Blaine Cook&#8217;s starling. Here are a few great paragraphs from the readme:</p> <blockquote> <p>Each server handles a set of reliable, ordered message queues. When you put a cluster of these servers together, with no cross communication, and pick a server at random whenever you do a set or get, you end up with a reliable, loosely ordered message queue.</p> </blockquote> <blockquote> <p>In many situations, loose ordering is sufficient. Dropping the requirement on cross communication makes it horizontally scale to infinity and beyond: no multicast, no clustering, no &#8220;elections&#8221;, no coordination at all. No talking! Shhh!</p> </blockquote> <p>It features the memcached protocol, is durable (journaled), has fanout queues, item expiration, and even supports transactional reads.</p> <p>My favorite thing about Kestrel? <strong>It is simple, soooo simple</strong>. Sound too good to be true? Probably is, but the honeymoon has been great so far.</p> <p>Now that we&#8217;ve covered what Kestrel is and that it is amazing, let&#8217;s talk about how I rolled it out.</p> <h2>4. Architecture</h2> <p>Here is the general idea. The app writes track requests to the tracking service. Workers process off those track requests and generate the reports in the primary database.</p> <p>After the primary database writes, we send the information through a pusher proxy process, which sends it off to <a href="http://pusher.com">pusher.com</a>, the service that provides all the live web socket goodness that is in Gauges. Below is a helpful sketch:</p> <p><img src="/assets/4f54f4e9dabe9d46ae0032f6/article_full/sketch.jpg" class="image full" alt="" /></p> <p>That probably all makes sense, but remember that we weren&#8217;t starting from scratch. We already had servers setup that were tracking requests and I needed to ensure that was uninterrupted.</p> <h2>5. Rollout</h2> <p>Brandon and I have been on a <a href="http://railstips.org/blog/archives/2012/02/06/more-tiny-classes/">tiny classes</a> and services kick of late. <strong>What I am about to say may sound heretical, but we&#8217;ve felt that we need a few more layers in our apps</strong>. We&#8217;ve started using Gauges as a test bed for this stuff, while also spending a lot of time reading about clean code and design patterns.</p> <p>We decided to create a tiny standardization around exposing services and choosing which one gets used in which environment. Brandon took the standardization and <a href="https://github.com/bkeepers/morphine">moved it into a gem</a> where we could start trying stuff and share it with others. It isn&#8217;t much now, but we haven&#8217;t needed it to be.</p> <h3>Declaring Services</h3> <p>We created a Registry class for Gauges, which defined the various pieces we would use for Kestrel. It looked something like this:</p> <pre><code class="ruby">class Registry include Morphine register :track_service do KestrelTrackService.new(kestrel_client, track_config['queue']) end register :track_processor do KestrelTrackProcessor.new(blocking_kestrel_client, track_config['queue']) end end</code></pre> <p>We then store an instance of this register in Gauges.app. We probably should have named it Gauges.registry, but we can worry about that later.</p> <p>At this point, what we did probably seems pointless. The kestrel track service and processor look something like this:</p> <pre><code class="ruby">class KestrelTrackService def initialize(client, queue) @client = client @queue = queue end def record(attrs) @client.set(@queue, MessagePack.pack(attrs)) end end class KestrelTrackProcessor def initialize(client, queue) @client = client @queue = queue end def run loop { process } end def process record @client.get(@queue) end def record(data) Hit.record(MessagePack.unpack(data)) end end</code></pre> <p>The processor uses a blocking kestrel client, which is just a decorator of the vanilla kestrel client. As you can see, all we are doing is wrapping the kestrel-client and making it send the data to the right place.</p> <h3>Using Services</h3> <p>We then used the track_service in our TrackApp like this:</p> <pre><code class="ruby">class TrackApp &lt; Sinatra::Base get '/track.gif' do # stuff Gauges.app.track_service.record(track_attrs) # more stuff end end</code></pre> <p>Then, in our track_processor.rb process, we started the processor like so:</p> <pre><code class="ruby">Gauges.app.track_processor.run</code></pre> <p>Like any good programmer, I knew that we couldn&#8217;t just push this to production and cross our fingers. Instead, I wanted to roll it out to work like normal, but also push track requests to kestrel. This would allow me to see kestrel receiving jobs.</p> <p>On top of that, I also wanted to deploy the track processors to pop track requests off. At this point, I didn&#8217;t want them to actually process those track requests and write to the database, I just wanted to make sure the whole system was wired up correctly and stuff was flowing through it.</p> <p>Another important piece was seeing how many track request we could store in memory with Kestrel, based on our configuration, and how it performed when it used up all the allocated memory and started going to disk.</p> <h3>Service Magic</h3> <p>The extra layer around tracking and processing proved to be super helpful. Note that the above examples used the new Kestrel system, but that I wanted to push this out and go through a verification process first. First, to do the verification process, we created a real-time track service:</p> <pre><code class="ruby">class RealtimeTrackService def record(attrs) Hit.record(attrs) end end </code></pre> <p>This would allow us to change the track_service in the registry to perform as it currently was in production. Now, we have two services that know how to record track requests in a particular way. What I needed next was to use both of these services at the same time so I created a multi track service:</p> <pre><code class="ruby">class MultiTrackService include Enumerable def initialize(*services) @services = services end def record(attrs) each { |service| service.record(attrs) } end def each @services.each do |service| yield service end end end </code></pre> <p>This multi track services allowed me to record to both services for a single track request. The updated registry looked something like this:</p> <pre><code class="ruby">class Registry include Morphine register :track_service do which = track_config.fetch(:service, :realtime) send("#{which}_track_service") end register :multi_track_service do MultiTrackService.new(realtime_track_service, kestrel_track_service) end register :realtime_track_service do RealtimeTrackService.new end register :kestrel_track_service do KestrelTrackService.new(kestrel_client, track_config['queue']) end end</code></pre> <p>Note that now, track_service selects which service to use based on the config. All I had to do was update the config to use &#8220;multi&#8221; as the track service and we were performing realtime track requests while queueing them in Kestrel at the same time.</p> <p>The only thing left was to beef up failure around the Kestrel service so that it was limited in how it could affect production. For this, I chose to catch failures, log them, and move on as if they didn&#8217;t happen.</p> <pre><code class="ruby">class KestrelTrackService def initialize(client, queue, options={}) @client = client @queue = queue @logger = options.fetch(:logger, Logger.new(STDOUT)) end def record(attrs) begin @client.set(@queue, MessagePack.pack(attrs)) rescue =&gt; e log_failure(attrs, e) :error end end private def log_failure(attrs, exception) @logger.info "attrs: #{attrs.inspect} exception: #{exception.inspect}" end end</code></pre> <p>I also had a lot of instrumentation in the various track services, so that I could verify counts at a later point. These verifications counts would prove whether or not things were working. I left that out as it doesn&#8217;t help the article, but you definitely want to verify things when you roll them out.</p> <p>Now that the track service was ready to go, I needed a way to ensure that messages would flow through the track processors without actually modifying data. I used a similar technique as above. I created a new processor, aptly titled NoopTrackProcessor.</p> <pre><code class="ruby">class NoopTrackProcessor &lt; KestrelTrackProcessor def record(data) # don't actually record # instead just run verification end end </code></pre> <p>The noop track processor just inherits from the kestrel track processor and overrides the record method to run verification instead of generating reports.</p> <p>Next, I adjusted the registry to allow flipping the processor that is used based on the config.</p> <pre><code class="ruby">class Registry include Morphine register :track_processor do which = track_config.fetch(:processor, :noop) send("#{which}_track_processor") end register :kestrel_track_processor do KestrelTrackProcessor.new(blocking_kestrel_client, track_config['queue']) end register :noop_track_processor do NoopTrackProcessor.new(blocking_kestrel_client, track_config['queue']) end end</code></pre> <p>With those changes in place, I could now set the track service to multi, the track processor to noop, and I was good to deploy. So I did. And it was wonderful.</p> <h2>6. Verification</h2> <p>For the first few hours, I ran the multi track service and turned off the track processors. This created the effect of queueing and never dequeueing. The point was to see how many messages kestrel could hold in memory and how it performed once messages started going to disk.</p> <p>I used scout realtime to watch things during the evening while enjoying some of my favorite TV shows. A few hours later and almost 530k track requests later, Kestrel hit disk and hummed along like nothing happened.</p> <p><img src="/assets/4f54ffa2dabe9d2bc4010ff0/article_full/kestrel_hits_disk.jpg" class="image full" alt="" /></p> <p>Now that I had a better handle of Kestrel, I turned the track processors back on. Within a few minutes they had popped all the messages off. Remember, at this point, I was still just noop&#8217;ing in the track processors. All reports were still being built in the track request.</p> <p>I let the multi track service and noop track processors run through the night and by morning, when I checked my graphs, I felt pretty confident. I removed the error suppression from the kestrel service and flipped both track service and track processor to kestrel in the config.</p> <p>One more deploy and we were queueing all track requests in Kestrel and popping them off in the track processors after which, the reports were updated in the primary database. This meant our track request now performed a single Kestrel set, instead of several queries and updates. As you would expect, response times dropped like a rock.</p> <p><img src="/assets/4f5500eadabe9d5b6e0035e7/article_full/response_times.jpg" class="image full" alt="" /></p> <p>It is pretty obvious when Kestrel was rolled out as the graph went perfectly flat and dropped to ~4ms response times. <span class="caps">BOOM</span>.</p> <p>You might say, yeah, your track requests are now fast, but your track processors are doing the same work that the app was doing before. You would be correct. Sometimes growing is just about moving slowness into a more manageable place, until you have time to fix it.</p> <p>This change did not just move slowness to a different place though. It separated tracking and reporting. We can now turn the track processors off, make adjustments to the database, turn them back on, and instantly, they start working through the back log of track requests queued up while the database was down. No tracking data lost.</p> <p>I only showed you a handful of things that we instrumented to verify things were working. Another key metric for us, since we aim to be as close to realtime as possible, is the amount of time that it takes to go from queued to processing.</p> <p>Based on the numbers, it takes us around 500ms right now. I believe as long as we keep that number under a second, most people will have no clue that we aren&#8217;t doing everything live.</p> <h2>7. Conclusion</h2> <p>By no means are we where I want us to be availability-wise, but at least we are one more step in the right direction. Hopefully this article gives you a better idea how to roll things out into production safely. Layers are good. Whether you are using Rails, Sinatra, or some other language entirely, layer services so that you can easily change them.</p> <p>Also, we are now a few days in and Kestrel is a beast. Much thanks to <a href="https://github.com/robey">Robey</a> for writing it and Twitter for open sourcing it!</p> John Nunemaker More Tiny Classes 4f2d9117dabe9d290b0076af 2012-02-06T07:00:10-05:00 2012-02-06T07:00:00-05:00 <p>In which I share how we are using more tiny classes to make Gauges more maintainable.</p> <p>My last post, <a href="http://railstips.org/blog/archives/2012/02/04/keep-em-separated/">Keep &#8217;Em Separated</a>, made me realize I should start sharing more about what we are doing to make Gauges maintainable. This post is another in the same vein.</p> <p><a href="http://gaug.es">Gauges</a> allows you to share a gauge with someone else by email. That email does not have to exist prior to your adding it, because nothing is more annoying that wanting to share something with a friend or co-worker, but first having to get them to sign up for the service.</p> <p>If the email address is found, we add the user to the gauge and notify them that they have been added.</p> <p>If the email address is not found, we create an invite and then send an email to notify them they should sign up, so they can see the data.</p> <h2>The Problem: McUggo Route</h2> <p>The aforementioned sharing logic isn&#8217;t difficult, but it was just enough that our share route was getting uggo. It started off looking something like this:</p> <pre><code class="ruby">post('/gauges/:id/shares') do gauge = Gauge.get(params['id']) if user = User.first_by_email(params[:email]) Stats.increment('shares.existing') gauge.add_user(user) ShareWithExistingUserMailer.new(gauge, user).deliver {:share =&gt; SharePresenter.new(gauge, user)}.to_json else invite = gauge.invite(params['email']) Stats.increment('shares.new') ShareWithNewUserMailer.new(gauge, invite).deliver {:share =&gt; SharePresenter.new(gauge, invite)}.to_json end end</code></pre> <p>Let&#8217;s be honest. We&#8217;ve all seen Rails controller actions and Sinatra routes that are fantastically worse, but this was really burning my eyes, so I charged our <a href="http://theprogrammingbutler.com">programming butler</a> to refactor it.</p> <h2>The Solution: Move Logic to Separate Class</h2> <p>We talked some ideas through, and once he had finished, the route looked more like this:</p> <pre><code class="ruby">post('/gauges/:id/shares') do gauge = Gauge.get(params['id']) sharer = GaugeSharer.new(gauge, params['email']) receiver = sharer.perform {:share =&gt; SharePresenter.new(gauge, receiver)}.to_json end</code></pre> <p>Perfect? Who cares. Waaaaaaaaay better? <strong>Yes</strong>. The concern of a user existing or not is <strong>moved away to a place where the route could care less</strong>.</p> <p>Also, the bonus is that <strong>sharing a gauge can now be used without invoking a route</strong>.</p> <p>So what does GaugeSharer look like?</p> <pre><code class="ruby">class GaugeSharer def initialize(gauge, email) @gauge = gauge @email = email end def user @user ||= … # user from database end def existing? user.present? end def perform if existing? share_with_existing_user else share_with_invitee end end def share_with_existing_user # add user to gauge ShareWithExistingUserMailer.new(@gauge, user).deliver user end def share_with_invitee invite = ... # invite to db ShareWithNewUserMailer.new(@gauge, invite).deliver invite end end</code></pre> <p>Now, instead of having several higher-level tests to check each piece of logic, we can just ensure that GaugeSharer is invoked correctly in the route test and then test the crap out of GaugeSharer with unit tests. We can also use GaugeSharer anywhere else in the application that we want to.</p> <p><strong>This isn&#8217;t a dramatic change in code, but it has a dramatic effect on the coder</strong>. Moving all these bits into separate classes and tiny methods improves <strong>ease of testing</strong> and, probably more importantly, <strong>ease of grokking</strong> for another developer, including yourself at a later point in time.</p> John Nunemaker Keep 'Em Separated 4f2d73dddabe9d5bed005b52 2012-02-04T16:14:59-05:00 2012-02-04T13:00:00-05:00 <p>In which I share a quick tale of refactoring.</p> <p><strong>Note</strong>: If you end up enjoying this post, you should do two things: <a href="http://pusher.com/">sign up for Pusher</a> and then <a href="https://www.destroyallsoftware.com/">subscribe to destroy all software screencasts</a>. I&#8217;m not telling you do this because I get referrals, I just really like both services.</p> <p>For those that do not know, <a href="http://get.gaug.es">Gauges</a> currently uses <a href="http://pusher.com">Pusher.com</a> for flinging around all the traffic live.</p> <p>Every track request to Gauges sends a request to Pusher. We do this using EventMachine in a thread, as I have <a href="http://railstips.org/blog/archives/2011/05/04/eventmachine-and-passenger/">previously written about</a>.</p> <h2>The Problem</h2> <p>The downside of this, is when you get to the point we were (thousands of a requests a minute), there are so many pusher notifications to send (thousands of a minute) that <strong>the EM thread starts stealing a lot of time</strong> from the main request thread. You end up with random slow requests that have one to five seconds of &#8220;uninstrumented&#8221; time. <strong>Definitely not a happy scaler does this make</strong>.</p> <p>In the past, we had talked about keeping track of which gauges were actually being watched and only sending a notification for those, but never actually did anything about it.</p> <h2>The Solution</h2> <p>Recently, Pusher added <a href="http://pusher.com/docs/webhooks">web hooks</a> on channel occupy and channel vacate. This, combined with a growing number of slow requests, was just the motivation I needed to come up with a solution.</p> <p>We (<a href="http://opensoul.org/">@bkeepers</a> and I) started by mapping a simple route to a class.</p> <pre><code class="ruby">class PusherApp &lt; BaseApp post '/pusher/ping' do webhook = Pusher::WebHook.new(request) if webhook.valid? PusherPing.receive(webhook) 'ok' else status 401 'invalid' end end end</code></pre> <p>Using a simple class method like this moves all logic out of the route and into a place that is easier to test. The receive method iterates the events and runs each ping individually.</p> <pre><code class="ruby">class PusherPing def self.receive(webhook) webhook.events.each do |event| new(event, webhook.time).run end end end</code></pre> <p>At first, we had something like this for each PusherPing instance.</p> <pre><code class="ruby">class PusherPing def initialize(event, time) @event = event || {} @time = time @event_name = @event['name'] @event_channel = @event['channel'] end def run case @event_name when 'channel_occupied' occupied when 'channel_vacated' vacated end end def occupied update(@time) end def vacated update(nil) end def update(value) # update the gauge in the # db with the value end end</code></pre> <p>We pushed out the change so we could start marking gauges as occupied. We then forced a browser refresh, which effectively vacated and re-occupied all gauges people were watching.</p> <p>Once we new the occupied state of each gauge was correct, we added the code to only send the request to pusher on track if a gauge was occupied.</p> <p>Deploy. Celebrate. Booyeah.</p> <h2>The New Problem</h2> <p>Then, less than a day later, we realized that pusher doesn&#8217;t guarantee the order of events. Imagine someone vacating and then occupying a gauge, but receiving the occupy first and then the vacate.</p> <p><strong>This situation would mean that live tracking would never turn on</strong> for the gauge. Indeed, it started happening to a few people, who quickly let us know.</p> <h2>The New Solution</h2> <p>We figured it was better to send a few extra notifications than never send any, so we decided to &#8220;occupy&#8221; gauges on our own when people loaded up the Gauges dashboard.</p> <p>We started in and quickly realized the error of our ways in the pusher ping. Having the database calls directly tied to the PusherPing class meant that we had two options:</p> <ol> <li>Use the PusherPing class to occupy a gauge when the dashboard loads, which just felt wrong.</li> <li>Re-write it to separate the occupying and vacating of a gauge from the PusherPing class.</li> </ol> <p>Since we are good little developers, we went with 2. We created a GaugeOccupier class that looks like this:</p> <pre><code class="ruby">class GaugeOccupier attr_reader :ids def initialize(*ids) @ids = ids.flatten.compact.uniq end def occupy(time=Time.now.utc) update(time) end def vacate update(nil) end private def update(value) return if @ids.blank? # do the db updates end end</code></pre> <p>We tested that class on its own quite quickly and refactored the PusherPing to use it.</p> <pre><code class="ruby">class PusherPing def run case @event_name when 'channel_occupied' GaugeOccupier.new(gauge_id).occupy(@time) when 'channel_vacated' GaugeOccupier.new(gauge_id).vacate end end end</code></pre> <p>Boom. PusherPing now worked the same and we had a way to &#8220;occupy&#8221; gauges separate from the PusherPing. We added the occupy logic to the correct point in our app like so:</p> <pre><code class="ruby">ids = gauges.map { |gauge| gauge.id } GaugeOccupier.new(ids).occupy</code></pre> <p>At this point, we were now &#8220;occupied&#8221; more than &#8220;vacated&#8221;, which is good. However, you may have noticed, that we still had the issue where someone loads the dashboard, we occupy the gauge, but then receive a delayed, or what I will now refer to as &#8220;stale&#8221;, hook.</p> <p>To fix the stale hook issue, we simply added a bit of logic to the PusherPing class to detect staleness and simple ignore the ping if it is stale.</p> <pre><code class="ruby">class PusherPing def run return if stale? # do occupy/vacate end def stale? return false if gauge.occupied_at.blank? gauge.occupied_at &gt; @time end end</code></pre> <h2>Closing Thoughts</h2> <p>This is by no means a perfect solution. There are still other holes. For example, a gauge could be occupied by us after we receive a vacate hook from pusher and stay in an &#8220;occupied&#8221; state, sending notifications that no one is looking for.</p> <p>To fix that issue, we can add a cleanup cron or something that occasionally gets all occupied channels from pusher and vacates gauges that are not in the list.</p> <p>We decided it wasn&#8217;t worth the time. We pushed out the occupy fix and are now reaping the benefits of sending about 1/6th of the pusher requests we were before. This means our EventMachine thread is doing less work, which gives our main thread more time to process requests.</p> <p>You might think us crazy for sending hundreds of http requests in a thread that shares time with the main request thread, but it is actually working quite well.</p> <p>We know that some day we will have to move this to a queue and an external process that processes the queue, but <strong>that day is not today</strong>. Instead, we can <strong>focus on the next round of features that will blow people&#8217;s socks off</strong>.</p> John Nunemaker What a Year 4f00aa8adabe9d3fb000d149 2012-01-01T14:13:58-05:00 2012-01-01T13:00:00-05:00 <p>In which I share about a crazy year.</p> <p>The last 12 months have been nuts. My health and professional/personal life were completely at odds.</p> <p>Between January and August, I had three hernia surgeries. As if that wasn&#8217;t enough for one year, the last few months of the year I&#8217;ve been plagued by a few other ailments (which are still giving me a hard time). Definitely a rough stretch. I will never take health for granted again and really look forward to getting back to &#8220;normal&#8221;.</p> <p>Quite the contrary to my health, Ordered List grew from 2 to 5 people, helped Zynga launch Words with Friends on Facebook, launched <a href="http://gaug.es">Gauges</a> and <a href="http://speakerdeck.com">Speaker Deck</a> while improving <a href="http://harmonyapp.com">Harmony</a>, and, finally, was acquired by the only other company in the world I wanted to be a part of, <a href="http://github.com">GitHub</a>.</p> <p>Here is to a healthy 2012.</p> John Nunemaker Acquired 4edd006ddabe9d380e005b02 2011-12-05T13:03:56-05:00 2011-12-05T12:00:00-05:00 <p>In which I announce my first day at GitHub.</p> <p>Several times over the past few years, I have stated that GitHub is probably the only other place I could see myself working. Today, it is official. All of Ordered List has joined GitHub.</p> <p><a href="https://github.com/blog/993-ordered-list-is-a-githubber"><img src="/assets/4edcff3edabe9d1f7800e4bc/article_full/oloctocat.png" alt="" /></a></p> <p>Maybe someday I&#8217;ll write about what Ordered List has meant to me, but today I am going to fully enjoy the present, instead of rambling about the past. I have no doubt great things will come of this.</p> <p>You can read more at <a href="https://github.com/blog/993-ordered-list-is-a-githubber">GitHub</a> and <a href="http://orderedlist.com/blog/articles/ordered-list-acquired-by-github/">Ordered List</a>.</p> John Nunemaker Creating an API 4ed78dd8dabe9d5ded01f52c 2011-12-01T12:43:33-05:00 2011-12-01T09:00:00-05:00 <p>In which I share a few things we used while building the Gaug.es <span class="caps">API</span>.</p> <p>A few weeks back, we publicly released the <a href="http://get.gaug.es/documentation/api/">Gauges <span class="caps">API</span></a>. Despite building <a href="http://gaug.es">Gauges</a> from the ground up as an <span class="caps">API</span>, it was a lot of work. You really have to cross your t&#8217;s and dot your i&#8217;s when releasing an <span class="caps">API</span>.</p> <h2>1. Document as You Build</h2> <p>We made the mistake of documenting after most of the build was done. The problem is documenting sucks. Leaving that pain until the end, when you are excited to release it, makes doing the work twice as hard. Thankfully, we have a <a href="http://theprogrammingbutler.com">closer</a> on our team who powered through it.</p> <h2>2. Be Consistent</h2> <p>As we documented the <span class="caps">API</span>, we noticed a lot of inconsistencies. For example, in some places we return a hash and in others we returned an array. Upon realizing these issues, we started making some rules.</p> <p>To solve the array/hash issue, we elected that every response should return a hash. This is the most flexible solution going forward. It allows us to inject new keys without having to convert the response or release a whole new version of the <span class="caps">API</span>.</p> <p>Changing from an array to a hash meant that we needed to namespace the array with a key. We then noticed that some places were name-spaced and others weren&#8217;t. Again, we decided on a rule. In this case, all top level objects should be name-spaced, but objects referenced from a top level object or a collection of several objects did not require name-spacing.</p> <pre><code class="javascript">{users:[{user:{...}}, {user:{...}}]} // nope {users:[{...}, {...}]} // yep {username: 'jnunemaker'} // nope {user: {username:'jnunemaker'}} // yep </code></pre> <p>You get the idea. Consistency is important. It is not so much how you do it as that you always do it the same.</p> <h2>3. Provide the URLs</h2> <p>Most of my initial open source work was wrapping APIs. The one thing that always annoyed me was having to generate urls. Each resource should know the URLs that matter. For example, a user resource in Gauges has a few URLs that can be called to get various data:</p> <pre><code class="javascript">{ "user": { "name": "John Doe", "urls": { "self": "https://secure.gaug.es/me", "gauges": "https://secure.gaug.es/gauges", "clients": "https://secure.gaug.es/clients" }, "id": "4e206261e5947c1d38000001", "last_name": "Doe", "email": "john@doe.com", "first_name": "John" } }</code></pre> <p>The previous <span class="caps">JSON</span> is the response of the resource /me. /me returns data about the authenticated user and the URLs to update itself (self), get all gauges (/gauges), and get all <span class="caps">API</span> clients (/clients). Let&#8217;s say next you request /gauges. Each gauge returned has the URLs to get more data about the gauge.</p> <pre><code class="javascript">{ "gauges": [ { // various attributes "urls": { "self":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001", "referrers":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001/referrers", "technology":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001/technology", // ... etc }, } ] }</code></pre> <p>We thought this would prove helpful. We&#8217;ll see in the long run if it turns out to work well.</p> <h2>4. Present the Data</h2> <p>Finally, never ever use to_json and friends from a controller or sinatra get/post/put block. At least as a bare minimum rule, the second you start calling to_json with :methods, :except, :only, or any of the other options, you probably want to move it to a separate class.</p> <p>For Gauges, we call these classes presenters. For example, here is a simplified version of the UserPresenter.</p> <pre><code class="ruby">class UserPresenter def initialize(user) @user = user end def as_json(*) { 'id' =&gt; @user.id, 'email' =&gt; @user.email, 'name' =&gt; @user.name, 'first_name' =&gt; @user.first_name, 'last_name' =&gt; @user.last_name, 'urls' =&gt; { 'self' =&gt; "#{Gauges.api_url}/me", 'gauges' =&gt; "#{Gauges.api_url}/gauges", 'clients' =&gt; "#{Gauges.api_url}/clients", } } end end</code></pre> <p>Nothing fancy. Just a simple ruby class that sits in app/presenters. Here is an example of the the /me route looks like in our Sinatra app.</p> <pre><code class="ruby">get('/me') do content_type(:json) sign_in_required {:user =&gt; UserPresenter.new(current_user)}.to_json end</code></pre> <p>This simple presentation layer makes it really easy to test the responses in detail using unit tests and then just have a single integration test that makes sure overall things look good. I&#8217;ve found this tiny layer a breath of fresh air.</p> <p>I am sure that nothing above was shocking or awe-inspiring, but I hope that it saves you some time on your next public <span class="caps">API</span>.</p> John Nunemaker Stupid Simple Debugging 4e5fa602dabe9d2e8d007bf0 2011-09-01T11:49:52-04:00 2011-08-31T23:00:00-04:00 <p>In which I talk about old school debugging.</p> <p>There are all kinds of fancy debugging tools out there, but personally, I get the most mileage out of good old puts statements.</p> <p>When I started with Ruby, several years ago, I used puts like this to debug:</p> <pre><code class="ruby">puts account.inspect</code></pre> <p>The problem with this is two fold. First, if you have a few puts statements, you don&#8217;t know which one is actually which object. This always led me to doing something like this:</p> <pre><code class="ruby">puts "account: #{account.inspect}"</code></pre> <p>Second, depending on whether you are just in Ruby or running an app through a web server, puts is sometimes swallowed. This led me to often times do something like this when using Rails:</p> <pre><code class="ruby">Rails.logger.debug "account: #{account.inspect}"</code></pre> <p>Now, not only do I have to think about which method to use to debug something, I also have to think about where the output will be sent so I can watch for it.</p> <h2>Enter Log Buddy</h2> <p>Then, one fateful afternoon, I stumbled across log buddy (gem install log_buddy). In every project, whether it be a library, Rails app, or Sinatra app, <strong>one of the first gems I throw in my Gemfile is log_buddy</strong>.</p> <p>Once you have the gem installed, you can tell log buddy where your log file is and whether or not to actually log like so:</p> <pre><code class="ruby">LogBuddy.init({ :logger =&gt; Gauges.logger, :disabled =&gt; Gauges.production?, })</code></pre> <p>Simply provide log buddy with a logger and tell it if you want it to be silenced in a given situation or environment and you get some nice bang for your buck.</p> <h2>One Method, One Character</h2> <p>First, log buddy adds a nice and short method named <code>d</code>. <code>d</code> is 4X shorter than <code>puts</code>, so right off the bat you get some productivity gains. The <code>d</code> method takes any argument and calls inspect on it. Short and sweet.</p> <pre><code class="ruby">d account # will puts account.inspect d 'Some message' # will puts "Some message"</code></pre> <p>The cool part is that on top of printing the inspected object to stdout, it also logs it to the logger provided in in LogBuddy.init. No more thinking about which method to use or where output will be. One method, output is sent to multiple places.</p> <p>This is nice, but it won&#8217;t win you any new friends. Where log buddy gets really cool, is when you pass it a block.</p> <pre><code class="ruby">d { account } # puts and logs account = &lt;Account ...&gt;</code></pre> <p>Again, one method, output to stdout and your log file, but when you use a block, it does magic to print out the variable name and that inspected value. You can also pass in several objects, separating them with semi-colons.</p> <pre><code class="ruby">d { account; account.creator; current_user }</code></pre> <p>This gives you each variable on its own line with the name and inspected value. Nothing fancy, but log buddy has saved me a lot of time over the past year. I figured it was time I send it some love.</p> John Nunemaker