Advanced Rails Studio Day 3 (Live Blogging)

The day starts with a list of potential topics. The class body votes on the topics that they want to cover. All of my votes went to ‘file uploads’ for work reasons – and fortunately this pushed it over the top! :D

And the results in order are:

  1. AJAX
  2. File Uploads
  3. Perf & Scalability
  4. Deployment
  5. AR with unsupported DBs
  6. Debugging

Notes after the jump:

AJAX & RJS

  • Motherhood and apple pie describing RJS, Prototype, and Script.aculo.us at a high level
  • DT: Believes that fewer web developers (well, he said “Rails”) are trying to emlate desktop applications with AJAX
    • Me: Or those few that are do a better job? (i.e., Meebo)
ruby -e "self.javascript_guru?"
=> false
  • Hopefully this doesn’t surprise anyone (again, motherhood and apple pie here) but: RJS generates JavaScript server side and sends the JS back to Prototype which evals the generated JavaScript which updates the DOM.
  • For custom JS:
    • page << "raw javascript here" and it concatenates the JS in order with the other calls to page.
  • Interesting to me: the “Old Way” involves generating DOM IDs in the calling view and the RJS – been there done that
    • In Rails 2.0: the “simply_helpful” plugin provides a div_for and tag_for that generate the IDs for you. Sweet!
    • From the audience: JRails is a plugin that monkey-patches Rails to support JQuery. From toying with JQuery a little, I <3 it.
  • page.select takes a CSS selector and generates appropriate GS
  page.select("div.queston").each do |q|
    q.visual_effect ....
  end
  • I dig it.
  • DT: Refactor into a helper if you’re going to reuse this (yup yup)
    • page[:an_element].reload seeks out a partial “_an_element.erb” and updates the element
    • update_page { |p| p.alert("Hello!") } just generates a String
    • Can be called from controller, helper, or view
    • CF: puts his RJS in the controller! His RJS code tends to be very short. If you’re doing more than that, then you’re probably making a busy page. (This is a handy take away)

Example:

  render :update do |page|
    page[:foo].your\_rjs\_operation
  end
  • To ‘easily’ support AJAX and synchronous:
  respond_to do |format|
    format.html { do something }  # handles text/html http requests
    format.js                     # causes lookup of RJS if making an AJAX request
  end
  • CF: Can call update_page as often as you want unlike render
    • Makes it easy to mix JS, HTML, and Ruby in the view – but could get ugly
    • So write helpers to be called via link_to_function (Slide 205 and 206)
  • Audience remark: Testing a page that can respond to text/html and a JS request seems as though it can be brittle/hard to test

    • CF: Yes, it’s harder to test a site that does AJAX than one that does not
    • DT: Triple good point!: With AJAX, you have to consider how friendly your website is to screen readers. I hope that my current colleagues are reading this….
  • Writing your own RJS extensions:

    • The JS generators uses method_missing so page.lightbox.dosomething would invoke Lightbox.dosomething in JS.
    • Be careful of the page size resulting from all of the code generated into the HTML

break

File Uploads

  • Two basic solutions:

    • attachment_fu plugin
    • paperclip
    • Biggest recipe in the Advanced Recipes book and freely available on the book site
  • Topics interesting to audience

    • Performance (my question)
    • Custom Mongrel handler
      • CF: Should be about 10 lines of code
      • MC: Code should be out there somewhere
    • Merb (obvious solution but thicker than it used to be?) – started as a custom Mongrel handler
    • Multiple concurrent uploads (also of interest)
    • Me: See Ezra’s talk on this
    • Progress bars
  • attachment_fu (yes, I’ve never used it. Shoot me.)

    • In events_final web app:
    • User has a Portrait
    • Portrait has_attachment is monkey-patched into AR (See code sample in events_final)
    • Some columns in the DB are required by attachment_fu
      • parent_id
      • width
      • height
      • content_type
      • filename
      • thumbnail (can have several)
    • Interesting notion of creating a logical model (in that it is not represented by a table in the DB) “PortraitService” (crappy name) delegate to:
    • create a transaction to handle the tasks of:
      • destroying an existing portrait
      • assigning the portrait to a user
      • saving the portrait
    • handle updates to the portrait
  • Little disappointed that the PragProg guys weren’t prepared to discuss custom Mongrel handlers due to Rails single-threaded limitation. Mentioned it to Chad as a future add for the course. Merb wouldn’t even exist if this wasn’t a severe limitation with Rails; Ezra started it as just a simple custom Mongrel handler.

  • And, given my input, Chad is digging it into a little now. :D

    • mongrel_cluster.yml
    • config_script option allows specification of custom Mongrel handler
require 'mongrel'
class MyHandler < Mongrel::HttpHandler
  def process(req, res)
    res.start do |out|
      out.write something     # do something useful here
    end
  end
end

uri "/myuploadhandler", MyHandler, :in_front => true
  • And just post to the URI specified in the custom handler.
  • This will run in a separate thread (which is the point)

Performance and Scaling

Really only have conceptual knowledge of this myself so the nitty gritty examples here were really handy.

  • Key points
    • Measure performance often
    • Identify hotspots
    • Change one thing at a time
    • Iterate
  • This jibes with Ezra’s remarks about performance optimization when he first started talking about custom Mongrel handlers and led into Merb.
  • Corollary: Guessing at performance bottlenecks is a waste of time.
  • MC quoting Ezra: “Post-mature optimizations are the cost of most hosting bills”
  • Optimizing AR
    • Look at the log files
    • Obviously, AR-generated queries could be ugly – so look at log files early
    • DT: Requests/second is the most useless statistic in the log file
    • DT: AR makes life simple – and sometimes simple is stupid
    • Selective Columns
    • Custom finders:
      • Use the :select option to specify the columns to pull.
      • Pull the ID column to avoid possible silent DB failure (DB specific)
    • Preloading Child Rows
    • For N + 1 queries (i.e. has_many :line_items that may have products itself)
      • Use of :include => :product
      • MC mentioned yesterday, take care with use of :include
      • DT: Please, please, measure when using :include. It may not help performance.
      • MC: suggests having separate associations for explicitness (readability and performance)
  has\_many line\_items
  has\_many line\_items\_with\_products :include => product
  • Assigning query results to a constant in an AR class that will load the results at startup and cache the results in the object model
    • Great for things that don’t change such as countries, states, etc.
  • ActiveRecord Query Cache
    • Based in identical SQL
    • Cleared on CUD
  Event.cache do |e|
    e.find(1) # DB
    e.find(1) # Cache
    e.update
    e.find(1) # DB
  end
  • Cache Store (Rails 2.1 – so still Edge)
    • API for configuring default store
      • Supports memory, file system, DRb (WTF?), memcache
      • So I ask: For memcache, this means a shared cache for a Mongrel cluster?
      • MC: yes
      • Sweet! (Yes, I loved Dude, Where’s My Car)
  store = ActionController::Base.cache_store
  store.write(key, value)

  stuff = store.fetch(key) do
    ARClass.stuff(key)
  end
  • Note that this is not just for ActiveRecord
  • Allows for caching across requests
  • Even more complex examples on slide 309

lunch

  • Page Caching
    • Pages that don’t take any parameters
    • Pages that don’t change
    • Cron job for cleaning up cache
    • Can’t cache
      • Personalized pages, pages w/ params, pages w/ content changes, time-based content
        • Although, for the latter, you could cache and force a refresh by destroying the cache periodically…
class SomethingsController < ApplicationController
  caches_page :index, :show
end
  • Fragment Caching
    • Cache small parts of templates
    • cache can be further parameterized to cache per user, etc.
    • Expiring specification is available in 2.1
      • But also via plugin: (see Karen Gillison)
  <% cache('some label') do -%>
    Your HTML goes here
  <% end -%>
  • Expiring caches

    • expire_page
    • expire_action
    • expire_fragment
  • Sweepers

    • For cleaning up caches
    • Observes models
    • Lifecycle actions on Sweeper get invoked when the lifecycle actions on the observed Model get invoked
    • Slide 319
    • In the controller, have to specify the Sweeper (see below)
    • To avoid having observers impact performance of transactions, put the expirey on a queue and operate on it later.
      • Me: If this were J2EE, shove it into a JMS queue – but J2EE…. ick.

DT: Complaining about the number of moving parts that are now being integrated into Rails: memcached, Starling, multiple Mongrels, Ferret, etc. Deployment is more painful than J2EE (Sadly, agree violently). Waiting for a backlash to occur at the complexity of it. Look at Twitter and how often it fails due to some SPOF (single point of failure). People writing many of the tools/plugins do not have enterprise experience and so are not taking external dependencies into account.

class MyController....
  cache_sweeper :my_sweeper
end
  • Memcache

    • Basically an in-memory hash (that you do not always need to use depending on your app)
    • cache_fu at the model level
      • supports TTL
      • Probably less relevant as of Rails 2.1
  • HTTP Performance

    • javascript_include_tag(:all, :cache => true)
      • Combines (and possibly compresses) JS files
    • ActionController:Base.asset_host = 'asssets%d.example.com'
      • Allows for federating assets across servers to avoid browser-limited number of connections per server
    • Yslow Firebug plugin (Andrew Willis mentioned this to me at lunch on Monday)
    • Tools
      • httperf
      • Siege
      • Profiler
  • General Points

    • Don’t use file-based sessions
    • Don’t forget indices
    • Watch the log
    • Cache where useful
  • DT: Consider adding some automated monitoring tools to determine when the system is beginning to perform badly

break

  • Worth noting that AOL Photos uses Rails – if you’re looking for another example of a large company relying on Rails.

Deploying

  • Deploy early
    • Me: It was personal development, but I was impatient to deploy the first (and only so far) time that I’ve used Capistrano. Better if I had tried to deploy earlier to forego that. Similar to TDD/BDD: makes the behavior less painful.
  • How many Mongrels in a cluster?
    • MC: Varies but fewer than you think
  • Question about not needing Mongrels
    • CF/DT: mod_rails – still in beta
    • Ruby processes that Apache comunicates with via *NIX sockets
    • Forking a process – so as some point out its similar to FastCGI
    • MC: There’s also Thin, Ezra’s work tying Rack and Rails together
  • MC: They started by using Apache 2.2.x
    • mod_rewrite
    • mod_proxy_balancer
  • Nginx
    • Small memory footprint
    • Easier to configure than Apache
    • MC: unless you have a damn good reason, pick Nginx (I need to use this beast – been hearing about it for over a year…)
  • “Heartbeat”
    • Custom Mongrel handler excellent for this

Posted by evan on Wednesday, May 07, 2008

blog comments powered by Disqus